diff --git a/hledger-lib/Hledger/Data/Journal.hs b/hledger-lib/Hledger/Data/Journal.hs index a3672ec8f..afe9838d7 100644 --- a/hledger-lib/Hledger/Data/Journal.hs +++ b/hledger-lib/Hledger/Data/Journal.hs @@ -52,6 +52,7 @@ module Hledger.Data.Journal ( -- overJournalAmounts, -- traverseJournalAmounts, -- journalCanonicalCommodities, + journalCommoditiesDeclared, journalDateSpan, journalStartDate, journalEndDate, @@ -268,6 +269,10 @@ journalDescriptions = nubSort . map tdescription . jtxns journalPostings :: Journal -> [Posting] journalPostings = concatMap tpostings . jtxns +-- | Sorted unique commodity symbols declared by commodity directives in this journal. +journalCommoditiesDeclared :: Journal -> [AccountName] +journalCommoditiesDeclared = nubSort . M.keys . jcommodities + -- | Sorted unique account names posted to by this journal's transactions. journalAccountNamesUsed :: Journal -> [AccountName] journalAccountNamesUsed = accountNamesFromPostings . journalPostings diff --git a/hledger-lib/Hledger/Read/Common.hs b/hledger-lib/Hledger/Read/Common.hs index 14abdcd3a..e3f7ed939 100644 --- a/hledger-lib/Hledger/Read/Common.hs +++ b/hledger-lib/Hledger/Read/Common.hs @@ -148,6 +148,7 @@ import Text.Megaparsec.Custom import Hledger.Data import Hledger.Utils +import Safe (headMay) --- ** doctest setup -- $setup @@ -334,33 +335,38 @@ journalFinalise InputOpts{auto_,ignore_assertions_,commoditystyles_,strict_} f t Left e -> throwError e Right () -> - -- Infer and apply canonical styles for each commodity (or throw an error). - -- This affects transaction balancing/assertions/assignments, so needs to be done early. - case journalApplyCommodityStyles pj' of - Left e -> throwError e - Right pj'' -> either throwError return $ - pj'' - & (if not auto_ || null (jtxnmodifiers pj'') - then - -- Auto postings are not active. - -- Balance all transactions and maybe check balance assertions. - journalBalanceTransactions (not ignore_assertions_) - else \j -> do -- Either monad - -- Auto postings are active. - -- Balance all transactions without checking balance assertions, - j' <- journalBalanceTransactions False j - -- then add the auto postings - -- (Note adding auto postings after balancing means #893b fails; - -- adding them before balancing probably means #893a, #928, #938 fail.) - case journalModifyTransactions d j' of - Left e -> throwError e - Right j'' -> do - -- then apply commodity styles once more, to style the auto posting amounts. (XXX inefficient ?) - j''' <- journalApplyCommodityStyles j'' - -- then check balance assertions. - journalBalanceTransactions (not ignore_assertions_) j''' - ) - & fmap journalInferMarketPricesFromTransactions -- infer market prices from commodity-exchanging transactions + -- and using declared commodities + case if strict_ then journalCheckCommoditiesDeclared pj' else Right () of + Left e -> throwError e + Right () -> + + -- Infer and apply canonical styles for each commodity (or throw an error). + -- This affects transaction balancing/assertions/assignments, so needs to be done early. + case journalApplyCommodityStyles pj' of + Left e -> throwError e + Right pj'' -> either throwError return $ + pj'' + & (if not auto_ || null (jtxnmodifiers pj'') + then + -- Auto postings are not active. + -- Balance all transactions and maybe check balance assertions. + journalBalanceTransactions (not ignore_assertions_) + else \j -> do -- Either monad + -- Auto postings are active. + -- Balance all transactions without checking balance assertions, + j' <- journalBalanceTransactions False j + -- then add the auto postings + -- (Note adding auto postings after balancing means #893b fails; + -- adding them before balancing probably means #893a, #928, #938 fail.) + case journalModifyTransactions d j' of + Left e -> throwError e + Right j'' -> do + -- then apply commodity styles once more, to style the auto posting amounts. (XXX inefficient ?) + j''' <- journalApplyCommodityStyles j'' + -- then check balance assertions. + journalBalanceTransactions (not ignore_assertions_) j''' + ) + & fmap journalInferMarketPricesFromTransactions -- infer market prices from commodity-exchanging transactions -- | Check that all the journal's postings are to accounts declared with -- account directives, returning an error message otherwise. @@ -370,13 +376,34 @@ journalCheckAccountsDeclared j = sequence_ $ map checkacct $ journalPostings j checkacct Posting{paccount,ptransaction} | paccount `elem` as = Right () | otherwise = - Left $ "\nstrict mode: undeclared account \""++T.unpack paccount++"\" is posted to" + Left $ "\nstrict mode: undeclared account \""++T.unpack paccount++"\"" ++ case ptransaction of - Just Transaction{tsourcepos} -> "\n at: "++showGenericSourcePos tsourcepos + Just Transaction{tsourcepos} -> "\nin transaction at: "++showGenericSourcePos tsourcepos Nothing -> "" where as = journalAccountNamesDeclared j +-- | Check that all the commodities used in this journal's postings have been declared +-- by commodity directives, returning an error message otherwise. +journalCheckCommoditiesDeclared :: Journal -> Either String () +journalCheckCommoditiesDeclared j = + sequence_ $ map checkcommodities $ journalPostings j + where + checkcommodities Posting{..} = + case mfirstundeclaredcomm of + Nothing -> Right () + Just c -> Left $ + "\nstrict mode: undeclared commodity \""++T.unpack c++"\"" + ++ case ptransaction of + Just Transaction{tsourcepos} -> "\nin transaction at: "++showGenericSourcePos tsourcepos + Nothing -> "" + where + mfirstundeclaredcomm = + headMay $ filter (not . (`elem` cs)) $ catMaybes $ + (acommodity . baamount <$> pbalanceassertion) : + (map (Just . acommodity) $ amounts pamount) + cs = journalCommoditiesDeclared j + setYear :: Year -> JournalParser m () setYear y = modify' (\j -> j{jparsedefaultyear=Just y}) diff --git a/hledger-lib/hledger_journal.m4.md b/hledger-lib/hledger_journal.m4.md index 3e906a476..d0d98042e 100644 --- a/hledger-lib/hledger_journal.m4.md +++ b/hledger-lib/hledger_journal.m4.md @@ -992,6 +992,11 @@ Note hledger normally uses so 0.5 displayed with zero decimal digits is "0". (More at [Commodity display style](#commodity-display-style).) +#### Commodity error checking + +In [strict mode], enabled with the `-s`/`--strict` flag, hledger will report an error if a +commodity symbol is used that has not been declared by a [`commodity` directive](#declaring-commodities). This works similarly to [account error checking](#account-error-checking), see the notes there for more details. + ### Default commodity The `D` directive sets a default commodity, to be used for amounts without a commodity symbol (ie, plain numbers). @@ -1070,16 +1075,14 @@ The simplest form is just the word `account` followed by a hledger-style account assets:bank:checking ``` -#### Account existence +#### Account error checking -By default, accounts come into existence when a transaction references them. -This is convenient, but when you mis-spell an account name in a transaction, -hledger won't be able to detect it. Usually this isn't a big problem, as you'll -notice the error in balance reports, or when reconciling account balances. +By default, accounts come into existence when a transaction references them by name. +This is convenient, but it means hledger can't warn you when you mis-spell an account name in the journal. +Usually you'll find the error later, as an extra account in balance reports, +or an incorrect balance when reconciling. -When you want more error checking, you can enable [strict mode] with the `-s`/`--strict` flag. Then hledger will will report an error if any transaction references -an account that has not been declared by an [account directive](#declaring-accounts). -Some things to note: +In [strict mode], enabled with the `-s`/`--strict` flag, hledger will report an error if any transaction uses an account name that has not been declared by an [account directive](#declaring-accounts). Some notes: - The declaration is case-sensitive; transactions must use the correct account name capitalisation. - The account directive's scope is "whole file and below" (see [directives](#directives)). This means it affects all of the current file, and any files it includes, but not parent or sibling files. The position of account directives within the file does not matter, though it's usual to put them at the top. diff --git a/hledger/hledger.m4.md b/hledger/hledger.m4.md index 457f3273c..808cc0997 100644 --- a/hledger/hledger.m4.md +++ b/hledger/hledger.m4.md @@ -773,8 +773,10 @@ easy journal files without a lot of declarations: With the `-s`/`--strict` flag, additional checks are performed: -- Are all accounts referenced by transactions declared with an account directive ? - ([Account existence](journal.html#account-existence)) +- Are all accounts posted to, declared with an `account` directive ? + ([Account error checking](journal.html#account-error-checking)) +- Are all commodities declared with a `commodity` directive ? + ([Commodity error checking](journal.html#commodity-error-checking)) See also: