Skip to content

Commit 0ca9b83

Browse files
committed
feat: lint on (syntactically) duplicate imports (#16384)
Motivation: PR #16273
1 parent dc64441 commit 0ca9b83

File tree

1 file changed

+27
-1
lines changed

1 file changed

+27
-1
lines changed

Mathlib/Tactic/Linter/TextBased.lean

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Authors: Michael Rothgang
66

77
import Batteries.Data.String.Matcher
88
import Mathlib.Data.Nat.Notation
9+
import Std.Data.HashMap.Basic
910

1011
/-!
1112
## Text-based linters
@@ -64,6 +65,7 @@ inductive StyleError where
6465
| broadImport (module : BroadImports)
6566
/-- A line ends with windows line endings (\r\n) instead of unix ones (\n). -/
6667
| windowsLineEnding
68+
| duplicateImport (importStatement: String) (alreadyImportedLine: ℕ)
6769
deriving BEq
6870

6971
/-- How to format style errors -/
@@ -94,6 +96,8 @@ def StyleError.errorMessage (err : StyleError) : String := match err with
9496
benchmark it. If this is fine, feel free to allow this linter."
9597
| windowsLineEnding => "This line ends with a windows line ending (\r\n): please use Unix line\
9698
endings (\n) instead"
99+
| StyleError.duplicateImport (importStatement) (alreadyImportedLine) =>
100+
s!"Duplicate imports: {importStatement} (already imported on line {alreadyImportedLine})"
97101

98102
/-- The error code for a given style error. Keep this in sync with `parse?_errorContext` below! -/
99103
-- FUTURE: we're matching the old codes in `lint-style.py` for compatibility;
@@ -104,6 +108,7 @@ def StyleError.errorCode (err : StyleError) : String := match err with
104108
| StyleError.adaptationNote => "ERR_ADN"
105109
| StyleError.broadImport _ => "ERR_IMP"
106110
| StyleError.windowsLineEnding => "ERR_WIN"
111+
| StyleError.duplicateImport _ _ => "ERR_DIMP"
107112

108113
/-- Context for a style error: the actual error, the line number in the file we're reading
109114
and the path to the file. -/
@@ -190,6 +195,7 @@ def parse?_errorContext (line : String) : Option ErrorContext := Id.run do
190195
| "ERR_AUT" => some (StyleError.authors)
191196
| "ERR_ADN" => some (StyleError.adaptationNote)
192197
| "ERR_WIN" => some (StyleError.windowsLineEnding)
198+
| "ERR_DIMP" => some (StyleError.duplicateImport "" 0)
193199
| "ERR_IMP" =>
194200
-- XXX tweak exceptions messages to ease parsing?
195201
if (errorMessage.get! 0).containsSubstr "tactic" then
@@ -285,6 +291,26 @@ def adaptationNoteLinter : TextbasedLinter := fun lines ↦ Id.run do
285291
lineNumber := lineNumber + 1
286292
return (errors, none)
287293

294+
/-- Lint on a collection of input strings if one of the is a duplicate import statement. -/
295+
def duplicateImportsLinter : TextbasedLinter := fun lines ↦ Id.run do
296+
let mut lineNumber := 1
297+
let mut errors := Array.mkEmpty 0
298+
let mut importStatements : Std.HashMap String ℕ := {}
299+
for line in lines do
300+
if line.startsWith "import " then
301+
let lineWithoutComment := (line.splitOn "--")[0]!
302+
let importStatement := lineWithoutComment.trim
303+
if importStatements.contains importStatement then
304+
let alreadyImportedLine := importStatements[importStatement]!
305+
errors := errors.push (
306+
(StyleError.duplicateImport importStatement alreadyImportedLine),
307+
lineNumber
308+
)
309+
else
310+
importStatements := importStatements.insert importStatement lineNumber
311+
lineNumber := lineNumber + 1
312+
return (errors, none)
313+
288314
/-- Lint a collection of input strings if one of them contains an unnecessarily broad import. -/
289315
def broadImportsLinter : TextbasedLinter := fun lines ↦ Id.run do
290316
let mut errors := Array.mkEmpty 0
@@ -325,7 +351,7 @@ end
325351

326352
/-- All text-based linters registered in this file. -/
327353
def allLinters : Array TextbasedLinter := #[
328-
copyrightHeaderLinter, adaptationNoteLinter, broadImportsLinter
354+
copyrightHeaderLinter, adaptationNoteLinter, broadImportsLinter, duplicateImportsLinter
329355
]
330356

331357

0 commit comments

Comments
 (0)