/-
Copyright 2026 The Formal Conjectures Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-/
module
public import FormalConjecturesForMathlib.Lean.Elab.InfoTree.Util
public import Lean.Elab.Command
public import Lean.Linter.Basic
public import Lean.Meta.Basic
public import Lean.Server.InfoUtilsopen Lean Meta Elab Commandpublic section
A copy of ContextInfo.runMetaM that keeps the messages and passes in the same filename.
def Lean.Elab.ContextInfo.runMetaMInCommandElabM {α} (info : ContextInfo) (lctx : LocalContext)
(x : MetaM α) : Command.CommandElabM α := do
let x := (withLocalInstances lctx.decls.toList.reduceOption x).run { lctx := lctx } { mctx := info.mctx }
/-
We must execute `x` using the `ngen` stored in `info`. Otherwise, we may create `MVarId`s and `FVarId`s that
have been used in `lctx` and `info.mctx`.
-/
let initHeartbeats ← IO.getNumHeartbeats
let ((a, _), st) ←
x.toIO {
options := info.options,
currNamespace := info.currNamespace,
openDecls := info.openDecls,
fileName := (← getFileName),
fileMap := info.fileMap,
initHeartbeats
}
{
env := info.env,
ngen := info.ngen,
infoState := ← getInfoState,
traceState := ← getTraceState
}
modify fun s =>
{ s with
messages := s.messages ++ st.messages,
infoState := st.infoState,
traceState := st.traceState
}
return anamespace Lean.Elab.Command.LinterA linter that applies to terms.
The lint argument receives both the original syntax and elaborated expression,
provided the option is enabled.
Note that the linter lives in StateT σ, so that it can build up state inherited from parent trees.
This doesn't return a Linter, as then the caller would have to remember to pass the name.
def runTermLinter {σ} [Inhabited σ] (opt : Lean.Option Bool)
(lint : Term → Expr → StateT σ MetaM Unit) : Command.CommandElab :=
withSetOptionIn fun cmdStx => do
let infoTrees := (← get).infoState.trees.toArray
if (← MonadState.get).messages.hasErrors then return
let some cmdStxRange := cmdStx.getRange? | return
for tree in infoTrees do
(tree.visitStateOf (σ := σ) (m := StateT σ CommandElabM)
fun ci info _ => do
-- the warning can be disabled locally
unless opt.get ci.options do return
let some range := info.range? | return
if !cmdStxRange.contains range.start then return
if let .ofTermInfo ti := info then
let s ← get
try
let (_, s) ← ci.runMetaMInCommandElabM ti.lctx do (lint ⟨ti.stx⟩ ti.expr).run s
set s
catch e =>
logErrorAt ti.stx m!"Thrown from the {opt.name} term linter:{indentD e.toMessageData}"
).run' default
-- Traces from linters are otherwise completely dropped
-- https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/trace.5Bdebug.5D.20in.20linters/near/485099940
let traces := (← getTraceState).traces.toArray
if !traces.isEmpty then
Lean.logInfo <|.trace { cls := opt.name } .nil (traces.map (·.msg))