Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add external/public linter #144

Merged
merged 21 commits into from
Jul 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions cli/CLI.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,28 @@ import Prelude
data Opts
= Compile FilePath Locale
| Flatten FilePath
| Lint FilePath

getOpts :: IO Opts
getOpts = execParser (info (opts <**> helper) (progDesc h))
where h = "Compile ICU messages into code."

opts :: Parser Opts
opts = subparser
( command "compile" (info (compile <**> helper) mempty)
<> command "flatten" (info (flatten <**> helper) mempty)
)
opts = subparser . mconcat $
[ command "compile" (info (compile <**> helper) mempty)
, command "flatten" (info (flatten <**> helper) mempty)
, command "lint" (info (lint <**> helper) mempty)
]

compile :: Parser Opts
compile = Compile <$> pathp <*> localep

flatten :: Parser Opts
flatten = Flatten <$> pathp

lint :: Parser Opts
lint = Lint <$> pathp

pathp :: Parser FilePath
pathp = argument str (metavar "filepath")

Expand Down
11 changes: 10 additions & 1 deletion cli/Main.hs
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
module Main where

import CLI (Opts (..), getOpts)
import qualified Data.Map as M
import qualified Data.Text as T
import Intlc.Compiler (compileDataset, compileFlattened)
import Intlc.Core
import Intlc.Linter
import Intlc.Parser (parseDataset, printErr)
import Intlc.Parser.Error (ParseFailure)
import Prelude

main :: IO ()
main = getOpts >>= \case
Compile path loc -> tryCompile loc =<< getParsed path
Flatten path -> either parserDie (putTextLn . compileFlattened) =<< getParsed path
Flatten path -> either parserDie (putTextLn . compileFlattened) =<< getParsed path
Lint path -> either parserDie lint =<< getParsed path
where lint = exit . M.mapMaybe (statusToMaybe . lintExternal . message)
exit sts
| M.size sts > 0 = die . T.unpack . ("Errors\n" <>) . M.foldrWithKey mkLine mempty $ sts
| otherwise = pure ()
mkLine k es acc = acc <> "\n" <> k <> ": " <> e
where e = T.intercalate ", " . toList . fmap show $ es
where tryCompile l = either parserDie (either compilerDie putTextLn . compileDataset l)
parserDie = die . printErr
compilerDie = die . T.unpack . ("Invalid keys:\n" <>) . T.intercalate "\n" . fmap ("\t" <>) . toList
Expand Down
3 changes: 0 additions & 3 deletions internal/CLI.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Use newtype instead of data" #-}

module CLI (Opts (..), getOpts) where

import Options.Applicative
Expand Down
6 changes: 3 additions & 3 deletions internal/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ main = getOpts >>= \case
expandPlurals' = putTextLn . compileDataset . fmap (\x -> x { message = expandPlurals (message x) })

lint' :: Dataset Translation -> IO ()
lint' = exit . M.mapMaybe (statusToMaybe . lint . message)
lint' = exit . M.mapMaybe (statusToMaybe . lintInternal . message)

exit :: Dataset (NonEmpty LintingError) -> IO ()
exit :: Dataset (NonEmpty InternalLint) -> IO ()
exit sts
| M.size sts > 0 = die . T.unpack . ("Errors\n" <>) . M.foldrWithKey mkLine mempty $ sts
| otherwise = pure ()

mkLine :: Text -> NonEmpty LintingError -> Text -> Text
mkLine :: Text -> NonEmpty InternalLint -> Text -> Text
mkLine k es acc = acc <> "\n" <> k <> ": " <> e
where e = T.intercalate ", " . toList . fmap show $ es
1 change: 1 addition & 0 deletions intlc.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ common common
, optics ^>=0.4
, relude ^>=1.0
, text ^>=1.2
, these ^>=1.1
, validation ^>=1.1
mixins:
base hiding (Prelude)
Expand Down
9 changes: 5 additions & 4 deletions lib/Intlc/Backend/ICU/Compiler.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@

