|
| 1 | +/- |
| 2 | +Copyright (c) 2025 Thomas R. Murrills. All rights reserved. |
| 3 | +Released under Apache 2.0 license as described in the file LICENSE. |
| 4 | +Authors: Thomas R. Murrills |
| 5 | +-/ |
| 6 | +module |
| 7 | + |
| 8 | +public meta import Lean.Elab.Command |
| 9 | +public import Lean.Linter.Basic |
| 10 | +public import Lean.Environment |
| 11 | +-- Import this linter explicitly to ensure that |
| 12 | +-- this file has a valid copyright header and module docstring. |
| 13 | +import Mathlib.Tactic.Linter.Header |
| 14 | + |
| 15 | +/-! |
| 16 | +# Private module linter |
| 17 | +
|
| 18 | +This linter lints against nonempty modules that have only private declarations, and suggests adding |
| 19 | +`@[expose] public section` to the top or selectively marking declarations as `public`. |
| 20 | +
|
| 21 | +## Implementation notes |
| 22 | +
|
| 23 | +`env.constants.map₂` contains all locally-defined constants, and accessing this waits until all |
| 24 | +declarations are added. By linting (only) the `eoi` token, we can capture all constants defined in |
| 25 | +the file. |
| 26 | +
|
| 27 | +Note that private declarations are exactly those which satisfy `isPrivateName`, whether private due |
| 28 | +to an explicit `private` or due to not being made `public`. |
| 29 | +-/ |
| 30 | + |
| 31 | +meta section |
| 32 | + |
| 33 | +open Lean Elab Command Linter |
| 34 | + |
| 35 | +namespace Mathlib.Linter |
| 36 | + |
| 37 | +/-- The `privateModule` linter lints against nonempty modules that have only private declarations, |
| 38 | +and suggests adding `@[expose] public section` or selectively marking declarations as `public`. -/ |
| 39 | +public register_option linter.privateModule : Bool := { |
| 40 | + defValue := false |
| 41 | + descr := "Enable the `privateModule` linter, which lints against nonempty modules that have only \ |
| 42 | + private declarations." |
| 43 | +} |
| 44 | + |
| 45 | +/-- |
| 46 | +The `privateModule` linter lints against nonempty modules that have only private declarations, |
| 47 | +and suggests adding `@[expose] public section` to the top. |
| 48 | +
|
| 49 | +This linter only acts on the end-of-input `Parser.Command.eoi` token, and ignores all other syntax. |
| 50 | +It logs its message at the top of the file. |
| 51 | +-/ |
| 52 | +def privateModule : Linter where run stx := do |
| 53 | + if stx.isOfKind ``Parser.Command.eoi then |
| 54 | + unless getLinterValue linter.privateModule (← getLinterOptions) do |
| 55 | + return |
| 56 | + if (← getEnv).header.isModule then |
| 57 | + -- Don't lint an imports-only module: |
| 58 | + if !(← getEnv).constants.map₂.isEmpty then |
| 59 | + -- Exit if any declaration is public: |
| 60 | + for (decl, _) in (← getEnv).constants.map₂ do |
| 61 | + if !isPrivateName decl then return |
| 62 | + -- Lint if all names are private: |
| 63 | + let topOfFileRef := Syntax.atom (.synthetic ⟨0⟩ ⟨0⟩) "" |
| 64 | + logLint linter.privateModule topOfFileRef |
| 65 | + "The current module only contains private declarations.\n\n\ |
| 66 | + Consider adding `@[expose] public section` at the beginning of the module, \ |
| 67 | + or selectively marking declarations as `public`." |
| 68 | + |
| 69 | +initialize addLinter privateModule |
| 70 | + |
| 71 | +end Mathlib.Linter |
0 commit comments