imp: run checks in a well-defined order; and tweak that order

Now commodities are checked before accounts, and tags before recentassertions.
Also some check doc cleanups.
This commit is contained in:
Simon Michael 2024-04-26 17:30:05 -10:00
parent 4cbf72ab1f
commit 55401282a0
6 changed files with 65 additions and 61 deletions

View File

@ -8,6 +8,7 @@ others can be called only via the check command.
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
module Hledger.Data.JournalChecks ( module Hledger.Data.JournalChecks (
journalStrictChecks,
journalCheckAccounts, journalCheckAccounts,
journalCheckBalanceAssertions, journalCheckBalanceAssertions,
journalCheckCommodities, journalCheckCommodities,
@ -42,6 +43,15 @@ import Data.Ord
import Hledger.Data.Dates (showDate) import Hledger.Data.Dates (showDate)
import Hledger.Data.Balancing (journalBalanceTransactions, defbalancingopts) import Hledger.Data.Balancing (journalBalanceTransactions, defbalancingopts)
-- | Run the extra -s/--strict checks on a journal, in order of priority,
-- returning the first error message if any of them fail.
journalStrictChecks :: Journal -> Either String ()
journalStrictChecks j = do
-- keep the order of checks here synced with Check.md and Hledger.Cli.Commands.Check.Check.
-- journalCheckOrdereddates j
-- journalCheckBalanceAssertions j
journalCheckCommodities j
journalCheckAccounts j
-- | Check that all the journal's postings are to accounts with -- | Check that all the journal's postings are to accounts with
-- account directives, returning an error message otherwise. -- account directives, returning an error message otherwise.

View File

@ -107,7 +107,6 @@ module Hledger.Read (
orDieTrying, orDieTrying,
-- * Misc -- * Misc
journalStrictChecks,
saveLatestDates, saveLatestDates,
saveLatestDatesForFiles, saveLatestDatesForFiles,
@ -159,7 +158,7 @@ import Hledger.Read.RulesReader (tests_RulesReader)
-- import Hledger.Read.TimeclockReader (tests_TimeclockReader) -- import Hledger.Read.TimeclockReader (tests_TimeclockReader)
import Hledger.Utils import Hledger.Utils
import Prelude hiding (getContents, writeFile) import Prelude hiding (getContents, writeFile)
import Hledger.Data.JournalChecks (journalCheckAccounts, journalCheckCommodities) import Hledger.Data.JournalChecks (journalStrictChecks)
--- ** doctest setup --- ** doctest setup
-- $setup -- $setup
@ -307,13 +306,6 @@ readJournalFilesAndLatestDates iopts pfs = do
(js, lastdates) <- unzip <$> mapM (readJournalFileAndLatestDates iopts) pfs (js, lastdates) <- unzip <$> mapM (readJournalFileAndLatestDates iopts) pfs
return (maybe def sconcat $ nonEmpty js, catMaybes lastdates) return (maybe def sconcat $ nonEmpty js, catMaybes lastdates)
-- | Run the extra -s/--strict checks on a journal,
-- returning the first error message if any of them fail.
journalStrictChecks :: Journal -> Either String ()
journalStrictChecks j = do
journalCheckAccounts j
journalCheckCommodities j
-- | An easy version of 'readJournal' which assumes default options, and fails -- | An easy version of 'readJournal' which assumes default options, and fails
-- in the IO monad. -- in the IO monad.
readJournal' :: Text -> IO Journal readJournal' :: Text -> IO Journal

View File

@ -11,7 +11,7 @@ module Hledger.Cli.Commands.Check (
import Data.Char (toLower) import Data.Char (toLower)
import Data.Either (partitionEithers) import Data.Either (partitionEithers)
import Data.List (isPrefixOf, find) import Data.List (isPrefixOf, find, sort)
import Control.Monad (forM_) import Control.Monad (forM_)
import System.Console.CmdArgs.Explicit import System.Console.CmdArgs.Explicit
@ -36,7 +36,7 @@ check copts@CliOpts{rawopts_} j = do
case partitionEithers (map parseCheckArgument args) of case partitionEithers (map parseCheckArgument args) of
(unknowns@(_:_), _) -> error' $ "These checks are unknown: "++unwords unknowns (unknowns@(_:_), _) -> error' $ "These checks are unknown: "++unwords unknowns
([], checks) -> forM_ checks $ runCheck copts' j ([], checks) -> forM_ (sort checks) $ runCheck copts' j
-- | Regenerate this CliOpts' report specification, after updating its -- | Regenerate this CliOpts' report specification, after updating its
-- underlying report options with the given update function. -- underlying report options with the given update function.
@ -49,26 +49,25 @@ cliOptsUpdateReportSpecWith roptsupdate copts@CliOpts{reportspec_} =
Right rs -> copts{reportspec_=rs} Right rs -> copts{reportspec_=rs}
-- | A type of error check that we can perform on the data. -- | A type of error check that we can perform on the data.
-- Some of these imply other checks that are done first, -- If performing multiple checks, they will be performed in the order defined here, generally.
-- eg currently Parseable and Autobalanced are always done, -- (We report only the first failure, so the more useful checks should come first.)
-- and Assertions are always done unless -I is in effect.
data Check = data Check =
-- keep the order here synced with Check.md and Hledger.Data.JournalChecks.journalStrictChecks.
-- done always -- done always
Parseable Parseable
| Autobalanced | Autobalanced
-- done always unless -I is used | Assertions -- unless -I is used
| Assertions -- done when --strict is used, or when specified with the check command
-- done when -s is used, or on demand by check
| Accounts
| Commodities
| Balanced | Balanced
-- done on demand by check | Commodities
| Accounts
-- done when specified with the check command
| Ordereddates | Ordereddates
| Payees | Payees
| Recentassertions
| Tags | Tags
| Recentassertions
| Uniqueleafnames | Uniqueleafnames
deriving (Read,Show,Eq,Enum,Bounded) deriving (Read,Show,Eq,Enum,Bounded,Ord)
-- | Parse the name (or a name prefix) of an error check, or return the name unparsed. -- | Parse the name (or a name prefix) of an error check, or return the name unparsed.
-- Check names are conventionally all lower case, but this parses case insensitively. -- Check names are conventionally all lower case, but this parses case insensitively.
@ -97,6 +96,12 @@ runCheck _opts j (chck,_) = do
d <- getCurrentDay d <- getCurrentDay
let let
results = case chck of results = case chck of
-- these checks are assumed to have passed earlier during journal parsing:
Parseable -> Right ()
Autobalanced -> Right ()
Balanced -> Right ()
Assertions -> Right ()
Accounts -> journalCheckAccounts j Accounts -> journalCheckAccounts j
Commodities -> journalCheckCommodities j Commodities -> journalCheckCommodities j
Ordereddates -> journalCheckOrdereddates j Ordereddates -> journalCheckOrdereddates j
@ -104,8 +109,6 @@ runCheck _opts j (chck,_) = do
Recentassertions -> journalCheckRecentAssertions d j Recentassertions -> journalCheckRecentAssertions d j
Tags -> journalCheckTags j Tags -> journalCheckTags j
Uniqueleafnames -> journalCheckUniqueleafnames j Uniqueleafnames -> journalCheckUniqueleafnames j
-- the other checks have been done earlier during withJournalDo
_ -> Right ()
case results of case results of
Right () -> return () Right () -> return ()

View File

@ -4,7 +4,7 @@ Check for various kinds of errors in your data.
_FLAGS _FLAGS
hledger provides a number of built-in error checks to help hledger provides a number of built-in correctness checks to help
prevent problems in your data. prevent problems in your data.
Some of these are run automatically; or, Some of these are run automatically; or,
you can use this `check` command to run them on demand, you can use this `check` command to run them on demand,
@ -22,63 +22,64 @@ hledger check ordereddates payees # basic + two other checks
If you are an Emacs user, you can also configure flycheck-hledger to run these checks, If you are an Emacs user, you can also configure flycheck-hledger to run these checks,
providing instant feedback as you edit the journal. providing instant feedback as you edit the journal.
Here are the checks currently available: Here are the checks currently available.
They are performed in the order they are shown here.
(Eg, an `ordereddates` failure takes precedence over an `assertions` failure).
### Default checks ### Default checks
These checks are run automatically by (almost) all hledger commands: These checks are always performed, by (almost) all hledger commands:
- **parseable** - data files are in a supported [format](hledger.md#data-formats), - **parseable** - data files are in a supported [format](#data-formats),
with no syntax errors and no invalid include directives. with no syntax errors and no invalid include directives
- **autobalanced** - all transactions are [balanced](hledger.html#postings), after converting to cost. - **autobalanced** - all transactions are [balanced](#postings),
Missing amounts and missing [costs] are inferred automatically where possible. after inferring missing amounts and conversion [costs] where possible,
and then converting to cost
- **assertions** - all [balance assertions] in the journal are passing. - **assertions** - all [balance assertions] in the journal are passing.
(This check can be disabled with `-I`/`--ignore-assertions`.) (This check can be disabled with `-I`/`--ignore-assertions`.)
### Strict checks ### Strict checks
These additional checks are run when the `-s`/`--strict` ([strict mode]) flag is used. These additional checks are run when the `-s`/`--strict` ([strict mode])
Or, they can be run by giving their names as arguments to `check`: flag is used with any command; or,
when they are given as arguments to the `check` command:
- **balanced** - all transactions are balanced after converting to cost, - **balanced** - like `autobalanced`, but conversion costs will not be
without inferring missing costs. inferred, and must be written explicitly
If conversion costs are required, they must be explicit.
- **accounts** - all account names used by transactions
[have been declared](hledger.html#account-error-checking)
- **commodities** - all commodity symbols used - **commodities** - all commodity symbols used
[have been declared](hledger.html#commodity-error-checking) [have been declared](#commodity-error-checking)
- **accounts** - all account names used
[have been declared](#account-error-checking)
### Other checks ### Other checks
These checks can be run only by giving their names as arguments to `check`. These checks can be run by giving their names as arguments to `check`:
They are more specialised and not desirable for everyone:
- **ordereddates** - transactions are ordered by date within each file - **ordereddates** - within each file, transactions are ordered by date
- **payees** - all payees used by transactions [have been declared](#payee-directive) - **payees** - all payees used by transactions [have been declared](#payee-directive)
- **tags** - all tags used by transactions [have been declared](#tag-directive)
- **recentassertions** - all accounts with balance assertions have a - **recentassertions** - all accounts with balance assertions have a
balance assertion within 7 days of their latest posting balance assertion within 7 days of their latest posting
- **tags** - all tags used by transactions [have been declared](#tag-directive)
- **uniqueleafnames** - all account leaf names are unique - **uniqueleafnames** - all account leaf names are unique
### Custom checks ### Custom checks
A few more checks are are available as separate [add-on commands], You can build your own custom checks with [add-on command scripts].
in <https://github.com/simonmichael/hledger/tree/master/bin>: (See also [Cookbook > Scripting](scripting.html).)
Here are some examples from [hledger/bin/](https://github.com/simonmichael/hledger/tree/master/bin):
- **hledger-check-tagfiles** - all tag values containing / (a forward slash) exist as file paths - **hledger-check-tagfiles** - all tag values containing / (a forward slash) exist as file paths
- **hledger-check-fancyassertions** - more complex balance assertions are passing - **hledger-check-fancyassertions** - more complex balance assertions are passing
You could make similar scripts to perform your own custom checks.
See: Cookbook -> [Scripting](scripting.html).
### More about specific checks ### More about specific checks
@ -92,7 +93,7 @@ It assumes that adding a balance assertion requires/reminds you to check the rea
in that case, I recommend to import transactions uncleared, in that case, I recommend to import transactions uncleared,
and when you manually review and clear them, also check the latest assertion against the real-world balance.) and when you manually review and clear them, also check the latest assertion against the real-world balance.)
[add-on commands]: #add-on-commands [add-on command scripts]: #add-on-commands
[balance assertions]: #balance-assertions [balance assertions]: #balance-assertions
[strict mode]: #strict-mode [strict mode]: #strict-mode
[costs]: #costs [costs]: #costs

View File

@ -16,10 +16,12 @@ $ hledger -f- check accounts
# ** 3. also fails for forecast accounts # ** 3. also fails for forecast accounts
< <
commodity $
account a account a
~ 2022-01-31 ~ 2022-01-31
a $1 a $1
b b
$ hledger -f- --today 2022-01-01 --forecast check accounts $ hledger -f- --today 2022-01-01 --forecast check accounts
>2 /account "b" has not been declared/ >2 /account "b" has not been declared/
>=1 >=1
@ -31,6 +33,7 @@ $ hledger -f- --today 2022-01-01 --forecast --strict bal
# ** 5. also fails for auto accounts # ** 5. also fails for auto accounts
< <
commodity $
account a account a
= a = a

View File

@ -1,11 +1,12 @@
# check ordereddates succeeds when transaction dates are ordered # * check ordereddates
# ** 1. check ordereddates succeeds when transaction dates are ordered
< <
2020-01-01 2020-01-01
2020-01-01 2020-01-01
2020-01-02 2020-01-02
$ hledger -f- check ordereddates $ hledger -f- check ordereddates
# and otherwise fails # ** 2. and otherwise fails
< <
2020-01-01 2020-01-01
2020-01-02 2020-01-02
@ -15,18 +16,12 @@ $ hledger -f- check ordereddates
>2 /date .*is out of order/ >2 /date .*is out of order/
>=1 >=1
# With --date2, it checks secondary dates instead # ** 3. --date2 and secondary dates are ignored
< <
2020-01-02 2020-01-02
2020-01-01=2020-01-03 2020-01-01=2020-01-03
$ hledger -f- check ordereddates --date2 $ hledger -f- check ordereddates --date2
>2 /date .*is out of order/
#
<
2020-01-01=2020-01-03
2020-01-02
$ hledger -f- check ordereddates --date2
>2 /date2 .*is out of order/
>=1 >=1
# XXX not supported: With a query, only matched transactions' dates are checked. # XXX not supported: With a query, only matched transactions' dates are checked.