diff --git a/src/ReadDTS.js b/src/ReadDTS.js index 73c6516..2a76573 100644 --- a/src/ReadDTS.js +++ b/src/ReadDTS.js @@ -112,6 +112,24 @@ function _readDTS(options, visit, file, either) { }); } } + else if (ts.isModuleDeclaration(node)) { + var moduleType = checker.getTypeAtLocation(node); + var declarations_1 = []; + // let m = checker.getSymbolAtLocation(moduleType); + ts.forEachChild(node, function (d) { + if (ts.isModuleBlock(d)) { + d.statements.forEach(function (s) { + // XXX: isNodeExported fails in case of ambient modules - why? + // if (isNodeExported(checker, d)) { + declarations_1.push(visitDeclaration(s)); + }); + } + }); + return onDeclaration.module({ + fullyQualifiedName: checker.getFullyQualifiedName(moduleType.symbol), + declarations: declarations_1 + }); + } var nodeType = checker.getTypeAtLocation(node); var fullyQualifiedName = null; try { diff --git a/src/ReadDTS.purs b/src/ReadDTS.purs index dfe4838..c835957 100644 --- a/src/ReadDTS.purs +++ b/src/ReadDTS.purs @@ -60,6 +60,11 @@ type OnDeclarationBase (nullable ∷ Type → Type) d t = , properties ∷ Array (Property t) } → d + , module ∷ + { fullyQualifiedName ∷ FullyQualifiedName + , declarations ∷ Array d + } + → d , typeAlias ∷ { name ∷ String , typeParameters ∷ Array (TypeParameter nullable t) diff --git a/src/ReadDTS.ts b/src/ReadDTS.ts index bc05044..cc2f147 100644 --- a/src/ReadDTS.ts +++ b/src/ReadDTS.ts @@ -22,6 +22,8 @@ export function _readDTS( options: { strictNullChecks: boolean }, visit: { onDeclaration: { + // can we return fqn + function: (x: { fullyQualifiedName: string | undefined, returnType: t, parameters: { type: t, name: string }[] }) => d interface: (x: { name: string, @@ -29,9 +31,8 @@ export function _readDTS( properties: Property[] typeParameters: TypeParameter[] }) => d + module: (x: { fullyQualifiedName: string, declarations: d[] }) => d typeAlias: (x: { name: string, type: t, typeParameters: TypeParameter[] }) => d - // can we return fqn - function: (x: { fullyQualifiedName: string | undefined, returnType: t, parameters: { type: t, name: string }[] }) => d unknown: (u: { fullyQualifiedName: Nullable, msg: string }) => d }, onTypeNode: { @@ -172,6 +173,23 @@ export function _readDTS( returnType: getTSType(signature.getReturnType()) }) } + } else if(ts.isModuleDeclaration(node)) { + let moduleType = checker.getTypeAtLocation(node) + let declarations:d[] = []; + // let m = checker.getSymbolAtLocation(moduleType); + ts.forEachChild(node, function(d){ + if(ts.isModuleBlock(d)) { + d.statements.forEach(function(s) { + // XXX: isNodeExported fails in case of ambient modules - why? + // if (isNodeExported(checker, d)) { + declarations.push(visitDeclaration(s)); + }); + } + }) + return onDeclaration.module({ + fullyQualifiedName: checker.getFullyQualifiedName(moduleType.symbol), + declarations: declarations + }); } let nodeType = checker.getTypeAtLocation(node); diff --git a/src/ReadDTS/AST.purs b/src/ReadDTS/AST.purs index c38aaa7..8bad775 100644 --- a/src/ReadDTS/AST.purs +++ b/src/ReadDTS/AST.purs @@ -14,8 +14,8 @@ import Data.Newtype (class Newtype) import Data.Traversable (class Traversable, for, sequence, traverse, traverseDefault) import Effect (Effect) import Matryoshka (CoalgebraM, anaM) +import ReadDTS (CompilerOptions, FullyQualifiedName, TsDeclaration, Visit, readDTS, unsafeTsStringToString) import ReadDTS (File, unsafeTsStringToString) as ReadDTS -import ReadDTS (FullyQualifiedName, TsDeclaration, Visit, CompilerOptions, readDTS, unsafeTsStringToString) import Type.Prelude (SProxy(..)) type Property ref = @@ -82,6 +82,10 @@ data TypeConstructor ref , properties ∷ Array (Property ref) , typeParameters ∷ Array (TypeParameter ref) } + | Module + { declarations ∷ Array (TypeConstructor ref) + , fullyQualifiedName ∷ FullyQualifiedName + } | TypeAlias { name ∷ String , type ∷ TypeNode ref @@ -101,6 +105,7 @@ instance foldableTypeConstructor ∷ Foldable TypeConstructor where foldMap f (Interface i) = foldMap (foldMap f <<< _.type) i.properties <> foldMap (foldMap (foldMap f) <<< _.default) i.typeParameters + foldMap f (Module m) = foldMap (foldMap f) m.declarations foldMap f (TypeAlias ta) = foldMap f ta.type <> foldMap (foldMap (foldMap f) <<< _.default) ta.typeParameters @@ -120,6 +125,8 @@ instance traversableTypeConstructor ∷ Traversable TypeConstructor where $ (\ms tp → i { properties = ms, typeParameters = tp }) <$> (sequence <<< map sequenceProperty) i.properties <*> (sequence <<< map sequenceTypeParameter) i.typeParameters + sequence (Module { declarations, fullyQualifiedName}) = + Module <<< { declarations: _, fullyQualifiedName } <$> sequence (map sequence declarations) sequence (TypeAlias ta) = map TypeAlias $ { type: _, typeParameters: _, name: ta.name } <$> sequence ta.type <*> (sequence <<< map sequenceTypeParameter) ta.typeParameters @@ -246,6 +253,7 @@ visit = { onDeclaration: { function: FunctionSignature , interface: Interface <<< over (_typeParametersL <<< traversed <<< _nameL) unsafeTsStringToString + , module: Module , typeAlias: TypeAlias <<< over (_typeParametersL <<< traversed <<< _nameL) unsafeTsStringToString , unknown: UnknownTypeConstructor } diff --git a/src/ReadDTS/Instantiation.purs b/src/ReadDTS/Instantiation.purs index 83911e5..e0cb3e5 100644 --- a/src/ReadDTS/Instantiation.purs +++ b/src/ReadDTS/Instantiation.purs @@ -18,7 +18,7 @@ import Data.Traversable (class Traversable, for, sequence, traverse, traverseDef import Data.Tuple (Tuple(..)) as Tuple import Matryoshka (Algebra, cata) import ReadDTS (FullyQualifiedName, fqnToString) -import ReadDTS.AST (Application(..), Application', TypeConstructor(..), TypeNode) +import ReadDTS.AST (Application(..), Application', TypeConstructor, TypeNode) import ReadDTS.AST as AST type Property a = { type ∷ a, optional ∷ Boolean } @@ -42,6 +42,10 @@ data TypeF a | Null | Number | NumberLiteral Number + | Module + { fullyQualifiedName ∷ String + , types ∷ Array a + } | Object String (Map String (Property a)) | String | StringLiteral String @@ -67,6 +71,7 @@ instance eq1TypeF ∷ Eq1 TypeF where eq1 Null Null = true eq1 Number Number = true eq1 (NumberLiteral n1) (NumberLiteral n2) = n1 == n2 + eq1 (Module m1) (Module m2) = m1.fullyQualifiedName == m2.fullyQualifiedName eq1 (Object n1 props1) (Object n2 props2) = n1 == n2 && props1 == props2 eq1 String String = true eq1 (StringLiteral s1) (StringLiteral s2) = s1 == s2 @@ -84,6 +89,7 @@ instance foldableTypeF ∷ Foldable TypeF where foldMap _ (BooleanLiteral _) = mempty foldMap f (Function r) = foldMap (f <<< _.type) r.parameters <> f r.returnType foldMap f (Intersection t1 t2) = f t1 <> f t2 + foldMap f (Module m) = foldMap f m.types foldMap _ Null = mempty foldMap f Number = mempty foldMap _ (NumberLiteral _) = mempty @@ -112,6 +118,8 @@ instance traversableTypeF ∷ Traversable TypeF where where sequenceParameter { name, type: t } = { name, type: _ } <$> t sequence (Intersection t1 t2) = Intersection <$> t1 <*> t2 + sequence (Module { fullyQualifiedName, types }) = + Module <<< { types: _, fullyQualifiedName } <$> sequence types sequence Null = pure Null sequence Number = pure $ Number sequence (NumberLiteral n) = pure $ NumberLiteral n @@ -133,22 +141,25 @@ type Instantiate = Map String Type → Except String Type instantiateApplication ∷ Algebra Application Instantiate instantiateApplication (Application { typeArguments, typeConstructor }) = case typeConstructor of - FunctionSignature fd@{ fullyQualifiedName } → \ctx → do + AST.FunctionSignature fd@{ fullyQualifiedName } → \ctx → do parameters ← traverse (\r@{ name } → { name, type: _ } <$> instantiateTypeNode r.type ctx) fd.parameters returnType ← instantiateTypeNode fd.returnType ctx pure $ roll $ Function { fullyQualifiedName, parameters, returnType } - Interface { fullyQualifiedName, properties, typeParameters } → \ctx → do + AST.Interface { fullyQualifiedName, properties, typeParameters } → \ctx → do typeArguments' ← traverse (flip instantiateTypeNode ctx) typeArguments let ctx' = Map.fromFoldable (Array.zip (map _.name typeParameters) typeArguments') roll <$> Object (fqnToString fullyQualifiedName) <<< Map.fromFoldable <$> for properties \{ name, type: t, optional } → (Tuple.Tuple name <<< { type: _, optional }) <$> instantiateTypeNode t ctx' - TypeAlias { type: t, typeParameters } → \ctx → do + AST.Module { declarations, fullyQualifiedName } → \ctx → do + roll <<< Module <<< { fullyQualifiedName: fqnToString fullyQualifiedName, types: _ } <$> for declarations \tc → do + instantiateApplication (Application { typeArguments: [], typeConstructor: tc }) ctx + AST.TypeAlias { type: t, typeParameters } → \ctx → do typeArguments' ← traverse (flip instantiateTypeNode ctx) typeArguments let ctx' = Map.fromFoldable (Array.zip (map _.name typeParameters) typeArguments') instantiateTypeNode t ctx' - UnknownTypeConstructor r → const $ pure $ roll $ Unknown + AST.UnknownTypeConstructor r → const $ pure $ roll $ Unknown ("Unknown type constructor: " <> show r.fullyQualifiedName) intersection ∷ Type → Type → Type diff --git a/src/ReadDTS/Instantiation/Pretty.purs b/src/ReadDTS/Instantiation/Pretty.purs index a5e4126..d42ea35 100644 --- a/src/ReadDTS/Instantiation/Pretty.purs +++ b/src/ReadDTS/Instantiation/Pretty.purs @@ -43,6 +43,7 @@ pprint = render <<< cata alg , returnType ] alg (Intersection t1 t2) = hcat [ t1, text " & ", t2 ] + alg (Module m) = hcat $ [ text "Module", text m.fullyQualifiedName ] <> m.types alg Null = text "null" alg Number = text "number" alg (Object _ props) | length props == 0 = text "{}" @@ -76,6 +77,7 @@ pprintTypeName (Array t) = "[ " <> t <> "]" pprintTypeName (Boolean) = "boolean" pprintTypeName (Function r) = "function:" <> r.fullyQualifiedName pprintTypeName (Intersection t1 t2) = "intersection: " <> t1 <> " & " <> t2 +pprintTypeName (Module m) = "module" <> m.fullyQualifiedName pprintTypeName Null = "null" pprintTypeName Number = "number" pprintTypeName (NumberLiteral n) = "@" <> show n diff --git a/test/Main.purs b/test/Main.purs index aea08af..92c93d3 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -2,11 +2,71 @@ module Test.Main where import Prelude +import Control.Comonad (extract) +import Control.Monad.Except (runExceptT) +import Data.Either (Either(..)) +import Data.Foldable (traverse_) +import Data.Maybe (Maybe(..)) +import Data.Traversable (traverse) import Effect (Effect) +import ReadDTS.AST (build) +import ReadDTS.Instantiation (instantiate) as I +import ReadDTS.Instantiation.Pretty (pprint) import Test.ReadDTS.Instantiation (suite) as Test.ReadDTS.Instantiation +import Test.Unit.Console (print) import Test.Unit.Main (runTest) as Test +-- main :: Effect Unit +-- main = do +-- Test.runTest Test.ReadDTS.Instantiation.suite + +source = """ +declare module 'vscode' { + + // export const version: string; + + // /** + // * Represents a reference to a command. Provides a title which + // * will be used to represent a command in the UI and, optionally, + // * an array of arguments which will be passed to the command handler + // * function when invoked. + // */ + export interface Command { + /** + * Title of the command, like `save`. + */ + title: string; + + /** + * The identifier of the actual command handler. + * @see [commands.registerCommand](#commands.registerCommand). + */ + command: string; + + /** + * A tooltip for the command, when represented in the UI. + */ + tooltip?: string; + + /** + * Arguments that the command handler should be + * invoked with. + */ + arguments?: any[]; + } +} +""" + main :: Effect Unit main = do - Test.runTest Test.ReadDTS.Instantiation.suite + -- Test.runTest Test.ReadDTS.Instantiation.suite + e <- build { strictNullChecks: true } { path: "test/vscode.d.ts", source: Just source } + case e of + Right r -> do + let + mts = extract $ runExceptT $ traverse (\tc -> I.instantiate tc []) r + case mts of + Right ts -> traverse_ print $ map pprint ts + Left e -> print $ show e + Left e -> print $ show e diff --git a/test/ReadDTS/AST.purs b/test/ReadDTS/AST.purs index 5d24a95..a18e02e 100644 --- a/test/ReadDTS/AST.purs +++ b/test/ReadDTS/AST.purs @@ -54,6 +54,12 @@ stringOnDeclaration = (foldMap (foldMap _.tsDeclarations <<< _.default)) i.typeParameters <> foldMap _.type.tsDeclarations i.properties } + -- | TODO: Fix printing of module here + , module: \i → DeclarationRepr + { fullyQualifiedName: Nothing -- Just i.fullyQualifiedName + , repr: serModule i + , tsDeclarations: [] + } -- | It seems that type aliases don't introduce names so they don't -- | have fullyQualifiedName... but of course I can be wrong. , typeAlias: \r@{ type: t, typeParameters } → DeclarationRepr @@ -78,6 +84,10 @@ serTypeAlias r <> " <" <> joinWith ", " (map (unsafeTsStringToString <<< _.name) r.typeParameters) <> "> : " <> r.type.repr +serModule { fullyQualifiedName } + = "module " + <> show fullyQualifiedName + serInterface { name, fullyQualifiedName, properties, typeParameters } = "interface " <> show fullyQualifiedName