@@ -6,6 +6,7 @@ Authors: Michael Rothgang
66
77import Batteries.Data.String.Matcher
88import 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: ℕ)
6769deriving 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
109114and 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. -/
289315def broadImportsLinter : TextbasedLinter := fun lines ↦ Id.run do
290316 let mut errors := Array.mkEmpty 0
325351
326352/-- All text-based linters registered in this file. -/
327353def allLinters : Array TextbasedLinter := #[
328- copyrightHeaderLinter, adaptationNoteLinter, broadImportsLinter
354+ copyrightHeaderLinter, adaptationNoteLinter, broadImportsLinter, duplicateImportsLinter
329355 ]
330356
331357
0 commit comments