-- | This module defines functions which are useful for determining if
-- a given name is a legal JavaScript name according to
-- <https://stackoverflow.com/questions/1661197/what-characters-are-valid-for-javascript-variable-names this post>.
module Data.Aeson.TypeScript.LegalName where

import Data.Char
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Set as Set


-- | The return type is the illegal characters that are in the name. If the
-- input has no illegal characters, then you have 'Nothing'.
checkIllegalNameChars :: NonEmpty Char -> Maybe (NonEmpty Char)
checkIllegalNameChars :: NonEmpty Char -> Maybe (NonEmpty Char)
checkIllegalNameChars (Char
firstChar :| [Char]
restChars) = [Char] -> Maybe (NonEmpty Char)
forall a. [a] -> Maybe (NonEmpty a)
NonEmpty.nonEmpty ([Char] -> Maybe (NonEmpty Char))
-> [Char] -> Maybe (NonEmpty Char)
forall a b. (a -> b) -> a -> b
$
  let
    legalFirstCategories :: Set GeneralCategory
legalFirstCategories =
      [GeneralCategory] -> Set GeneralCategory
forall a. Ord a => [a] -> Set a
Set.fromList
        [ GeneralCategory
UppercaseLetter
        , GeneralCategory
LowercaseLetter
        , GeneralCategory
TitlecaseLetter
        , GeneralCategory
ModifierLetter
        , GeneralCategory
OtherLetter
        , GeneralCategory
LetterNumber
        ]
    legalRestCategories :: Set GeneralCategory
legalRestCategories =
      [GeneralCategory] -> Set GeneralCategory
forall a. Ord a => [a] -> Set a
Set.fromList
        [ GeneralCategory
NonSpacingMark
        , GeneralCategory
SpacingCombiningMark
        , GeneralCategory
DecimalNumber
        , GeneralCategory
ConnectorPunctuation
        ]
        Set GeneralCategory -> Set GeneralCategory -> Set GeneralCategory
forall a. Ord a => Set a -> Set a -> Set a
`Set.union` Set GeneralCategory
legalFirstCategories
    isIllegalFirstChar :: Char -> Bool
isIllegalFirstChar Char
c = Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$
      Char
c Char -> [Char] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Char
'$', Char
'_'] Bool -> Bool -> Bool
|| Char -> GeneralCategory
generalCategory Char
c GeneralCategory -> Set GeneralCategory -> Bool
forall a. Ord a => a -> Set a -> Bool
`Set.member` Set GeneralCategory
legalFirstCategories
    isIllegalRestChar :: Char -> Bool
isIllegalRestChar Char
c = Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$
      Char -> GeneralCategory
generalCategory Char
c GeneralCategory -> Set GeneralCategory -> Bool
forall a. Ord a => a -> Set a -> Bool
`Set.member` Set GeneralCategory
legalRestCategories
  in
    (Char -> Bool) -> [Char] -> [Char]
forall a. (a -> Bool) -> [a] -> [a]
filter Char -> Bool
isIllegalFirstChar [Char
firstChar] [Char] -> [Char] -> [Char]
forall a. Semigroup a => a -> a -> a
<> (Char -> Bool) -> [Char] -> [Char]
forall a. (a -> Bool) -> [a] -> [a]
filter Char -> Bool
isIllegalRestChar [Char]
restChars