module Intlc.Backend.ICU.Compiler where

import Data.These (These (..), mergeTheseWith)
import Intlc.ICU
import Prelude hiding (Type)
import Prelude hiding (Type)

compileMsg :: Message -> Text
compileMsg (Message xs) = stream xs
Expand All @@ -30,7 +31,7 @@ interp n (Time fmt) = "{" <> n <> ", time, " <> dateT
interp n (Plural (Cardinal p)) = "{" <> n <> ", plural, " <> cardinalPlural p <> "}"
interp n (Plural (Ordinal p)) = "{" <> n <> ", selectordinal, " <> ordinalPlural p <> "}"
interp _ PluralRef = "#"
interp n (Select xs y) = "{" <> n <> ", select, " <> select xs y <> "}"
interp n (Select x) = "{" <> n <> ", select, " <> select x <> "}"
interp n (Callback xs) = "<" <> n <> ">" <> stream xs <> "</" <> n <> ">"

dateTimeFmt :: DateTimeFmt -> Text
Expand Down Expand Up @@ -64,7 +65,7 @@ pluralRule Many = "many"
pluralWildcard :: PluralWildcard -> Text
pluralWildcard (PluralWildcard xs) = "other {" <> stream xs <> "}"

select :: NonEmpty SelectCase -> Maybe SelectWildcard -> Text
select xs mw = unwords . toList $ (case' <$> toList xs) <> foldMap (pure . wild) mw
select :: These (NonEmpty SelectCase) SelectWildcard -> Text
select = unwords . mergeTheseWith (toList . fmap case') (pure . wild) (<>)
where case' (SelectCase n ys) = n <> " {" <> stream ys <> "}"
wild (SelectWildcard ys) = "other {" <> stream ys <> "}"
36 changes: 21 additions & 15 deletions lib/Intlc/Backend/JavaScript/Language.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module Intlc.Backend.JavaScript.Language where

import Data.These (These (..))
import Intlc.Core (Locale)
import qualified Intlc.ICU as ICU
import Prelude
Expand Down Expand Up @@ -33,7 +34,7 @@ data MatchCond

data MatchRet
= LitMatchRet (NonEmpty Branch)
| NonLitMatchRet (NonEmpty Branch) Wildcard
| NonLitMatchRet [Branch] Wildcard
| RecMatchRet (NonEmpty Branch) Match
deriving (Show, Eq)

Expand All @@ -60,32 +61,37 @@ fromInterp nraw t =
x <- fromBoolCase True trueCase
y <- fromBoolCase False falseCase
pure . TMatch . Match n LitCond . LitMatchRet $ x :| [y]
ICU.String -> pure $ TStr n
ICU.Number -> pure $ TNum n
ICU.Date x -> pure $ TDate n x
ICU.Time x -> pure $ TTime n x
ICU.Plural x -> TMatch <$> fromPlural n x
ICU.PluralRef -> pure $ TNum n
ICU.Select cs (Just w) -> ((TMatch . Match n LitCond) .) . NonLitMatchRet <$> (fromSelectCase `mapM` cs) <*> fromSelectWildcard w
ICU.Select cs Nothing -> TMatch . Match n LitCond . LitMatchRet <$> (fromSelectCase `mapM` cs)
ICU.Callback xs -> TApply n <$> (fromToken `mapM` xs)
ICU.String -> pure $ TStr n
ICU.Number -> pure $ TNum n
ICU.Date x -> pure $ TDate n x
ICU.Time x -> pure $ TTime n x
ICU.Plural x -> TMatch <$> fromPlural n x
ICU.PluralRef -> pure $ TNum n
ICU.Select x -> case x of
(This cs) -> TMatch . Match n LitCond . LitMatchRet <$> ret
where ret = fromSelectCase `mapM` cs
(That w) -> TMatch . Match n LitCond <$> ret
where ret = NonLitMatchRet mempty <$> fromSelectWildcard w
(These cs w) -> TMatch . Match n LitCond <$> ret
where ret = NonLitMatchRet <$> (toList <$> fromSelectCase `mapM` cs) <*> fromSelectWildcard w
ICU.Callback xs -> TApply n <$> (fromToken `mapM` xs)
where n = Ref nraw

fromPlural :: Ref -> ICU.Plural -> ASTCompiler Match
fromPlural r p = case p of
ICU.Cardinal (ICU.LitPlural lcs mw) -> Match r LitCond <$> case mw of
Nothing -> LitMatchRet <$> (fromExactPluralCase `mapM` lcs)
Just w -> NonLitMatchRet <$> (fromExactPluralCase `mapM` lcs) <*> fromPluralWildcard w
Just w -> NonLitMatchRet <$> (toList <$> fromExactPluralCase `mapM` lcs) <*> fromPluralWildcard w
ICU.Cardinal (ICU.RulePlural rcs w) -> Match r CardinalPluralRuleCond <$> m
where m = NonLitMatchRet <$> (fromRulePluralCase `mapM` rcs) <*> fromPluralWildcard w
where m = NonLitMatchRet <$> (toList <$> fromRulePluralCase `mapM` rcs) <*> fromPluralWildcard w
ICU.Cardinal (ICU.MixedPlural lcs rcs w) -> Match r LitCond <$> m
where m = RecMatchRet <$> (fromExactPluralCase `mapM` lcs) <*> (Match r CardinalPluralRuleCond <$> im)
im = NonLitMatchRet <$> (fromRulePluralCase `mapM` rcs) <*> fromPluralWildcard w
im = NonLitMatchRet <$> (toList <$> fromRulePluralCase `mapM` rcs) <*> fromPluralWildcard w
ICU.Ordinal (ICU.OrdinalPlural [] rcs w) -> Match r OrdinalPluralRuleCond <$> m
where m = NonLitMatchRet <$> (fromRulePluralCase `mapM` rcs) <*> fromPluralWildcard w
where m = NonLitMatchRet <$> (toList <$> fromRulePluralCase `mapM` rcs) <*> fromPluralWildcard w
ICU.Ordinal (ICU.OrdinalPlural (lc:lcs) rcs w) -> Match r LitCond <$> m
where m = RecMatchRet <$> ((:|) <$> fromExactPluralCase lc <*> (fromExactPluralCase `mapM` lcs)) <*> im
im = Match r OrdinalPluralRuleCond <$> (NonLitMatchRet <$> (fromRulePluralCase `mapM` rcs) <*> fromPluralWildcard w)
im = Match r OrdinalPluralRuleCond <$> (NonLitMatchRet <$> (toList <$> fromRulePluralCase `mapM` rcs) <*> fromPluralWildcard w)

fromExactPluralCase :: ICU.PluralCase ICU.PluralExact -> ASTCompiler Branch
fromExactPluralCase (ICU.PluralCase (ICU.PluralExact n) xs) = Branch n <$> (fromToken `mapM` xs)
Expand Down
27 changes: 14 additions & 13 deletions lib/Intlc/Backend/TypeScript/Language.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module Intlc.Backend.TypeScript.Language where

import Data.List.NonEmpty (nub)
import qualified Data.Map as M
import Data.These (These (..))
import qualified Intlc.ICU as ICU
import Prelude

Expand Down Expand Up @@ -48,21 +49,21 @@ fromToken ICU.Plaintext {} = mempty
fromToken (ICU.Interpolation x y) = fromInterp x y

fromInterp :: Text -> ICU.Type -> UncollatedArgs
fromInterp n (ICU.Bool xs ys) = (n, TBool) : (fromToken =<< xs) <> (fromToken =<< ys)
fromInterp n ICU.String = pure (n, TStr)
fromInterp n ICU.Number = pure (n, TNum)
fromInterp n ICU.Date {} = pure (n, TDate)
fromInterp n ICU.Time {} = pure (n, TDate)
fromInterp n (ICU.Plural x) = fromPlural n x
fromInterp n (ICU.Bool xs ys) = (n, TBool) : (fromToken =<< xs) <> (fromToken =<< ys)
fromInterp n ICU.String = pure (n, TStr)
fromInterp n ICU.Number = pure (n, TNum)
fromInterp n ICU.Date {} = pure (n, TDate)
fromInterp n ICU.Time {} = pure (n, TDate)
fromInterp n (ICU.Plural x) = fromPlural n x
-- Plural references are treated as a no-op.
fromInterp _ ICU.PluralRef = mempty
fromInterp n (ICU.Select cs mw) = (n, t) : (fromSelectCase =<< toList cs) <> foldMap fromSelectWildcard mw
fromInterp _ ICU.PluralRef = mempty
fromInterp n (ICU.Select x) = case x of
(That w) -> (n, TStr) : fromSelectWildcard w
(These cs w) -> (n, TStr) : (fromSelectCase =<< toList cs) <> fromSelectWildcard w
-- When there's no wildcard case we can compile to a union of string literals.
where t = case mw of
Just _ -> TStr
Nothing -> TStrLitUnion $ caseLit <$> cs
caseLit (ICU.SelectCase x _) = x
fromInterp n (ICU.Callback xs) = (n, TEndo) : (fromToken =<< xs)
(This cs) -> (n, TStrLitUnion (lit <$> cs)) : (fromSelectCase =<< toList cs)
where lit (ICU.SelectCase l _) = l
fromInterp n (ICU.Callback xs) = (n, TEndo) : (fromToken =<< xs)

fromPlural :: Text -> ICU.Plural -> UncollatedArgs
fromPlural n (ICU.Cardinal (ICU.LitPlural ls mw)) = (n, t) : (fromExactPluralCase =<< toList ls) <> foldMap fromPluralWildcard mw
Expand Down
23 changes: 12 additions & 11 deletions lib/Intlc/Compiler.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Data.Foldable (elem)
import Data.List.Extra (firstJust, unionBy)
import qualified Data.Map as M
import qualified Data.Text as T
import Data.These (These (..))
import Intlc.Backend.JavaScript.Compiler as JS
import qualified Intlc.Backend.JSON.Compiler as JSON
import qualified Intlc.Backend.TypeScript.Compiler as TS
Expand Down Expand Up @@ -37,7 +38,7 @@ compileTranslation l k (Translation v be _) = case be of
TypeScriptReact -> TS.compileNamedExport JSX l k v

type ICUBool = (ICU.Stream, ICU.Stream)
type ICUSelect = (NonEmpty ICU.SelectCase, Maybe ICU.SelectWildcard)
type ICUSelect = These (NonEmpty ICU.SelectCase) ICU.SelectWildcard

compileFlattened :: Dataset Translation -> Text
compileFlattened = JSON.compileDataset . mapMsgs flatten
Expand All @@ -52,11 +53,11 @@ mapTokens :: (ICU.Token -> ICU.Token) -> ICU.Stream -> ICU.Stream
mapTokens f = fmap $ f >>> \case
x@(ICU.Plaintext {}) -> x
x@(ICU.Interpolation n t) -> case t of
ICU.Bool xs ys -> g $ ICU.Bool (h xs) (h ys)
ICU.Plural y -> g . ICU.Plural $ mapPluralStreams h y
ICU.Select ys mz -> g . uncurry ICU.Select $ mapSelectStreams h (ys, mz)
ICU.Callback ys -> g . ICU.Callback . h $ ys
_ -> x
ICU.Bool xs ys -> g $ ICU.Bool (h xs) (h ys)
ICU.Plural y -> g . ICU.Plural $ mapPluralStreams h y
ICU.Select y -> g . ICU.Select $ mapSelectStreams h y
ICU.Callback ys -> g . ICU.Callback . h $ ys
_ -> x
where g = ICU.Interpolation n
h = fmap f

Expand All @@ -69,8 +70,8 @@ flatten (ICU.Message xs) = ICU.Message . flattenStream $ xs
, mapPlural <$> extractFirstPlural ys
]
mapBool (n, ls, boo, rs) = streamFromArg n . uncurry ICU.Bool $ mapBoolStreams (around ls rs) boo
mapSelect (n, ls, sel, rs) = streamFromArg n . uncurry ICU.Select $ mapSelectStreams (around ls rs) sel
mapPlural (n, ls, plu, rs) = streamFromArg n . ICU.Plural $ mapPluralStreams (around ls rs) plu
mapSelect (n, ls, sel, rs) = streamFromArg n . ICU.Select $ mapSelectStreams (around ls rs) sel
mapPlural (n, ls, plu, rs) = streamFromArg n . ICU.Plural $ mapPluralStreams (around ls rs) plu
around ls rs = flattenStream . ICU.mergePlaintext . surround ls rs
surround ls rs cs = ls <> cs <> rs
streamFromArg n = pure . ICU.Interpolation n
Expand Down Expand Up @@ -119,8 +120,8 @@ extractFirstArg f xs = firstJust arg (zip [0..] xs)

extractFirstSelect :: ICU.Stream -> Maybe (Text, ICU.Stream, ICUSelect, ICU.Stream)
extractFirstSelect = extractFirstArg $ \case
ICU.Select xs y -> Just (xs, y)
_ -> Nothing
ICU.Select x -> Just x
_ -> Nothing

extractFirstPlural :: ICU.Stream -> Maybe (Text, ICU.Stream, ICU.Plural, ICU.Stream)
extractFirstPlural = extractFirstArg $ \case
Expand All @@ -131,7 +132,7 @@ mapBoolStreams :: (ICU.Stream -> ICU.Stream) -> ICUBool -> ICUBool
mapBoolStreams f (xs, ys) = (f xs, f ys)

mapSelectStreams :: (ICU.Stream -> ICU.Stream) -> ICUSelect -> ICUSelect
mapSelectStreams f (xs, mw) = (mapSelectCase f <$> xs, mapSelectWildcard f <$> mw)
mapSelectStreams f = bimap (fmap (mapSelectCase f)) (mapSelectWildcard f)

mapSelectCase :: (ICU.Stream -> ICU.Stream) -> ICU.SelectCase -> ICU.SelectCase
mapSelectCase f (ICU.SelectCase x ys) = ICU.SelectCase x (f ys)
Expand Down
13 changes: 6 additions & 7 deletions lib/Intlc/ICU.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

module Intlc.ICU where

import Prelude hiding (Type)
import Data.These (These (..), mergeTheseWith)
import Prelude hiding (Type)

newtype Message = Message Stream
deriving (Show, Eq)
Expand Down Expand Up @@ -39,11 +40,9 @@ getStream (Interpolation _ t) = case t of
PluralRef -> Nothing
Bool {trueCase, falseCase} -> Just $ trueCase <> falseCase
Plural x -> Just $ getPluralStream x
Select cs mw -> Just $ ss <> ws
where ss = (\(SelectCase _ xs) -> xs) `concatMap` cs
ws = case mw of
Nothing -> []
Just (SelectWildcard xs) -> xs
Select x -> Just . mergeTheseWith (concatMap f) g (<>) $ x
where f (SelectCase _ xs) = xs
g (SelectWildcard w) = w
Callback xs -> Just xs

getPluralStream :: Plural -> Stream
Expand Down Expand Up @@ -90,7 +89,7 @@ data Type
-- Plural hash references have their own distinct type rather than merely
-- taking on `Number` to allow compilers to infer appropriately.
| PluralRef
| Select (NonEmpty SelectCase) (Maybe SelectWildcard)
| Select (These (NonEmpty SelectCase) SelectWildcard)
| Callback Stream
deriving (Show, Eq)

Expand Down
Loading