@@ -4,21 +4,77 @@ Released under Apache 2.0 license as described in the file LICENSE.
4
4
Authors: Michael Rothgang
5
5
-/
6
6
7
+ import Lean.Elab.ParseImportsFast
8
+ import Batteries.Data.String.Basic
7
9
import Mathlib.Tactic.Linter.TextBased
8
10
import Cli.Basic
9
11
10
12
/-!
11
13
# Text-based style linters
12
14
13
- This files defines the `lint-style` executable which runs all text-based style linters.
15
+ This file defines the `lint-style` executable which runs all text-based style linters.
14
16
The linters themselves are defined in `Mathlib.Tactic.Linter.TextBased`.
15
17
16
- In addition, this checks that every file in `scripts` is documented in its top-level README.
18
+ In addition, this checks that
19
+ - `Mathlib.Init` is (transitively) imported in all of mathlib, and
20
+ - every file in `scripts` is documented in its top-level README.
17
21
-/
18
22
19
- open Cli Mathlib.Linter.TextBased
23
+ open Cli Mathlib.Linter.TextBased System.FilePath
24
+
25
+ /-- Parse all imports in a text file at `path` and return just their names:
26
+ this is just a thin wrapper around `Lean.parseImports'`.
27
+ Omit `Init (which is part of the prelude). -/
28
+ def findImports (path : System.FilePath) : IO (Array Lean.Name) := do
29
+ return (← Lean.parseImports' (← IO.FS.readFile path) path.toString)
30
+ |>.map (fun imp ↦ imp.module) |>.erase `Init
31
+
32
+
33
+ /-- Check that `Mathlib.Init` is transitively imported in all of Mathlib.
34
+
35
+ Every file imported in `Mathlib.Init` should in turn import the `Header` linter
36
+ (except for the header linter itself, of course).
37
+ Return `true` iff there was an error.
38
+ -/
39
+ def checkInitImports : IO Bool := do
40
+ -- Find any file in the Mathlib directory which does not contain any Mathlib import.
41
+ -- We simply parse `Mathlib.lean`, as CI ensures this file is up to date.
42
+ let allModuleNames := (← findImports "Mathlib.lean" ).erase `Batteries
43
+ let mut modulesWithoutMathlibImports := #[]
44
+ let mut importsHeaderLinter := #[]
45
+ for module in allModuleNames do
46
+ let path := System.mkFilePath (module.components.map fun n ↦ n.toString)|>.addExtension "lean"
47
+ let imports ← findImports path
48
+ let hasNoMathlibImport := imports.all fun name ↦ name.getRoot != `Mathlib
49
+ if hasNoMathlibImport then
50
+ modulesWithoutMathlibImports := modulesWithoutMathlibImports.push module
51
+ if imports.contains `Mathlib.Tactic.Linter.Header then
52
+ importsHeaderLinter := importsHeaderLinter.push module
53
+
54
+ -- Every file importing the `header` linter should be imported in `Mathlib/Init.lean` itself.
55
+ -- (Downstream files should import `Mathlib.Init` and not the header linter.)
56
+ -- The only exception are auto-generated import-only files.
57
+ let initImports ← findImports ("Mathlib" / "Init.lean" )
58
+ let mismatch := importsHeaderLinter.filter (fun mod ↦
59
+ ![`Mathlib, `Mathlib.Tactic, `Mathlib.Init].contains mod && !initImports.contains mod)
60
+ -- This file is transitively imported by `Mathlib.Init`.
61
+ |>.erase `Mathlib.Tactic.DeclarationNames
62
+ if mismatch.size > 0 then
63
+ IO.eprintln s! "error: the following { mismatch.size} module(s) import the `header` linter \
64
+ directly, but should import Mathlib.Init instead: { mismatch} \n \
65
+ The `header` linter is included in Mathlib.Init, and every file in Mathlib \
66
+ should import Mathlib.Init.\n Please adjust the imports accordingly."
67
+ return true
68
+
69
+ -- Now, it only remains to check that every module (except for the Header linter itself)
70
+ -- imports some file in Mathlib.
71
+ let missing := modulesWithoutMathlibImports.erase `Mathlib.Tactic.Linter.Header
72
+ if missing.size > 0 then
73
+ IO.eprintln s! "error: the following { missing.size} module(s) do not import Mathlib.Init: \
74
+ { missing} "
75
+ return true
76
+ return false
20
77
21
- open System.FilePath
22
78
23
79
/-- Verifies that every file in the `scripts` directory is documented in `scripts/README.md`.
24
80
Return `True` if there are undocumented scripts, otherwise `False`. -/
@@ -40,19 +96,21 @@ def allScriptsDocumented : IO Bool := do
40
96
{ String.intercalate "," undocumented.toList} "
41
97
return undocumented.size == 0
42
98
99
+
43
100
/-- Implementation of the `lint-style` command line program. -/
44
101
def lintStyleCli (args : Cli.Parsed) : IO UInt32 := do
45
102
let style : ErrorFormat := match args.hasFlag "github" with
46
103
| true => ErrorFormat.github
47
104
| false => ErrorFormat.humanReadable
48
105
let fix := args.hasFlag "fix"
49
106
-- Read all module names to lint.
50
- let mut allModules := #[]
107
+ let mut allModuleNames := #[]
51
108
for s in ["Archive.lean" , "Counterexamples.lean" , "Mathlib.lean" ] do
52
- allModules := allModules.append ((← IO.FS.lines s).map (·.stripPrefix "import " ))
53
- -- Note: since we manually add "Batteries" to "Mathlib.lean", we remove it here manually.
54
- allModules := allModules.erase "Batteries"
55
- let mut numberErrors ← lintModules allModules style fix
109
+ allModuleNames := allModuleNames.append (← findImports s)
110
+ -- Note: since "Batteries" is added explicitly to "Mathlib.lean", we remove it here manually.
111
+ allModuleNames := allModuleNames.erase `Batteries
112
+ let mut numberErrors ← lintModules allModuleNames style fix
113
+ if ← checkInitImports then numberErrors := numberErrors + 1
56
114
if !(← allScriptsDocumented) then numberErrors := numberErrors + 1
57
115
-- If run with the `--fix` argument, return a zero exit code.
58
116
-- Otherwise, make sure to return an exit code of at most 125,
0 commit comments