From b494b3926682622a28b3c5e38d579df20286104f Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 13 Jan 2024 18:57:53 +0800 Subject: [PATCH 01/28] remove unsafe coerce to use type class based method --- hls-plugin-api/src/Ide/Plugin/Properties.hs | 63 +++++++++++---------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/hls-plugin-api/src/Ide/Plugin/Properties.hs b/hls-plugin-api/src/Ide/Plugin/Properties.hs index 9baaf26833..a9b679d413 100644 --- a/hls-plugin-api/src/Ide/Plugin/Properties.hs +++ b/hls-plugin-api/src/Ide/Plugin/Properties.hs @@ -44,13 +44,11 @@ import qualified Data.Aeson.Types as A import Data.Either (fromRight) import Data.Function ((&)) import Data.Kind (Constraint, Type) -import qualified Data.Map.Strict as Map import Data.Proxy (Proxy (..)) import Data.String (IsString (fromString)) import qualified Data.Text as T import GHC.OverloadedLabels (IsLabel (..)) import GHC.TypeLits -import Unsafe.Coerce (unsafeCoerce) -- | Types properties may have data PropertyType @@ -114,7 +112,11 @@ data SomePropertyKeyWithMetaData -- A property is an immediate child of the json object in each plugin's "config" section. -- It was designed to be compatible with vscode's settings UI. -- Use 'emptyProperties' and 'useProperty' to create and consume 'Properties'. -newtype Properties (r :: [PropertyKey]) = Properties (Map.Map String SomePropertyKeyWithMetaData) +-- newtype Properties (r :: [PropertyKey]) = Properties (Map.Map String SomePropertyKeyWithMetaData) +data Properties (r :: [PropertyKey]) where + ConsProperties :: (k ~ 'PropertyKey s t, KnownSymbol s, NotElem s ks) + => KeyNameProxy s -> (SPropertyKey k) -> (MetaData t) -> Properties ks -> Properties (k : ks) + EmptyProperties :: Properties '[] -- | A proxy type in order to allow overloaded labels as properties' names at the call site data KeyNameProxy (s :: Symbol) = KnownSymbol s => KeyNameProxy @@ -132,10 +134,9 @@ type family FindByKeyName (s :: Symbol) (r :: [PropertyKey]) :: PropertyType whe FindByKeyName s ('PropertyKey s t ': _) = t FindByKeyName s (_ ': xs) = FindByKeyName s xs -type family Elem (s :: Symbol) (r :: [PropertyKey]) :: Constraint where - Elem s ('PropertyKey s _ ': _) = () - Elem s (_ ': xs) = Elem s xs - Elem s '[] = TypeError ('Text "The key ‘" ':<>: 'Text s ':<>: 'Text "’ is missing") +type family IsPropertySymbol (s :: Symbol) (r :: PropertyKey) :: Bool where + IsPropertySymbol s ('PropertyKey s _) = 'True + IsPropertySymbol s _ = 'False type family NotElem (s :: Symbol) (r :: [PropertyKey]) :: Constraint where NotElem s ('PropertyKey s _ ': _) = TypeError ('Text "The key ‘" ':<>: 'Text s ':<>: 'Text "’ is already defined") @@ -143,7 +144,20 @@ type family NotElem (s :: Symbol) (r :: [PropertyKey]) :: Constraint where NotElem s '[] = () -- | In row @r@, there is a 'PropertyKey' @k@, which has name @s@ and carries haskell type @t@ -type HasProperty s k t r = (k ~ 'PropertyKey s t, Elem s r, FindByKeyName s r ~ t, KnownSymbol s) +type HasProperty s k t r = (k ~ 'PropertyKey s t, FindByKeyName s r ~ t, KnownSymbol s, FindPropertyMeta s r t) +class FindPropertyMeta (s :: Symbol) (r :: [PropertyKey]) t where + findSomePropertyKeyWithMetaData :: KeyNameProxy s -> Properties r -> (SPropertyKey ('PropertyKey s t), MetaData t) +instance + (FindPropertyMetaIf (IsPropertySymbol symbol k) symbol k ks t) => + FindPropertyMeta symbol (k : ks) t + where + findSomePropertyKeyWithMetaData = findSomePropertyKeyWithMetaDataIf +class (bool ~ IsPropertySymbol symbol k) => FindPropertyMetaIf bool symbol k ks t where + findSomePropertyKeyWithMetaDataIf :: KeyNameProxy symbol -> Properties (k : ks) -> (SPropertyKey ('PropertyKey symbol t), MetaData t) +instance (k ~ 'PropertyKey s t) => FindPropertyMetaIf 'True s k ks t where + findSomePropertyKeyWithMetaDataIf _ (ConsProperties _ k m _) = (k, m) +instance ('False ~ IsPropertySymbol s k, FindPropertyMeta s ks t) => FindPropertyMetaIf 'False s k ks t where + findSomePropertyKeyWithMetaDataIf s (ConsProperties _ _ _ ks) = findSomePropertyKeyWithMetaData s ks -- --------------------------------------------------------------------- @@ -164,7 +178,7 @@ type HasProperty s k t r = (k ~ 'PropertyKey s t, Elem s r, FindByKeyName s r ~ -- @ emptyProperties :: Properties '[] -emptyProperties = Properties Map.empty +emptyProperties = EmptyProperties insert :: (k ~ 'PropertyKey s t, NotElem s r, KnownSymbol s) => @@ -173,30 +187,14 @@ insert :: MetaData t -> Properties r -> Properties (k ': r) -insert kn key metadata (Properties old) = - Properties - ( Map.insert - (symbolVal kn) - (SomePropertyKeyWithMetaData key metadata) - old - ) +insert = ConsProperties find :: (HasProperty s k t r) => KeyNameProxy s -> Properties r -> (SPropertyKey k, MetaData t) -find kn (Properties p) = case p Map.! symbolVal kn of - (SomePropertyKeyWithMetaData sing metadata) -> - -- Note [Constraints] - -- It's safe to use unsafeCoerce here: - -- Since each property name is unique that the redefinition will be prevented by predication on the type level list, - -- the value we get from the name-indexed map must be exactly the singleton and metadata corresponding to the type. - -- We drop this information at type level: some of the above type families return '() :: Constraint', - -- so GHC will consider them as redundant. - -- But we encode it using semantically identical 'Map' at term level, - -- which avoids inducting on the list by defining a new type class. - unsafeCoerce (sing, metadata) +find = findSomePropertyKeyWithMetaData -- --------------------------------------------------------------------- @@ -350,7 +348,10 @@ defineEnumProperty kn description enums defaultValue = -- | Converts a properties definition into kv pairs with default values from 'MetaData' toDefaultJSON :: Properties r -> [A.Pair] -toDefaultJSON (Properties p) = [toEntry s v | (s, v) <- Map.toList p] +toDefaultJSON pr = case pr of + EmptyProperties -> [] + ConsProperties keyNameProxy k m xs -> + toEntry (symbolVal keyNameProxy) (SomePropertyKeyWithMetaData k m) : toDefaultJSON xs where toEntry :: String -> SomePropertyKeyWithMetaData -> A.Pair toEntry s = \case @@ -371,8 +372,10 @@ toDefaultJSON (Properties p) = [toEntry s v | (s, v) <- Map.toList p] -- | Converts a properties definition into kv pairs as vscode schema toVSCodeExtensionSchema :: T.Text -> Properties r -> [A.Pair] -toVSCodeExtensionSchema prefix (Properties p) = - [fromString (T.unpack prefix <> k) A..= toEntry v | (k, v) <- Map.toList p] +toVSCodeExtensionSchema prefix ps = case ps of + EmptyProperties -> [] + ConsProperties (keyNameProxy :: KeyNameProxy s) (k :: SPropertyKey k) (m :: MetaData t) xs -> + fromString (T.unpack prefix <> symbolVal keyNameProxy) A..= toEntry (SomePropertyKeyWithMetaData k m) : toVSCodeExtensionSchema prefix xs where toEntry :: SomePropertyKeyWithMetaData -> A.Value toEntry = \case From 18a83673653a0e7924226ab23c1eec355f023c20 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 13 Jan 2024 19:04:26 +0800 Subject: [PATCH 02/28] revert Elem to have better error display --- hls-plugin-api/src/Ide/Plugin/Properties.hs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/hls-plugin-api/src/Ide/Plugin/Properties.hs b/hls-plugin-api/src/Ide/Plugin/Properties.hs index a9b679d413..19581ffe48 100644 --- a/hls-plugin-api/src/Ide/Plugin/Properties.hs +++ b/hls-plugin-api/src/Ide/Plugin/Properties.hs @@ -138,13 +138,18 @@ type family IsPropertySymbol (s :: Symbol) (r :: PropertyKey) :: Bool where IsPropertySymbol s ('PropertyKey s _) = 'True IsPropertySymbol s _ = 'False +type family Elem (s :: Symbol) (r :: [PropertyKey]) :: Constraint where + Elem s ('PropertyKey s _ ': _) = () + Elem s (_ ': xs) = Elem s xs + Elem s '[] = TypeError ('Text "The key ‘" ':<>: 'Text s ':<>: 'Text "’ is missing") + type family NotElem (s :: Symbol) (r :: [PropertyKey]) :: Constraint where NotElem s ('PropertyKey s _ ': _) = TypeError ('Text "The key ‘" ':<>: 'Text s ':<>: 'Text "’ is already defined") NotElem s (_ ': xs) = NotElem s xs NotElem s '[] = () -- | In row @r@, there is a 'PropertyKey' @k@, which has name @s@ and carries haskell type @t@ -type HasProperty s k t r = (k ~ 'PropertyKey s t, FindByKeyName s r ~ t, KnownSymbol s, FindPropertyMeta s r t) +type HasProperty s k t r = (k ~ 'PropertyKey s t, Elem s r, FindByKeyName s r ~ t, KnownSymbol s, FindPropertyMeta s r t) class FindPropertyMeta (s :: Symbol) (r :: [PropertyKey]) t where findSomePropertyKeyWithMetaData :: KeyNameProxy s -> Properties r -> (SPropertyKey ('PropertyKey s t), MetaData t) instance From fd05577bb062ab41c552b23b8e1a4071d7a0687a Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 13 Jan 2024 19:06:51 +0800 Subject: [PATCH 03/28] clean up --- hls-plugin-api/src/Ide/Plugin/Properties.hs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/hls-plugin-api/src/Ide/Plugin/Properties.hs b/hls-plugin-api/src/Ide/Plugin/Properties.hs index 19581ffe48..fb0ad5655e 100644 --- a/hls-plugin-api/src/Ide/Plugin/Properties.hs +++ b/hls-plugin-api/src/Ide/Plugin/Properties.hs @@ -152,10 +152,7 @@ type family NotElem (s :: Symbol) (r :: [PropertyKey]) :: Constraint where type HasProperty s k t r = (k ~ 'PropertyKey s t, Elem s r, FindByKeyName s r ~ t, KnownSymbol s, FindPropertyMeta s r t) class FindPropertyMeta (s :: Symbol) (r :: [PropertyKey]) t where findSomePropertyKeyWithMetaData :: KeyNameProxy s -> Properties r -> (SPropertyKey ('PropertyKey s t), MetaData t) -instance - (FindPropertyMetaIf (IsPropertySymbol symbol k) symbol k ks t) => - FindPropertyMeta symbol (k : ks) t - where +instance (FindPropertyMetaIf (IsPropertySymbol symbol k) symbol k ks t) => FindPropertyMeta symbol (k : ks) t where findSomePropertyKeyWithMetaData = findSomePropertyKeyWithMetaDataIf class (bool ~ IsPropertySymbol symbol k) => FindPropertyMetaIf bool symbol k ks t where findSomePropertyKeyWithMetaDataIf :: KeyNameProxy symbol -> Properties (k : ks) -> (SPropertyKey ('PropertyKey symbol t), MetaData t) From d09898e828b477107aff747a986d1972b703c5ce Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 13 Jan 2024 19:07:28 +0800 Subject: [PATCH 04/28] clean up --- hls-plugin-api/src/Ide/Plugin/Properties.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/hls-plugin-api/src/Ide/Plugin/Properties.hs b/hls-plugin-api/src/Ide/Plugin/Properties.hs index fb0ad5655e..ec94a6eedf 100644 --- a/hls-plugin-api/src/Ide/Plugin/Properties.hs +++ b/hls-plugin-api/src/Ide/Plugin/Properties.hs @@ -112,7 +112,6 @@ data SomePropertyKeyWithMetaData -- A property is an immediate child of the json object in each plugin's "config" section. -- It was designed to be compatible with vscode's settings UI. -- Use 'emptyProperties' and 'useProperty' to create and consume 'Properties'. --- newtype Properties (r :: [PropertyKey]) = Properties (Map.Map String SomePropertyKeyWithMetaData) data Properties (r :: [PropertyKey]) where ConsProperties :: (k ~ 'PropertyKey s t, KnownSymbol s, NotElem s ks) => KeyNameProxy s -> (SPropertyKey k) -> (MetaData t) -> Properties ks -> Properties (k : ks) From 72d625a7f414e631072728b0555575cd16549ae7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 13 Jan 2024 19:20:22 +0800 Subject: [PATCH 05/28] remove redundant-constraints suppresion --- hls-plugin-api/src/Ide/Plugin/Properties.hs | 2 -- 1 file changed, 2 deletions(-) diff --git a/hls-plugin-api/src/Ide/Plugin/Properties.hs b/hls-plugin-api/src/Ide/Plugin/Properties.hs index ec94a6eedf..3e14bda908 100644 --- a/hls-plugin-api/src/Ide/Plugin/Properties.hs +++ b/hls-plugin-api/src/Ide/Plugin/Properties.hs @@ -11,8 +11,6 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} --- See Note [Constraints] -{-# OPTIONS_GHC -Wno-redundant-constraints #-} module Ide.Plugin.Properties ( PropertyType (..), From 2b1a43241145fc148c90b616374c217eeed9bf66 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 14 Jan 2024 06:01:41 +0800 Subject: [PATCH 06/28] add KeyNamePath (..), usePropertyByPathEither, usePropertyByPath and test. --- hls-plugin-api/hls-plugin-api.cabal | 1 + hls-plugin-api/src/Ide/Plugin/Properties.hs | 103 +++++++++++++++++--- hls-plugin-api/test/Ide/PluginUtilsTest.hs | 46 +++++++++ 3 files changed, 136 insertions(+), 14 deletions(-) diff --git a/hls-plugin-api/hls-plugin-api.cabal b/hls-plugin-api/hls-plugin-api.cabal index 2ec296cecf..6aba862784 100644 --- a/hls-plugin-api/hls-plugin-api.cabal +++ b/hls-plugin-api/hls-plugin-api.cabal @@ -112,6 +112,7 @@ test-suite tests Ide.TypesTests build-depends: + , aeson , base , containers , data-default diff --git a/hls-plugin-api/src/Ide/Plugin/Properties.hs b/hls-plugin-api/src/Ide/Plugin/Properties.hs index 3e14bda908..0e06e50066 100644 --- a/hls-plugin-api/src/Ide/Plugin/Properties.hs +++ b/hls-plugin-api/src/Ide/Plugin/Properties.hs @@ -11,6 +11,7 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE RankNTypes #-} module Ide.Plugin.Properties ( PropertyType (..), @@ -29,9 +30,13 @@ module Ide.Plugin.Properties defineObjectProperty, defineArrayProperty, defineEnumProperty, + definePropertiesProperty, toDefaultJSON, toVSCodeExtensionSchema, usePropertyEither, + KeyNamePath (..), + usePropertyByPathEither, + usePropertyByPath, useProperty, (&), ) @@ -47,6 +52,7 @@ import Data.String (IsString (fromString)) import qualified Data.Text as T import GHC.OverloadedLabels (IsLabel (..)) import GHC.TypeLits +import Control.Arrow (first) -- | Types properties may have data PropertyType @@ -57,6 +63,7 @@ data PropertyType | TObject Type | TArray Type | TEnum Type + | TProperties [PropertyKey] type family ToHsType (t :: PropertyType) where ToHsType 'TNumber = Double -- in js, there are no distinct types for integers and floating-point values @@ -66,13 +73,14 @@ type family ToHsType (t :: PropertyType) where ToHsType ('TObject a) = a ToHsType ('TArray a) = [a] ToHsType ('TEnum a) = a + ToHsType ('TProperties _) = A.Object -- --------------------------------------------------------------------- -- | Metadata of a property data MetaData (t :: PropertyType) where MetaData :: - (IsTEnum t ~ 'False) => + (IsTEnum t ~ 'False, IsProperties t ~ 'False) => { defaultValue :: ToHsType t, description :: T.Text } -> @@ -85,6 +93,15 @@ data MetaData (t :: PropertyType) where enumDescriptions :: [T.Text] } -> MetaData t + PropertiesMetaData :: + (t ~ TProperties rs) => + { + defaultValue :: ToHsType t + , description :: T.Text + , childrenProperties :: Properties rs + } -> + MetaData t + -- | Used at type level for name-type mapping in 'Properties' data PropertyKey = PropertyKey Symbol PropertyType @@ -98,12 +115,13 @@ data SPropertyKey (k :: PropertyKey) where SObject :: (A.ToJSON a, A.FromJSON a) => Proxy a -> SPropertyKey ('PropertyKey s ('TObject a)) SArray :: (A.ToJSON a, A.FromJSON a) => Proxy a -> SPropertyKey ('PropertyKey s ('TArray a)) SEnum :: (A.ToJSON a, A.FromJSON a, Eq a, Show a) => Proxy a -> SPropertyKey ('PropertyKey s ('TEnum a)) + SProperties :: SPropertyKey ('PropertyKey s ('TProperties pp)) -- | Existential wrapper of 'SPropertyKey', with an extra 'MetaData' data SomePropertyKeyWithMetaData = forall k s t. (k ~ 'PropertyKey s t) => - SomePropertyKeyWithMetaData (SPropertyKey k) (MetaData t) + SomePropertyKeyWithMetaData (SPropertyKey k) (MetaData t ) -- | 'Properties' is a partial implementation of json schema, without supporting union types and validation. -- In hls, it defines a set of properties which used in dedicated configuration of a plugin. @@ -121,12 +139,48 @@ data KeyNameProxy (s :: Symbol) = KnownSymbol s => KeyNameProxy instance (KnownSymbol s', s ~ s') => IsLabel s (KeyNameProxy s') where fromLabel = KeyNameProxy +-- | a path to a property in a json object +data KeyNamePath (r :: [Symbol]) where + SingleKey :: KeyNameProxy s -> KeyNamePath '[s] + ConsKeysPath :: KeyNameProxy s1 -> KeyNamePath (s2 ': ss) -> KeyNamePath (s1 ': s2 ': ss) + +class ParsePropertyPath (rs :: [PropertyKey]) (r :: [Symbol]) where + usePropertyByPathEither :: KeyNamePath r -> Properties rs -> A.Object -> Either String (ToHsType (FindByKeyPath r rs)) + useDefault :: KeyNamePath r -> Properties rs -> ToHsType (FindByKeyPath r rs) + usePropertyByPath :: KeyNamePath r -> Properties rs -> A.Object -> ToHsType (FindByKeyPath r rs) + usePropertyByPath p ps x = fromRight (useDefault p ps) $ usePropertyByPathEither p ps x +instance (HasProperty s k t r) => ParsePropertyPath r '[s] where + usePropertyByPathEither (SingleKey kn) sm x = parseProperty kn (find kn sm) x + useDefault (SingleKey kn) sm = defaultValue metadata + where (_, metadata) = find kn sm +instance ( ToHsType (FindByKeyPath (s2 : ss) r2) ~ ToHsType (FindByKeyPath (s1 : s2 : ss) r) + ,HasProperty s1 ('PropertyKey s1 ('TProperties r2)) t2 r + , ParsePropertyPath r2 (s2 : ss)) + => ParsePropertyPath r (s1 ': s2 ': ss) where + usePropertyByPathEither (ConsKeysPath kn p) sm x = do + let (key, meta) = find kn sm + interMedia <- parseProperty kn (key, meta) x + case meta of + PropertiesMetaData {..} + -> usePropertyByPathEither p childrenProperties interMedia + useDefault (ConsKeysPath kn p) sm = useDefault p childrenProperties + where (_, PropertiesMetaData {..}) = find kn sm + -- --------------------------------------------------------------------- +type family IsProperties (t :: PropertyType) :: Bool where + IsProperties ('TProperties pp) = 'True + IsProperties _ = 'False + type family IsTEnum (t :: PropertyType) :: Bool where IsTEnum ('TEnum _) = 'True IsTEnum _ = 'False +type family FindByKeyPath (s :: [Symbol]) (r :: [PropertyKey]) :: PropertyType where + FindByKeyPath (s ': '[]) ('PropertyKey s t ': _) = t + FindByKeyPath (s ': xs) ('PropertyKey s ('TProperties rs) ': _) = FindByKeyPath xs rs + FindByKeyPath (s ': xs) (_ ': ys) = FindByKeyPath (s ': xs) ys + type family FindByKeyName (s :: Symbol) (r :: [PropertyKey]) :: PropertyType where FindByKeyName s ('PropertyKey s t ': _) = t FindByKeyName s (_ ': xs) = FindByKeyName s xs @@ -146,9 +200,9 @@ type family NotElem (s :: Symbol) (r :: [PropertyKey]) :: Constraint where NotElem s '[] = () -- | In row @r@, there is a 'PropertyKey' @k@, which has name @s@ and carries haskell type @t@ -type HasProperty s k t r = (k ~ 'PropertyKey s t, Elem s r, FindByKeyName s r ~ t, KnownSymbol s, FindPropertyMeta s r t) +type HasProperty s k t r = (k ~ 'PropertyKey s t, Elem s r, FindByKeyPath '[s] r ~ t, FindByKeyName s r ~ t, KnownSymbol s, FindPropertyMeta s r t) class FindPropertyMeta (s :: Symbol) (r :: [PropertyKey]) t where - findSomePropertyKeyWithMetaData :: KeyNameProxy s -> Properties r -> (SPropertyKey ('PropertyKey s t), MetaData t) + findSomePropertyKeyWithMetaData :: KeyNameProxy s -> Properties r -> (SPropertyKey ('PropertyKey s t), MetaData t) instance (FindPropertyMetaIf (IsPropertySymbol symbol k) symbol k ks t) => FindPropertyMeta symbol (k : ks) t where findSomePropertyKeyWithMetaData = findSomePropertyKeyWithMetaDataIf class (bool ~ IsPropertySymbol symbol k) => FindPropertyMetaIf bool symbol k ks t where @@ -224,6 +278,7 @@ parseProperty :: A.Object -> Either String (ToHsType t) parseProperty kn k x = case k of + (SProperties, _) -> parseEither (SNumber, _) -> parseEither (SInteger, _) -> parseEither (SString, _) -> parseEither @@ -343,6 +398,16 @@ defineEnumProperty :: defineEnumProperty kn description enums defaultValue = insert kn (SEnum Proxy) $ EnumMetaData defaultValue description (fst <$> enums) (snd <$> enums) +definePropertiesProperty :: + (KnownSymbol s, NotElem s r) => + KeyNameProxy s -> + T.Text -> + Properties childrenProps -> + Properties r -> + Properties ('PropertyKey s ('TProperties childrenProps) : r) +definePropertiesProperty kn description ps rs = + insert kn SProperties (PropertiesMetaData mempty description ps) rs + -- --------------------------------------------------------------------- -- | Converts a properties definition into kv pairs with default values from 'MetaData' @@ -368,60 +433,68 @@ toDefaultJSON pr = case pr of fromString s A..= defaultValue (SomePropertyKeyWithMetaData (SEnum _) EnumMetaData {..}) -> fromString s A..= defaultValue + (SomePropertyKeyWithMetaData SProperties PropertiesMetaData {..}) -> + fromString s A..= A.object (toDefaultJSON childrenProperties) -- | Converts a properties definition into kv pairs as vscode schema toVSCodeExtensionSchema :: T.Text -> Properties r -> [A.Pair] -toVSCodeExtensionSchema prefix ps = case ps of +toVSCodeExtensionSchema prefix p = [fromString (T.unpack prefix <> fromString k) A..= v | (k, v) <- toVSCodeExtensionSchema' p] +toVSCodeExtensionSchema' :: Properties r -> [(String, A.Value)] +toVSCodeExtensionSchema' ps = case ps of EmptyProperties -> [] ConsProperties (keyNameProxy :: KeyNameProxy s) (k :: SPropertyKey k) (m :: MetaData t) xs -> - fromString (T.unpack prefix <> symbolVal keyNameProxy) A..= toEntry (SomePropertyKeyWithMetaData k m) : toVSCodeExtensionSchema prefix xs + [(symbolVal keyNameProxy <> maybe "" ((<>) ".") k1, v) + | (k1, v) <- toEntry (SomePropertyKeyWithMetaData k m) ] + ++ toVSCodeExtensionSchema' xs where - toEntry :: SomePropertyKeyWithMetaData -> A.Value + wrapEmpty :: A.Value -> [(Maybe String, A.Value)] + wrapEmpty v = [(Nothing, v)] + toEntry :: SomePropertyKeyWithMetaData -> [(Maybe String, A.Value)] toEntry = \case (SomePropertyKeyWithMetaData SNumber MetaData {..}) -> - A.object + wrapEmpty $ A.object [ "type" A..= A.String "number", "markdownDescription" A..= description, "default" A..= defaultValue, "scope" A..= A.String "resource" ] (SomePropertyKeyWithMetaData SInteger MetaData {..}) -> - A.object + wrapEmpty $ A.object [ "type" A..= A.String "integer", "markdownDescription" A..= description, "default" A..= defaultValue, "scope" A..= A.String "resource" ] (SomePropertyKeyWithMetaData SString MetaData {..}) -> - A.object + wrapEmpty $ A.object [ "type" A..= A.String "string", "markdownDescription" A..= description, "default" A..= defaultValue, "scope" A..= A.String "resource" ] (SomePropertyKeyWithMetaData SBoolean MetaData {..}) -> - A.object + wrapEmpty $ A.object [ "type" A..= A.String "boolean", "markdownDescription" A..= description, "default" A..= defaultValue, "scope" A..= A.String "resource" ] (SomePropertyKeyWithMetaData (SObject _) MetaData {..}) -> - A.object + wrapEmpty $ A.object [ "type" A..= A.String "object", "markdownDescription" A..= description, "default" A..= defaultValue, "scope" A..= A.String "resource" ] (SomePropertyKeyWithMetaData (SArray _) MetaData {..}) -> - A.object + wrapEmpty $ A.object [ "type" A..= A.String "array", "markdownDescription" A..= description, "default" A..= defaultValue, "scope" A..= A.String "resource" ] (SomePropertyKeyWithMetaData (SEnum _) EnumMetaData {..}) -> - A.object + wrapEmpty $ A.object [ "type" A..= A.String "string", "description" A..= description, "enum" A..= enumValues, @@ -429,3 +502,5 @@ toVSCodeExtensionSchema prefix ps = case ps of "default" A..= defaultValue, "scope" A..= A.String "resource" ] + (SomePropertyKeyWithMetaData SProperties PropertiesMetaData {..}) -> + map (first Just) $ toVSCodeExtensionSchema' childrenProperties diff --git a/hls-plugin-api/test/Ide/PluginUtilsTest.hs b/hls-plugin-api/test/Ide/PluginUtilsTest.hs index a4f16a4491..23bf780aa0 100644 --- a/hls-plugin-api/test/Ide/PluginUtilsTest.hs +++ b/hls-plugin-api/test/Ide/PluginUtilsTest.hs @@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE TypeApplications #-} module Ide.PluginUtilsTest @@ -16,6 +17,11 @@ import Language.LSP.Protocol.Types (Position (..), Range (Range), import Test.Tasty import Test.Tasty.HUnit import Test.Tasty.QuickCheck +import Ide.Plugin.Properties (emptyProperties, definePropertiesProperty, defineStringProperty, toVSCodeExtensionSchema, KeyNamePath (..)) +import Data.Function ((&)) +import qualified Data.Aeson as A +import Ide.Plugin.Properties (toDefaultJSON, usePropertyByPathEither, usePropertyByPath) +import qualified Data.Aeson.Types as A tests :: TestTree tests = testGroup "PluginUtils" @@ -24,6 +30,7 @@ tests = testGroup "PluginUtils" , localOption (QuickCheckMaxSize 10000) $ testProperty "RangeMap-List filtering identical" $ prop_rangemapListEq @Int + , propertyTest ] unescapeTest :: TestTree @@ -137,3 +144,42 @@ prop_rangemapListEq r xs = cover 5 (length filteredList == 1) "1 match" $ cover 2 (length filteredList > 1) ">1 matches" $ Set.fromList filteredList === Set.fromList filteredRangeMap + + +propertyTest :: TestTree +propertyTest = testGroup "property api tests" [ + testCase "property toVSCodeExtensionSchema" $ do + let expect = "[(\"top.baz\",Object (fromList [(\"default\",String \"baz\"),(\"markdownDescription\",String \"baz\"),(\"scope\",String \"resource\"),(\"type\",String \"string\")])),(\"top.parent.foo\",Object (fromList [(\"default\",String \"foo\"),(\"markdownDescription\",String \"foo\"),(\"scope\",String \"resource\"),(\"type\",String \"string\")]))]" + let result = toVSCodeExtensionSchema "top." nestedPropertiesExample + show result @?= expect + , testCase "property toDefaultJSON" $ do + let expect = "[(\"baz\",String \"baz\"),(\"parent\",Object (fromList [(\"foo\",String \"foo\")]))]" + let result = toDefaultJSON nestedPropertiesExample + show result @?= expect + , testCase "parsePropertyPath single key path" $ do + let obj = A.object (toDefaultJSON nestedPropertiesExample) + let (Right key1) = A.parseEither (A.withObject "test parsePropertyPath" $ \o -> do + let (Right key1) = usePropertyByPathEither examplePath1 nestedPropertiesExample o + return key1) obj + key1 @?= "baz" + + , testCase "parsePropertyPath two key path" $ do + let obj = A.object (toDefaultJSON nestedPropertiesExample) + let (Right key1) = A.parseEither (A.withObject "test parsePropertyPath" $ \o -> do + let (Right key1) = usePropertyByPathEither examplePath2 nestedPropertiesExample o + return key1) obj + key1 @?= "foo" + , testCase "parsePropertyPath two key path default" $ do + let obj = A.object [] + let (Right key1) = A.parseEither (A.withObject "test parsePropertyPath" $ \o -> do + let key1 = usePropertyByPath examplePath2 nestedPropertiesExample o + return key1) obj + key1 @?= "foo" + ] + where + nestedPropertiesExample = emptyProperties + & definePropertiesProperty #parent "parent" (emptyProperties & defineStringProperty #foo "foo" "foo") + & defineStringProperty #baz "baz" "baz" + + examplePath1 = SingleKey #baz + examplePath2 = ConsKeysPath #parent (SingleKey #foo) From 0b3c0b98f5570b55c5aa9db89b1b7edc25c93cb8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 14 Jan 2024 06:57:17 +0800 Subject: [PATCH 07/28] add usePropertyByPathAction action to allow getting nested property --- ghcide/src/Development/IDE/Core/Rules.hs | 19 +++++++++++++++++-- hls-plugin-api/src/Ide/Plugin/Properties.hs | 6 +++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/ghcide/src/Development/IDE/Core/Rules.hs b/ghcide/src/Development/IDE/Core/Rules.hs index 7cc89ce170..884f118bc9 100644 --- a/ghcide/src/Development/IDE/Core/Rules.hs +++ b/ghcide/src/Development/IDE/Core/Rules.hs @@ -27,6 +27,7 @@ module Development.IDE.Core.Rules( getParsedModuleWithComments, getClientConfigAction, usePropertyAction, + usePropertyByPathAction, getHieFile, -- * Rules CompiledLinkables(..), @@ -144,7 +145,10 @@ import Ide.Plugin.Properties (HasProperty, KeyNameProxy, Properties, ToHsType, - useProperty) + useProperty, + usePropertyByPath, + HasPropertyByPath + ) import Ide.Types (DynFlagsModifications (dynFlagsModifyGlobal, dynFlagsModifyParser), PluginId) import Control.Concurrent.STM.Stats (atomically) @@ -169,6 +173,7 @@ import GHC (mgModSummaries) #if MIN_VERSION_ghc(9,3,0) import qualified Data.IntMap as IM +import Ide.Plugin.Properties (KeyNamePath) #endif @@ -538,7 +543,7 @@ reportImportCyclesRule recorder = let cycles = mapMaybe (cycleErrorInFile fileId) (toList errs) -- Convert cycles of files into cycles of module names forM cycles $ \(imp, files) -> do - modNames <- forM files $ + modNames <- forM files $ getModuleName . idToPath depPathIdMap pure $ toDiag imp $ sort modNames where cycleErrorInFile f (PartOfCycle imp fs) @@ -1108,6 +1113,16 @@ usePropertyAction kn plId p = do pluginConfig <- getPluginConfigAction plId pure $ useProperty kn p $ plcConfig pluginConfig +usePropertyByPathAction :: + (HasPropertyByPath props path t) => + KeyNamePath path -> + PluginId -> + Properties props -> + Action (ToHsType t) +usePropertyByPathAction path plId p = do + pluginConfig <- getPluginConfigAction plId + pure $ usePropertyByPath path p $ plcConfig pluginConfig + -- --------------------------------------------------------------------- getLinkableRule :: Recorder (WithPriority Log) -> Rules () diff --git a/hls-plugin-api/src/Ide/Plugin/Properties.hs b/hls-plugin-api/src/Ide/Plugin/Properties.hs index 9255c56a96..2e940230d2 100644 --- a/hls-plugin-api/src/Ide/Plugin/Properties.hs +++ b/hls-plugin-api/src/Ide/Plugin/Properties.hs @@ -35,10 +35,11 @@ module Ide.Plugin.Properties toDefaultJSON, toVSCodeExtensionSchema, usePropertyEither, + useProperty, KeyNamePath (..), usePropertyByPathEither, usePropertyByPath, - useProperty, + HasPropertyByPath, (&), ) where @@ -201,8 +202,11 @@ type family NotElem (s :: Symbol) (r :: [PropertyKey]) :: Constraint where NotElem s (_ ': xs) = NotElem s xs NotElem s '[] = () + -- | In row @r@, there is a 'PropertyKey' @k@, which has name @s@ and carries haskell type @t@ type HasProperty s k t r = (k ~ 'PropertyKey s t, Elem s r, FindByKeyPath '[s] r ~ t, FindByKeyName s r ~ t, KnownSymbol s, FindPropertyMeta s r t) +-- similar to HasProperty, but the path is given as a type-level list of symbols +type HasPropertyByPath props path t = (t ~ FindByKeyPath path props, ParsePropertyPath props path) class FindPropertyMeta (s :: Symbol) (r :: [PropertyKey]) t where findSomePropertyKeyWithMetaData :: KeyNameProxy s -> Properties r -> (SPropertyKey ('PropertyKey s t), MetaData t) instance (FindPropertyMetaIf (IsPropertySymbol symbol k) symbol k ks t) => FindPropertyMeta symbol (k : ks) t where From 3e9c9c86a108903a28719d1c2225cede5143fc75 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 14 Jan 2024 06:58:51 +0800 Subject: [PATCH 08/28] clean up --- hls-plugin-api/src/Ide/Plugin/Properties.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hls-plugin-api/src/Ide/Plugin/Properties.hs b/hls-plugin-api/src/Ide/Plugin/Properties.hs index 2e940230d2..75f8015242 100644 --- a/hls-plugin-api/src/Ide/Plugin/Properties.hs +++ b/hls-plugin-api/src/Ide/Plugin/Properties.hs @@ -21,8 +21,10 @@ module Ide.Plugin.Properties PropertyKey (..), SPropertyKey (..), KeyNameProxy (..), + KeyNamePath (..), Properties, HasProperty, + HasPropertyByPath, emptyProperties, defineNumberProperty, defineIntegerProperty, @@ -36,10 +38,8 @@ module Ide.Plugin.Properties toVSCodeExtensionSchema, usePropertyEither, useProperty, - KeyNamePath (..), usePropertyByPathEither, usePropertyByPath, - HasPropertyByPath, (&), ) where From f213a8c55fb0c79a761ea6d20e6cdf3e14d367ed Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 14 Jan 2024 07:32:05 +0800 Subject: [PATCH 09/28] cleanup --- hls-plugin-api/src/Ide/Plugin/Properties.hs | 32 ++++++++++++--------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/hls-plugin-api/src/Ide/Plugin/Properties.hs b/hls-plugin-api/src/Ide/Plugin/Properties.hs index 75f8015242..e52d6d0347 100644 --- a/hls-plugin-api/src/Ide/Plugin/Properties.hs +++ b/hls-plugin-api/src/Ide/Plugin/Properties.hs @@ -142,24 +142,27 @@ data KeyNameProxy (s :: Symbol) = KnownSymbol s => KeyNameProxy instance (KnownSymbol s', s ~ s') => IsLabel s (KeyNameProxy s') where fromLabel = KeyNameProxy +data NonEmptyList a = + a :| NonEmptyList a | NE a + -- | a path to a property in a json object -data KeyNamePath (r :: [Symbol]) where - SingleKey :: KeyNameProxy s -> KeyNamePath '[s] - ConsKeysPath :: KeyNameProxy s1 -> KeyNamePath (s2 ': ss) -> KeyNamePath (s1 ': s2 ': ss) +data KeyNamePath (r :: NonEmptyList Symbol) where + SingleKey :: KeyNameProxy s -> KeyNamePath (NE s) + ConsKeysPath :: KeyNameProxy s1 -> KeyNamePath ss -> KeyNamePath (s1 :| ss) -class ParsePropertyPath (rs :: [PropertyKey]) (r :: [Symbol]) where +class ParsePropertyPath (rs :: [PropertyKey]) (r :: NonEmptyList Symbol) where usePropertyByPathEither :: KeyNamePath r -> Properties rs -> A.Object -> Either String (ToHsType (FindByKeyPath r rs)) useDefault :: KeyNamePath r -> Properties rs -> ToHsType (FindByKeyPath r rs) usePropertyByPath :: KeyNamePath r -> Properties rs -> A.Object -> ToHsType (FindByKeyPath r rs) usePropertyByPath p ps x = fromRight (useDefault p ps) $ usePropertyByPathEither p ps x -instance (HasProperty s k t r) => ParsePropertyPath r '[s] where +instance (HasProperty s k t r) => ParsePropertyPath r (NE s) where usePropertyByPathEither (SingleKey kn) sm x = parseProperty kn (find kn sm) x useDefault (SingleKey kn) sm = defaultValue metadata where (_, metadata) = find kn sm -instance ( ToHsType (FindByKeyPath (s2 : ss) r2) ~ ToHsType (FindByKeyPath (s1 : s2 : ss) r) +instance ( ToHsType (FindByKeyPath ss r2) ~ ToHsType (FindByKeyPath (s1 :| ss) r) ,HasProperty s1 ('PropertyKey s1 ('TProperties r2)) t2 r - , ParsePropertyPath r2 (s2 : ss)) - => ParsePropertyPath r (s1 ': s2 ': ss) where + , ParsePropertyPath r2 ss) + => ParsePropertyPath r (s1 :| ss) where usePropertyByPathEither (ConsKeysPath kn p) sm x = do let (key, meta) = find kn sm interMedia <- parseProperty kn (key, meta) x @@ -179,10 +182,13 @@ type family IsTEnum (t :: PropertyType) :: Bool where IsTEnum ('TEnum _) = 'True IsTEnum _ = 'False -type family FindByKeyPath (s :: [Symbol]) (r :: [PropertyKey]) :: PropertyType where - FindByKeyPath (s ': '[]) ('PropertyKey s t ': _) = t - FindByKeyPath (s ': xs) ('PropertyKey s ('TProperties rs) ': _) = FindByKeyPath xs rs - FindByKeyPath (s ': xs) (_ ': ys) = FindByKeyPath (s ': xs) ys +type family FindByKeyPath (ne :: NonEmptyList Symbol) (r :: [PropertyKey]) :: PropertyType where + FindByKeyPath (s :| xs) ('PropertyKey s ('TProperties rs) ': _) = FindByKeyPath xs rs + FindByKeyPath (s :| xs) (_ ': ys) = FindByKeyPath (s :| xs) ys + FindByKeyPath (NE s) ('PropertyKey s t ': _) = t + FindByKeyPath (NE s) (_ ': ys) = FindByKeyPath (NE s) ys + FindByKeyPath (NE s) '[] = TypeError ('Text "The key ‘" ':<>: 'Text s ':<>: 'Text "’ is missing") + FindByKeyPath (s :| xs) '[] = TypeError ('Text "The key ‘" ':<>: 'Text s ':<>: 'Text "’ is missing") type family FindByKeyName (s :: Symbol) (r :: [PropertyKey]) :: PropertyType where FindByKeyName s ('PropertyKey s t ': _) = t @@ -204,7 +210,7 @@ type family NotElem (s :: Symbol) (r :: [PropertyKey]) :: Constraint where -- | In row @r@, there is a 'PropertyKey' @k@, which has name @s@ and carries haskell type @t@ -type HasProperty s k t r = (k ~ 'PropertyKey s t, Elem s r, FindByKeyPath '[s] r ~ t, FindByKeyName s r ~ t, KnownSymbol s, FindPropertyMeta s r t) +type HasProperty s k t r = (k ~ 'PropertyKey s t, Elem s r, FindByKeyPath (NE s) r ~ t, FindByKeyName s r ~ t, KnownSymbol s, FindPropertyMeta s r t) -- similar to HasProperty, but the path is given as a type-level list of symbols type HasPropertyByPath props path t = (t ~ FindByKeyPath path props, ParsePropertyPath props path) class FindPropertyMeta (s :: Symbol) (r :: [PropertyKey]) t where From 28f96567a6c40d21e174acd5340ef09f0af369d1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 14 Jan 2024 07:32:13 +0800 Subject: [PATCH 10/28] cleanup --- hls-plugin-api/src/Ide/Plugin/Properties.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/hls-plugin-api/src/Ide/Plugin/Properties.hs b/hls-plugin-api/src/Ide/Plugin/Properties.hs index e52d6d0347..58ba19bb74 100644 --- a/hls-plugin-api/src/Ide/Plugin/Properties.hs +++ b/hls-plugin-api/src/Ide/Plugin/Properties.hs @@ -188,7 +188,6 @@ type family FindByKeyPath (ne :: NonEmptyList Symbol) (r :: [PropertyKey]) :: Pr FindByKeyPath (NE s) ('PropertyKey s t ': _) = t FindByKeyPath (NE s) (_ ': ys) = FindByKeyPath (NE s) ys FindByKeyPath (NE s) '[] = TypeError ('Text "The key ‘" ':<>: 'Text s ':<>: 'Text "’ is missing") - FindByKeyPath (s :| xs) '[] = TypeError ('Text "The key ‘" ':<>: 'Text s ':<>: 'Text "’ is missing") type family FindByKeyName (s :: Symbol) (r :: [PropertyKey]) :: PropertyType where FindByKeyName s ('PropertyKey s t ': _) = t From 2be2ebbd13ec16aaec30ed8fa31431e7648bd8da Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 14 Jan 2024 07:34:01 +0800 Subject: [PATCH 11/28] cleanup --- hls-plugin-api/src/Ide/Plugin/Properties.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hls-plugin-api/src/Ide/Plugin/Properties.hs b/hls-plugin-api/src/Ide/Plugin/Properties.hs index 58ba19bb74..ba6733d71a 100644 --- a/hls-plugin-api/src/Ide/Plugin/Properties.hs +++ b/hls-plugin-api/src/Ide/Plugin/Properties.hs @@ -159,10 +159,10 @@ instance (HasProperty s k t r) => ParsePropertyPath r (NE s) where usePropertyByPathEither (SingleKey kn) sm x = parseProperty kn (find kn sm) x useDefault (SingleKey kn) sm = defaultValue metadata where (_, metadata) = find kn sm -instance ( ToHsType (FindByKeyPath ss r2) ~ ToHsType (FindByKeyPath (s1 :| ss) r) - ,HasProperty s1 ('PropertyKey s1 ('TProperties r2)) t2 r +instance ( ToHsType (FindByKeyPath ss r2) ~ ToHsType (FindByKeyPath (s :| ss) r) + ,HasProperty s ('PropertyKey s ('TProperties r2)) t2 r , ParsePropertyPath r2 ss) - => ParsePropertyPath r (s1 :| ss) where + => ParsePropertyPath r (s :| ss) where usePropertyByPathEither (ConsKeysPath kn p) sm x = do let (key, meta) = find kn sm interMedia <- parseProperty kn (key, meta) x From 8af4caf69847b414d3e96053cfa8b02f0e3f3c93 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 14 Jan 2024 07:36:06 +0800 Subject: [PATCH 12/28] reorder import --- ghcide/src/Development/IDE/Core/Rules.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ghcide/src/Development/IDE/Core/Rules.hs b/ghcide/src/Development/IDE/Core/Rules.hs index 884f118bc9..0c808e84ad 100644 --- a/ghcide/src/Development/IDE/Core/Rules.hs +++ b/ghcide/src/Development/IDE/Core/Rules.hs @@ -143,6 +143,7 @@ import System.Directory (makeAbsolute, doe import Data.Default (def, Default) import Ide.Plugin.Properties (HasProperty, KeyNameProxy, + KeyNamePath, Properties, ToHsType, useProperty, @@ -173,7 +174,6 @@ import GHC (mgModSummaries) #if MIN_VERSION_ghc(9,3,0) import qualified Data.IntMap as IM -import Ide.Plugin.Properties (KeyNamePath) #endif From bd99b4447d87ef63bfa180d846becd6e56c4b0c1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 14 Jan 2024 15:30:42 +0800 Subject: [PATCH 13/28] reformat --- hls-plugin-api/test/Ide/PluginUtilsTest.hs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/hls-plugin-api/test/Ide/PluginUtilsTest.hs b/hls-plugin-api/test/Ide/PluginUtilsTest.hs index 23bf780aa0..153fa2bc16 100644 --- a/hls-plugin-api/test/Ide/PluginUtilsTest.hs +++ b/hls-plugin-api/test/Ide/PluginUtilsTest.hs @@ -1,14 +1,24 @@ +{-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE TypeApplications #-} module Ide.PluginUtilsTest ( tests ) where +import qualified Data.Aeson as A +import qualified Data.Aeson.Types as A import Data.Char (isPrint) +import Data.Function ((&)) import qualified Data.Set as Set import qualified Data.Text as T +import Ide.Plugin.Properties (KeyNamePath (..), + definePropertiesProperty, + defineStringProperty, + emptyProperties, toDefaultJSON, + toVSCodeExtensionSchema, + usePropertyByPath, + usePropertyByPathEither) import qualified Ide.Plugin.RangeMap as RangeMap import Ide.PluginUtils (extractTextInRange, positionInRange, unescape) @@ -17,11 +27,6 @@ import Language.LSP.Protocol.Types (Position (..), Range (Range), import Test.Tasty import Test.Tasty.HUnit import Test.Tasty.QuickCheck -import Ide.Plugin.Properties (emptyProperties, definePropertiesProperty, defineStringProperty, toVSCodeExtensionSchema, KeyNamePath (..)) -import Data.Function ((&)) -import qualified Data.Aeson as A -import Ide.Plugin.Properties (toDefaultJSON, usePropertyByPathEither, usePropertyByPath) -import qualified Data.Aeson.Types as A tests :: TestTree tests = testGroup "PluginUtils" From c7f20f18b75e9e507cf563cb7aa99b641bbb1751 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 14 Jan 2024 15:32:10 +0800 Subject: [PATCH 14/28] add test --- hls-plugin-api/test/Ide/PluginUtilsTest.hs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/hls-plugin-api/test/Ide/PluginUtilsTest.hs b/hls-plugin-api/test/Ide/PluginUtilsTest.hs index 153fa2bc16..df456b15b6 100644 --- a/hls-plugin-api/test/Ide/PluginUtilsTest.hs +++ b/hls-plugin-api/test/Ide/PluginUtilsTest.hs @@ -167,7 +167,6 @@ propertyTest = testGroup "property api tests" [ let (Right key1) = usePropertyByPathEither examplePath1 nestedPropertiesExample o return key1) obj key1 @?= "baz" - , testCase "parsePropertyPath two key path" $ do let obj = A.object (toDefaultJSON nestedPropertiesExample) let (Right key1) = A.parseEither (A.withObject "test parsePropertyPath" $ \o -> do @@ -180,11 +179,21 @@ propertyTest = testGroup "property api tests" [ let key1 = usePropertyByPath examplePath2 nestedPropertiesExample o return key1) obj key1 @?= "foo" + , testCase "parsePropertyPath two key path not default" $ do + let obj = A.object (toDefaultJSON nestedPropertiesExample2) + let (Right key1) = A.parseEither (A.withObject "test parsePropertyPath" $ \o -> do + let (Right key1) = usePropertyByPathEither examplePath2 nestedPropertiesExample o + return key1) obj + key1 @?= "xxx" ] where nestedPropertiesExample = emptyProperties & definePropertiesProperty #parent "parent" (emptyProperties & defineStringProperty #foo "foo" "foo") & defineStringProperty #baz "baz" "baz" + nestedPropertiesExample2 = emptyProperties + & definePropertiesProperty #parent "parent" (emptyProperties & defineStringProperty #foo "foo" "xxx") + & defineStringProperty #baz "baz" "baz" + examplePath1 = SingleKey #baz examplePath2 = ConsKeysPath #parent (SingleKey #foo) From 28165cc78176b748c93d111d35cd7a62e4c8858f Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 14 Jan 2024 17:19:51 +0800 Subject: [PATCH 15/28] stylish --- hls-plugin-api/test/Ide/PluginUtilsTest.hs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hls-plugin-api/test/Ide/PluginUtilsTest.hs b/hls-plugin-api/test/Ide/PluginUtilsTest.hs index df456b15b6..a7126193e5 100644 --- a/hls-plugin-api/test/Ide/PluginUtilsTest.hs +++ b/hls-plugin-api/test/Ide/PluginUtilsTest.hs @@ -197,3 +197,11 @@ propertyTest = testGroup "property api tests" [ examplePath1 = SingleKey #baz examplePath2 = ConsKeysPath #parent (SingleKey #foo) + + +sieve = sx [2..] + where + sx (p:xs) = p : sx [x| x <- xs, x `mod` p > 0] + +main = do + print (take 10000 sieve) From 437bcfd35a8b3e856bb2768c80df5dda40e2f3da Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 14 Jan 2024 17:21:38 +0800 Subject: [PATCH 16/28] Revert "stylish" This reverts commit 28165cc78176b748c93d111d35cd7a62e4c8858f. --- hls-plugin-api/test/Ide/PluginUtilsTest.hs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/hls-plugin-api/test/Ide/PluginUtilsTest.hs b/hls-plugin-api/test/Ide/PluginUtilsTest.hs index a7126193e5..df456b15b6 100644 --- a/hls-plugin-api/test/Ide/PluginUtilsTest.hs +++ b/hls-plugin-api/test/Ide/PluginUtilsTest.hs @@ -197,11 +197,3 @@ propertyTest = testGroup "property api tests" [ examplePath1 = SingleKey #baz examplePath2 = ConsKeysPath #parent (SingleKey #foo) - - -sieve = sx [2..] - where - sx (p:xs) = p : sx [x| x <- xs, x `mod` p > 0] - -main = do - print (take 10000 sieve) From e75b0d808ab987548743eced59294335f4d65e51 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 14 Jan 2024 17:24:10 +0800 Subject: [PATCH 17/28] stylish --- hls-plugin-api/src/Ide/Plugin/Properties.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hls-plugin-api/src/Ide/Plugin/Properties.hs b/hls-plugin-api/src/Ide/Plugin/Properties.hs index ba6733d71a..ecc46210e2 100644 --- a/hls-plugin-api/src/Ide/Plugin/Properties.hs +++ b/hls-plugin-api/src/Ide/Plugin/Properties.hs @@ -6,12 +6,12 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} -{-# LANGUAGE RankNTypes #-} module Ide.Plugin.Properties @@ -44,6 +44,7 @@ module Ide.Plugin.Properties ) where +import Control.Arrow (first) import qualified Data.Aeson as A import qualified Data.Aeson.Types as A import Data.Either (fromRight) @@ -54,7 +55,6 @@ import Data.String (IsString (fromString)) import qualified Data.Text as T import GHC.OverloadedLabels (IsLabel (..)) import GHC.TypeLits -import Control.Arrow (first) -- | Types properties may have From f780642dffc72e681555c47ba1768e7fbde00b0d Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 14 Jan 2024 17:38:02 +0800 Subject: [PATCH 18/28] simplify the logic of FindByKeyPath --- hls-plugin-api/src/Ide/Plugin/Properties.hs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/hls-plugin-api/src/Ide/Plugin/Properties.hs b/hls-plugin-api/src/Ide/Plugin/Properties.hs index ecc46210e2..c1f2b26acf 100644 --- a/hls-plugin-api/src/Ide/Plugin/Properties.hs +++ b/hls-plugin-api/src/Ide/Plugin/Properties.hs @@ -185,9 +185,7 @@ type family IsTEnum (t :: PropertyType) :: Bool where type family FindByKeyPath (ne :: NonEmptyList Symbol) (r :: [PropertyKey]) :: PropertyType where FindByKeyPath (s :| xs) ('PropertyKey s ('TProperties rs) ': _) = FindByKeyPath xs rs FindByKeyPath (s :| xs) (_ ': ys) = FindByKeyPath (s :| xs) ys - FindByKeyPath (NE s) ('PropertyKey s t ': _) = t - FindByKeyPath (NE s) (_ ': ys) = FindByKeyPath (NE s) ys - FindByKeyPath (NE s) '[] = TypeError ('Text "The key ‘" ':<>: 'Text s ':<>: 'Text "’ is missing") + FindByKeyPath (NE s) ys = FindByKeyName s ys type family FindByKeyName (s :: Symbol) (r :: [PropertyKey]) :: PropertyType where FindByKeyName s ('PropertyKey s t ': _) = t From d13901f7f5d55d379579b487f0f2dedd7f016906 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 14 Jan 2024 17:45:43 +0800 Subject: [PATCH 19/28] use case instead of partial pattern --- hls-plugin-api/src/Ide/Plugin/Properties.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hls-plugin-api/src/Ide/Plugin/Properties.hs b/hls-plugin-api/src/Ide/Plugin/Properties.hs index c1f2b26acf..b0c64da543 100644 --- a/hls-plugin-api/src/Ide/Plugin/Properties.hs +++ b/hls-plugin-api/src/Ide/Plugin/Properties.hs @@ -169,8 +169,8 @@ instance ( ToHsType (FindByKeyPath ss r2) ~ ToHsType (FindByKeyPath (s :| ss) r) case meta of PropertiesMetaData {..} -> usePropertyByPathEither p childrenProperties interMedia - useDefault (ConsKeysPath kn p) sm = useDefault p childrenProperties - where (_, PropertiesMetaData {..}) = find kn sm + useDefault (ConsKeysPath kn p) sm = case find kn sm of + (_, PropertiesMetaData {..}) -> useDefault p childrenProperties -- --------------------------------------------------------------------- From 71d927c2a669e776f45f191d0f7f0d3da8215b1d Mon Sep 17 00:00:00 2001 From: soulomoon Date: Thu, 16 May 2024 16:01:47 +0800 Subject: [PATCH 20/28] Update hls-plugin-api/src/Ide/Plugin/Properties.hs Co-authored-by: fendor --- hls-plugin-api/src/Ide/Plugin/Properties.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hls-plugin-api/src/Ide/Plugin/Properties.hs b/hls-plugin-api/src/Ide/Plugin/Properties.hs index b0c64da543..3dd8da1661 100644 --- a/hls-plugin-api/src/Ide/Plugin/Properties.hs +++ b/hls-plugin-api/src/Ide/Plugin/Properties.hs @@ -124,7 +124,7 @@ data SPropertyKey (k :: PropertyKey) where data SomePropertyKeyWithMetaData = forall k s t. (k ~ 'PropertyKey s t) => - SomePropertyKeyWithMetaData (SPropertyKey k) (MetaData t ) + SomePropertyKeyWithMetaData (SPropertyKey k) (MetaData t) -- | 'Properties' is a partial implementation of json schema, without supporting union types and validation. -- In hls, it defines a set of properties which used in dedicated configuration of a plugin. From a82fe338be544ad368fff34ff83cebccdee34869 Mon Sep 17 00:00:00 2001 From: soulomoon Date: Thu, 16 May 2024 16:01:59 +0800 Subject: [PATCH 21/28] Update hls-plugin-api/src/Ide/Plugin/Properties.hs Co-authored-by: fendor --- hls-plugin-api/src/Ide/Plugin/Properties.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/hls-plugin-api/src/Ide/Plugin/Properties.hs b/hls-plugin-api/src/Ide/Plugin/Properties.hs index 3dd8da1661..ccd0b92e8c 100644 --- a/hls-plugin-api/src/Ide/Plugin/Properties.hs +++ b/hls-plugin-api/src/Ide/Plugin/Properties.hs @@ -155,6 +155,7 @@ class ParsePropertyPath (rs :: [PropertyKey]) (r :: NonEmptyList Symbol) where useDefault :: KeyNamePath r -> Properties rs -> ToHsType (FindByKeyPath r rs) usePropertyByPath :: KeyNamePath r -> Properties rs -> A.Object -> ToHsType (FindByKeyPath r rs) usePropertyByPath p ps x = fromRight (useDefault p ps) $ usePropertyByPathEither p ps x + instance (HasProperty s k t r) => ParsePropertyPath r (NE s) where usePropertyByPathEither (SingleKey kn) sm x = parseProperty kn (find kn sm) x useDefault (SingleKey kn) sm = defaultValue metadata From 4c13973e48eab5bae534c4c8f5befc8c0ff73dea Mon Sep 17 00:00:00 2001 From: soulomoon Date: Thu, 16 May 2024 16:02:17 +0800 Subject: [PATCH 22/28] Update hls-plugin-api/src/Ide/Plugin/Properties.hs Co-authored-by: fendor --- hls-plugin-api/src/Ide/Plugin/Properties.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/hls-plugin-api/src/Ide/Plugin/Properties.hs b/hls-plugin-api/src/Ide/Plugin/Properties.hs index ccd0b92e8c..23ed295a65 100644 --- a/hls-plugin-api/src/Ide/Plugin/Properties.hs +++ b/hls-plugin-api/src/Ide/Plugin/Properties.hs @@ -160,6 +160,7 @@ instance (HasProperty s k t r) => ParsePropertyPath r (NE s) where usePropertyByPathEither (SingleKey kn) sm x = parseProperty kn (find kn sm) x useDefault (SingleKey kn) sm = defaultValue metadata where (_, metadata) = find kn sm + instance ( ToHsType (FindByKeyPath ss r2) ~ ToHsType (FindByKeyPath (s :| ss) r) ,HasProperty s ('PropertyKey s ('TProperties r2)) t2 r , ParsePropertyPath r2 ss) From cf0ab4fd3026f223225c498ad2534d90080aef46 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 16 May 2024 16:32:43 +0800 Subject: [PATCH 23/28] add golden test and add comment --- hls-plugin-api/hls-plugin-api.cabal | 2 + .../testdata/Property/NestedProperty.json | 1 + .../test/testdata/PropertyNestedProperty.json | 1 + hls-plugin-api/src/Ide/Plugin/Properties.hs | 2 +- hls-plugin-api/test/Ide/PluginUtilsTest.hs | 41 ++++++++++++------- .../testdata/Property/NestedProperty.json | 1 + 6 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 hls-plugin-api/hls-plugin-api/test/testdata/Property/NestedProperty.json create mode 100644 hls-plugin-api/hls-plugin-api/test/testdata/PropertyNestedProperty.json create mode 100644 hls-plugin-api/test/testdata/Property/NestedProperty.json diff --git a/hls-plugin-api/hls-plugin-api.cabal b/hls-plugin-api/hls-plugin-api.cabal index 0600c26c25..eb00b42e00 100644 --- a/hls-plugin-api/hls-plugin-api.cabal +++ b/hls-plugin-api/hls-plugin-api.cabal @@ -112,6 +112,7 @@ test-suite tests Ide.TypesTests build-depends: + , bytestring , aeson , base , containers @@ -120,6 +121,7 @@ test-suite tests , lens , lsp-types , tasty + , tasty-golden , tasty-hunit , tasty-quickcheck , tasty-rerun diff --git a/hls-plugin-api/hls-plugin-api/test/testdata/Property/NestedProperty.json b/hls-plugin-api/hls-plugin-api/test/testdata/Property/NestedProperty.json new file mode 100644 index 0000000000..33d84fc425 --- /dev/null +++ b/hls-plugin-api/hls-plugin-api/test/testdata/Property/NestedProperty.json @@ -0,0 +1 @@ +{"top.baz":{"default":"baz","markdownDescription":"baz","scope":"resource","type":"string"},"top.parent.foo":{"default":"foo","markdownDescription":"foo","scope":"resource","type":"string"}} \ No newline at end of file diff --git a/hls-plugin-api/hls-plugin-api/test/testdata/PropertyNestedProperty.json b/hls-plugin-api/hls-plugin-api/test/testdata/PropertyNestedProperty.json new file mode 100644 index 0000000000..33d84fc425 --- /dev/null +++ b/hls-plugin-api/hls-plugin-api/test/testdata/PropertyNestedProperty.json @@ -0,0 +1 @@ +{"top.baz":{"default":"baz","markdownDescription":"baz","scope":"resource","type":"string"},"top.parent.foo":{"default":"foo","markdownDescription":"foo","scope":"resource","type":"string"}} \ No newline at end of file diff --git a/hls-plugin-api/src/Ide/Plugin/Properties.hs b/hls-plugin-api/src/Ide/Plugin/Properties.hs index ecfcde3712..dda2bb7e33 100644 --- a/hls-plugin-api/src/Ide/Plugin/Properties.hs +++ b/hls-plugin-api/src/Ide/Plugin/Properties.hs @@ -67,7 +67,7 @@ data PropertyType | TObject Type | TArray Type | TEnum Type - | TProperties [PropertyKey] + | TProperties [PropertyKey] -- ^ A typed TObject, defined in a recursive manner type family ToHsType (t :: PropertyType) where ToHsType 'TNumber = Double -- in js, there are no distinct types for integers and floating-point values diff --git a/hls-plugin-api/test/Ide/PluginUtilsTest.hs b/hls-plugin-api/test/Ide/PluginUtilsTest.hs index e2be3e6c06..807d05d9c6 100644 --- a/hls-plugin-api/test/Ide/PluginUtilsTest.hs +++ b/hls-plugin-api/test/Ide/PluginUtilsTest.hs @@ -7,11 +7,15 @@ module Ide.PluginUtilsTest ) where import qualified Data.Aeson as A +import qualified Data.Aeson.Text as A import qualified Data.Aeson.Types as A +import Data.ByteString.Lazy (ByteString) import Data.Char (isPrint) import Data.Function ((&)) import qualified Data.Set as Set import qualified Data.Text as T +import qualified Data.Text.Lazy as Tl +import Debug.Trace (trace, traceM) import Ide.Plugin.Properties (KeyNamePath (..), definePropertiesProperty, defineStringProperty, @@ -24,6 +28,7 @@ import Ide.PluginUtils (extractTextInRange, unescape) import Language.LSP.Protocol.Types (Position (..), Range (Range), UInt, isSubrangeOf) import Test.Tasty +import Test.Tasty.Golden (goldenVsStringDiff) import Test.Tasty.HUnit import Test.Tasty.QuickCheck @@ -153,40 +158,46 @@ prop_rangemapListEq r xs = Set.fromList filteredList === Set.fromList filteredRangeMap +gitDiff :: FilePath -> FilePath -> [String] +gitDiff fRef fNew = ["git", "-c", "core.fileMode=false", "diff", "--no-index", "--text", "--exit-code", fRef, fNew] + +goldenGitDiff :: TestName -> FilePath -> IO ByteString -> TestTree +goldenGitDiff name = goldenVsStringDiff name gitDiff + +testDir :: FilePath +testDir = "hls-plugin-api/test/testdata/Property" + propertyTest :: TestTree propertyTest = testGroup "property api tests" [ - testCase "property toVSCodeExtensionSchema" $ do - let expect = "[(\"top.baz\",Object (fromList [(\"default\",String \"baz\"),(\"markdownDescription\",String \"baz\"),(\"scope\",String \"resource\"),(\"type\",String \"string\")])),(\"top.parent.foo\",Object (fromList [(\"default\",String \"foo\"),(\"markdownDescription\",String \"foo\"),(\"scope\",String \"resource\"),(\"type\",String \"string\")]))]" - let result = toVSCodeExtensionSchema "top." nestedPropertiesExample - show result @?= expect + goldenGitDiff "property toVSCodeExtensionSchema" (testDir <> "/NestedProperty.json") (return $ A.encode $ A.object $ toVSCodeExtensionSchema "top." nestedPropertiesExample) , testCase "property toDefaultJSON" $ do let expect = "[(\"baz\",String \"baz\"),(\"parent\",Object (fromList [(\"foo\",String \"foo\")]))]" let result = toDefaultJSON nestedPropertiesExample show result @?= expect , testCase "parsePropertyPath single key path" $ do let obj = A.object (toDefaultJSON nestedPropertiesExample) - let (Right key1) = A.parseEither (A.withObject "test parsePropertyPath" $ \o -> do - let (Right key1) = usePropertyByPathEither examplePath1 nestedPropertiesExample o + let key1 = A.parseEither (A.withObject "test parsePropertyPath" $ \o -> do + let key1 = usePropertyByPathEither examplePath1 nestedPropertiesExample o return key1) obj - key1 @?= "baz" + key1 @?= Right (Right "baz") , testCase "parsePropertyPath two key path" $ do let obj = A.object (toDefaultJSON nestedPropertiesExample) - let (Right key1) = A.parseEither (A.withObject "test parsePropertyPath" $ \o -> do - let (Right key1) = usePropertyByPathEither examplePath2 nestedPropertiesExample o + let key1 = A.parseEither (A.withObject "test parsePropertyPath" $ \o -> do + let key1 = usePropertyByPathEither examplePath2 nestedPropertiesExample o return key1) obj - key1 @?= "foo" + key1 @?= Right (Right "foo") , testCase "parsePropertyPath two key path default" $ do let obj = A.object [] - let (Right key1) = A.parseEither (A.withObject "test parsePropertyPath" $ \o -> do + let key1 = A.parseEither (A.withObject "test parsePropertyPath" $ \o -> do let key1 = usePropertyByPath examplePath2 nestedPropertiesExample o return key1) obj - key1 @?= "foo" + key1 @?= Right "foo" , testCase "parsePropertyPath two key path not default" $ do let obj = A.object (toDefaultJSON nestedPropertiesExample2) - let (Right key1) = A.parseEither (A.withObject "test parsePropertyPath" $ \o -> do - let (Right key1) = usePropertyByPathEither examplePath2 nestedPropertiesExample o + let key1 = A.parseEither (A.withObject "test parsePropertyPath" $ \o -> do + let key1 = usePropertyByPathEither examplePath2 nestedPropertiesExample o return key1) obj - key1 @?= "xxx" + key1 @?= Right (Right "xxx") ] where nestedPropertiesExample = emptyProperties diff --git a/hls-plugin-api/test/testdata/Property/NestedProperty.json b/hls-plugin-api/test/testdata/Property/NestedProperty.json new file mode 100644 index 0000000000..a51b35883f --- /dev/null +++ b/hls-plugin-api/test/testdata/Property/NestedProperty.json @@ -0,0 +1 @@ +{"top.baz":{"default":"baz","markdownDescription":"baz","scope":"resource","type":"string"},"top.parent.foo":{"default":"foo","markdownDescription":"foo","scope":"resource","type":"string"}} From 994d912f9ad5f853a24b60a37d28607346d0a25a Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 16 May 2024 16:33:10 +0800 Subject: [PATCH 24/28] clean up --- .../hls-plugin-api/test/testdata/PropertyNestedProperty.json | 1 - 1 file changed, 1 deletion(-) delete mode 100644 hls-plugin-api/hls-plugin-api/test/testdata/PropertyNestedProperty.json diff --git a/hls-plugin-api/hls-plugin-api/test/testdata/PropertyNestedProperty.json b/hls-plugin-api/hls-plugin-api/test/testdata/PropertyNestedProperty.json deleted file mode 100644 index 33d84fc425..0000000000 --- a/hls-plugin-api/hls-plugin-api/test/testdata/PropertyNestedProperty.json +++ /dev/null @@ -1 +0,0 @@ -{"top.baz":{"default":"baz","markdownDescription":"baz","scope":"resource","type":"string"},"top.parent.foo":{"default":"foo","markdownDescription":"foo","scope":"resource","type":"string"}} \ No newline at end of file From 638559235f962cb66745a266ff9dbadb37cf1219 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 16 May 2024 16:37:03 +0800 Subject: [PATCH 25/28] clean up --- hls-plugin-api/test/Ide/PluginUtilsTest.hs | 2 +- hls-plugin-api/test/testdata/Property/NestedProperty.json | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 hls-plugin-api/test/testdata/Property/NestedProperty.json diff --git a/hls-plugin-api/test/Ide/PluginUtilsTest.hs b/hls-plugin-api/test/Ide/PluginUtilsTest.hs index 807d05d9c6..1ccee883ec 100644 --- a/hls-plugin-api/test/Ide/PluginUtilsTest.hs +++ b/hls-plugin-api/test/Ide/PluginUtilsTest.hs @@ -201,7 +201,7 @@ propertyTest = testGroup "property api tests" [ ] where nestedPropertiesExample = emptyProperties - & definePropertiesProperty #parent "parent" (emptyProperties & defineStringProperty #foo "foo" "foo") + & definePropertiesProperty #parent "parent" (emptyProperties & defineStringProperty #foo "foo" "foo" & defineStringProperty #boo "boo" "boo") & defineStringProperty #baz "baz" "baz" nestedPropertiesExample2 = emptyProperties diff --git a/hls-plugin-api/test/testdata/Property/NestedProperty.json b/hls-plugin-api/test/testdata/Property/NestedProperty.json deleted file mode 100644 index a51b35883f..0000000000 --- a/hls-plugin-api/test/testdata/Property/NestedProperty.json +++ /dev/null @@ -1 +0,0 @@ -{"top.baz":{"default":"baz","markdownDescription":"baz","scope":"resource","type":"string"},"top.parent.foo":{"default":"foo","markdownDescription":"foo","scope":"resource","type":"string"}} From 053bea8fd1df983a6dd7ec4e02fac794db0da6ca Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 16 May 2024 16:44:25 +0800 Subject: [PATCH 26/28] fix golden test --- .../testdata/Property/NestedProperty.json | 1 - hls-plugin-api/test/Ide/PluginUtilsTest.hs | 4 ++-- .../testdata/Property/NestedProperty.json | 20 +++++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) delete mode 100644 hls-plugin-api/hls-plugin-api/test/testdata/Property/NestedProperty.json create mode 100644 hls-plugin-api/test/testdata/Property/NestedProperty.json diff --git a/hls-plugin-api/hls-plugin-api/test/testdata/Property/NestedProperty.json b/hls-plugin-api/hls-plugin-api/test/testdata/Property/NestedProperty.json deleted file mode 100644 index 33d84fc425..0000000000 --- a/hls-plugin-api/hls-plugin-api/test/testdata/Property/NestedProperty.json +++ /dev/null @@ -1 +0,0 @@ -{"top.baz":{"default":"baz","markdownDescription":"baz","scope":"resource","type":"string"},"top.parent.foo":{"default":"foo","markdownDescription":"foo","scope":"resource","type":"string"}} \ No newline at end of file diff --git a/hls-plugin-api/test/Ide/PluginUtilsTest.hs b/hls-plugin-api/test/Ide/PluginUtilsTest.hs index 1ccee883ec..d7d7ecc542 100644 --- a/hls-plugin-api/test/Ide/PluginUtilsTest.hs +++ b/hls-plugin-api/test/Ide/PluginUtilsTest.hs @@ -159,13 +159,13 @@ prop_rangemapListEq r xs = gitDiff :: FilePath -> FilePath -> [String] -gitDiff fRef fNew = ["git", "-c", "core.fileMode=false", "diff", "--no-index", "--text", "--exit-code", fRef, fNew] +gitDiff fRef fNew = ["git", "-c", "core.fileMode=false", "diff", "-w", "--no-index", "--text", "--exit-code", fRef, fNew] goldenGitDiff :: TestName -> FilePath -> IO ByteString -> TestTree goldenGitDiff name = goldenVsStringDiff name gitDiff testDir :: FilePath -testDir = "hls-plugin-api/test/testdata/Property" +testDir = "test/testdata/Property" propertyTest :: TestTree propertyTest = testGroup "property api tests" [ diff --git a/hls-plugin-api/test/testdata/Property/NestedProperty.json b/hls-plugin-api/test/testdata/Property/NestedProperty.json new file mode 100644 index 0000000000..95f2b7af74 --- /dev/null +++ b/hls-plugin-api/test/testdata/Property/NestedProperty.json @@ -0,0 +1,20 @@ +{ + "top.baz": { + "default": "baz", + "markdownDescription": "baz", + "scope": "resource", + "type": "string" + }, + "top.parent.boo": { + "default": "boo", + "markdownDescription": "boo", + "scope": "resource", + "type": "string" + }, + "top.parent.foo": { + "default": "foo", + "markdownDescription": "foo", + "scope": "resource", + "type": "string" + } +} From 2a044137cd92ad6d793d15de4d7ea6663106e338 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 16 May 2024 16:45:01 +0800 Subject: [PATCH 27/28] fix golden test --- .../testdata/Property/NestedProperty.json | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/hls-plugin-api/test/testdata/Property/NestedProperty.json b/hls-plugin-api/test/testdata/Property/NestedProperty.json index 95f2b7af74..4c9e721c4d 100644 --- a/hls-plugin-api/test/testdata/Property/NestedProperty.json +++ b/hls-plugin-api/test/testdata/Property/NestedProperty.json @@ -1,20 +1 @@ -{ - "top.baz": { - "default": "baz", - "markdownDescription": "baz", - "scope": "resource", - "type": "string" - }, - "top.parent.boo": { - "default": "boo", - "markdownDescription": "boo", - "scope": "resource", - "type": "string" - }, - "top.parent.foo": { - "default": "foo", - "markdownDescription": "foo", - "scope": "resource", - "type": "string" - } -} +{"top.baz":{"default":"baz","markdownDescription":"baz","scope":"resource","type":"string"},"top.parent.boo":{"default":"boo","markdownDescription":"boo","scope":"resource","type":"string"},"top.parent.foo":{"default":"foo","markdownDescription":"foo","scope":"resource","type":"string"}} From 7d7322a1decf876d61e8b9362d7568e8dc3483db Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 16 May 2024 22:24:23 +0800 Subject: [PATCH 28/28] add more golden test --- hls-plugin-api/test/Ide/PluginUtilsTest.hs | 7 ++----- .../test/testdata/Property/NestedPropertyDefault.json | 1 + .../{NestedProperty.json => NestedPropertyVscode.json} | 0 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 hls-plugin-api/test/testdata/Property/NestedPropertyDefault.json rename hls-plugin-api/test/testdata/Property/{NestedProperty.json => NestedPropertyVscode.json} (100%) diff --git a/hls-plugin-api/test/Ide/PluginUtilsTest.hs b/hls-plugin-api/test/Ide/PluginUtilsTest.hs index d7d7ecc542..9d49ac276d 100644 --- a/hls-plugin-api/test/Ide/PluginUtilsTest.hs +++ b/hls-plugin-api/test/Ide/PluginUtilsTest.hs @@ -169,11 +169,8 @@ testDir = "test/testdata/Property" propertyTest :: TestTree propertyTest = testGroup "property api tests" [ - goldenGitDiff "property toVSCodeExtensionSchema" (testDir <> "/NestedProperty.json") (return $ A.encode $ A.object $ toVSCodeExtensionSchema "top." nestedPropertiesExample) - , testCase "property toDefaultJSON" $ do - let expect = "[(\"baz\",String \"baz\"),(\"parent\",Object (fromList [(\"foo\",String \"foo\")]))]" - let result = toDefaultJSON nestedPropertiesExample - show result @?= expect + goldenGitDiff "property toVSCodeExtensionSchema" (testDir <> "/NestedPropertyVscode.json") (return $ A.encode $ A.object $ toVSCodeExtensionSchema "top." nestedPropertiesExample) + , goldenGitDiff "property toDefaultJSON" (testDir <> "/NestedPropertyDefault.json") (return $ A.encode $ A.object $ toDefaultJSON nestedPropertiesExample) , testCase "parsePropertyPath single key path" $ do let obj = A.object (toDefaultJSON nestedPropertiesExample) let key1 = A.parseEither (A.withObject "test parsePropertyPath" $ \o -> do diff --git a/hls-plugin-api/test/testdata/Property/NestedPropertyDefault.json b/hls-plugin-api/test/testdata/Property/NestedPropertyDefault.json new file mode 100644 index 0000000000..0d8f57656c --- /dev/null +++ b/hls-plugin-api/test/testdata/Property/NestedPropertyDefault.json @@ -0,0 +1 @@ +{"baz":"baz","parent":{"boo":"boo","foo":"foo"}} \ No newline at end of file diff --git a/hls-plugin-api/test/testdata/Property/NestedProperty.json b/hls-plugin-api/test/testdata/Property/NestedPropertyVscode.json similarity index 100% rename from hls-plugin-api/test/testdata/Property/NestedProperty.json rename to hls-plugin-api/test/testdata/Property/NestedPropertyVscode.json