Skip to content

Commit ca9e9db

Browse files
committed
feat(lint-style): fix update-style-exceptions.py; produce human-readable output by default (#14012)
- Fix the output of `update-style-exceptions.py` by making `lake exe lint_style` optionally produce output in the right format: this regressed in #13620 - The current error messages are tailored for github annotations, which are not very readable for running the linter locally. Produce a human-readable and clickable error by default, but add a flag (which CI sets) for producing github-style output. This entails adding a small CLI for the lint-style executable.
1 parent 756e653 commit ca9e9db

File tree

3 files changed

+79
-26
lines changed

3 files changed

+79
-26
lines changed

Mathlib/Tactic/Linter/TextBased.lean

Lines changed: 77 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Authors: Michael Rothgang
55
-/
66

77
import Batteries.Data.String.Basic
8+
import Cli.Basic
89
import Mathlib.Init.Data.Nat.Notation
910

1011
/-!
@@ -30,10 +31,27 @@ inductive StyleError where
3031
| fileTooLong (number_lines : ℕ) (new_size_limit : ℕ) : StyleError
3132
deriving BEq
3233

34+
/-- How to format style errors -/
35+
inductive ErrorFormat
36+
/-- Produce style error output aimed at humans: no error code, clickable file name -/
37+
| humanReadable : ErrorFormat
38+
/-- Produce an entry in the style-exceptions file: mention the error code, slightly uglier
39+
than humand-readable output -/
40+
| exceptionsFile : ErrorFormat
41+
/-- Produce output suitable for Github error annotations: in particular,
42+
duplicate the file path, line number and error code -/
43+
| github : ErrorFormat
44+
deriving BEq
45+
3346
/-- Create the underlying error message for a given `StyleError`. -/
34-
def StyleError.errorMessage (err : StyleError) : String := match err with
47+
def StyleError.errorMessage (err : StyleError) (style : ErrorFormat) : String := match err with
3548
| StyleError.fileTooLong current_size size_limit =>
36-
s!"{size_limit} file contains {current_size} lines, try to split it up"
49+
match style with
50+
| ErrorFormat.github =>
51+
s!"file contains {current_size} lines (at most {size_limit} allowed), try to split it up"
52+
| ErrorFormat.exceptionsFile =>
53+
s!"{size_limit} file contains {current_size} lines, try to split it up"
54+
| ErrorFormat.humanReadable => s!"file contains {current_size} lines, try to split it up"
3755

3856
/-- The error code for a given style error. Keep this in sync with `parse?_errorContext` below! -/
3957
-- FUTURE: we're matching the old codes in `lint-style.py` for compatibility;
@@ -65,14 +83,24 @@ instance : BEq ErrorContext where
6583
-- We normalise errors before comparing them.
6684
&& (ctx.error).normalise == (ctx'.error).normalise
6785

68-
/-- Output the formatted error message, containing its context. -/
69-
def outputMessage (errctx : ErrorContext) : String :=
70-
-- We are outputting for github: duplicate file path, line number and error code,
71-
-- so that they are also visible in the plain text output.
72-
let path := errctx.path
73-
let nr := errctx.lineNumber
74-
let code := errctx.error.errorCode
75-
s!"::ERR file={path},line={nr},code={code}::{path}:{nr} {code}: {errctx.error.errorMessage}"
86+
/-- Output the formatted error message, containing its context.
87+
`style` specifies if the error should be formatted for humans or for github output matchers -/
88+
def outputMessage (errctx : ErrorContext) (style : ErrorFormat) : String :=
89+
let error_message := errctx.error.errorMessage style
90+
match style with
91+
| ErrorFormat.github =>
92+
-- We are outputting for github: duplicate file path, line number and error code,
93+
-- so that they are also visible in the plain text output.
94+
let path := errctx.path
95+
let nr := errctx.lineNumber
96+
let code := errctx.error.errorCode
97+
s!"::ERR file={path},line={nr},code={code}::{path}:{nr} {code}: {error_message}"
98+
| ErrorFormat.exceptionsFile =>
99+
-- Produce an entry in the exceptions file: with error code and "line" in front of the number.
100+
s!"{errctx.path} : line {errctx.lineNumber} : {errctx.error.errorCode} : {error_message}"
101+
| ErrorFormat.humanReadable =>
102+
-- Print for humans: clickable file name and omit the error code
103+
s!"error: {errctx.path}:{errctx.lineNumber}: {error_message}"
76104

77105
/-- Try parsing an `ErrorContext` from a string: return `some` if successful, `none` otherwise. -/
78106
def parse?_errorContext (line : String) : Option ErrorContext := Id.run do
@@ -111,10 +139,11 @@ def parseStyleExceptions (lines : Array String) : Array ErrorContext := Id.run d
111139
-- We treat all lines starting with "--" as a comment and ignore them.
112140
Array.filterMap (parse?_errorContext ·) (lines.filter (fun line ↦ !line.startsWith "--"))
113141

114-
/-- Print information about all errors encountered to standard output. -/
115-
def formatErrors (errors : Array ErrorContext) : IO Unit := do
142+
/-- Print information about all errors encountered to standard output.
143+
`style` specifies if we print errors for humand or github consumption. -/
144+
def formatErrors (errors : Array ErrorContext) (style : ErrorFormat) : IO Unit := do
116145
for e in errors do
117-
IO.println (outputMessage e)
146+
IO.println (outputMessage e style)
118147

119148
/-- Core logic of a text based linter: given a collection of lines,
120149
return an array of all style errors with line numbers. -/
@@ -150,22 +179,24 @@ end
150179

151180
/-- Read a file, apply all text-based linters and print formatted errors.
152181
Return `true` if there were new errors (and `false` otherwise).
153-
`sizeLimit` is any pre-existing limit on this file's size. -/
154-
def lintFile (path : FilePath) (sizeLimit : Option ℕ) : IO Bool := do
182+
`sizeLimit` is any pre-existing limit on this file's size.
183+
`style` specifies if errors should be formatted for github or human consumption. -/
184+
def lintFile (path : FilePath) (sizeLimit : Option ℕ) (style : ErrorFormat) : IO Bool := do
155185
let lines ← IO.FS.lines path
156186
-- We don't need to run any checks on imports-only files.
157187
-- NB. The Python script used to still run a few linters; this is in fact not necessary.
158188
if isImportsOnlyFile lines then
159189
return false
160190
if let some (StyleError.fileTooLong n limit) := checkFileLength lines sizeLimit then
161191
let arr := Array.mkArray1 (ErrorContext.mk (StyleError.fileTooLong n limit) 1 path)
162-
formatErrors arr
192+
formatErrors arr style
163193
return true
164194
return false
165195

166196
/-- Lint all files referenced in a given import-only file.
167-
Return the number of files which had new style errors. -/
168-
def lintAllFiles (path : FilePath) : IO UInt32 := do
197+
Return the number of files which had new style errors.
198+
`style` specifies if errors should be formatted for github or human consumption. -/
199+
def lintAllFiles (path : FilePath) (style : ErrorFormat) : IO UInt32 := do
169200
-- Read all module names from the file at `path`.
170201
let allModules ← IO.FS.lines path
171202
-- Read the style exceptions file.
@@ -181,14 +212,36 @@ def lintAllFiles (path : FilePath) : IO UInt32 := do
181212
let size_limits := (style_exceptions.filter (·.path == path)).filterMap (fun errctx ↦
182213
match errctx.error with
183214
| StyleError.fileTooLong _ limit => some limit)
184-
if ← lintFile path (size_limits.get? 0) then
215+
if ← lintFile path (size_limits.get? 0) style then
185216
number_error_files := number_error_files + 1
186217
return number_error_files
187218

188-
/-- The entry point to the `lake exe lint_style` command. -/
189-
def main : IO UInt32 := do
190-
let mut number_error_files := 0
219+
open Cli in
220+
/-- Implementation of the `lint_style` command line program. -/
221+
def lintStyleCli (args : Cli.Parsed) : IO UInt32 := do
222+
let errorStyle := match (args.hasFlag "github", args.hasFlag "update") with
223+
| (true, _) => ErrorFormat.github
224+
| (false, true) => ErrorFormat.exceptionsFile
225+
| (false, false) => ErrorFormat.humanReadable
226+
let mut number_error_files : UInt32 := 0
191227
for s in ["Archive.lean", "Counterexamples.lean", "Mathlib.lean"] do
192-
let n ← lintAllFiles (mkFilePath [s])
228+
let n ← lintAllFiles (mkFilePath [s]) errorStyle
193229
number_error_files := number_error_files + n
194-
return number_error_files
230+
return number_error_files
231+
232+
open Cli in
233+
/-- Setting up command line options and help text for `lake exe lint_style`. -/
234+
-- so far, no help options or so: perhaps that is fine?
235+
def lint_style : Cmd := `[Cli|
236+
lint_style VIA lintStyleCli; ["0.0.1"]
237+
"Run text-based style linters on every Lean file in Mathlib/, Archive/ and Counterexamples/.
238+
Print errors about any unexpected style errors to standard output."
239+
240+
FLAGS:
241+
github; "Print errors in a format suitable for github problem matchers\n\
242+
otherwise, produce human-readable output"
243+
update; "Print errors solely for the style exceptions file"
244+
]
245+
246+
/-- The entry point to the `lake exe lint_style` command. -/
247+
def main (args : List String) : IO UInt32 := do lint_style.validate args

scripts/lint-style.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ git ls-files 'Archive/*.lean' | xargs ./scripts/lint-style.py "$@"
4343
git ls-files 'Counterexamples/*.lean' | xargs ./scripts/lint-style.py "$@"
4444

4545
# Call the in-progress Lean rewrite of these Python lints.
46-
lake exe lint_style
46+
lake exe lint_style --github
4747

4848
# 2. Global checks on the mathlib repository
4949

scripts/update-style-exceptions.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@
1818
find Mathlib -name '*.lean' | xargs ./scripts/lint-style.py | LC_ALL=C sort > scripts/style-exceptions.txt
1919

2020
# Append the warnings of the Lean linter, on the file length.
21-
lake exe lint_style >> scripts/style-exceptions.txt
21+
lake exe lint_style --update >> scripts/style-exceptions.txt

0 commit comments

Comments
 (0)