diff --git a/spago.dhall b/spago.dhall index a840895..996896c 100644 --- a/spago.dhall +++ b/spago.dhall @@ -7,19 +7,16 @@ You can edit this file as you like. [ "aff" , "arrays" , "bifunctors" - , "console" - , "contravariant" , "control" - , "debug" , "effect" , "either" , "exceptions" , "fixed-points" , "foldable-traversable" - , "foreign" + , "foreign-object" , "functions" + , "identity" , "integers" - , "js-unsafe-stringify" , "lists" , "matryoshka" , "maybe" @@ -28,7 +25,6 @@ You can edit this file as you like. , "node-buffer" , "node-fs" , "node-path" - , "nonempty" , "nullable" , "ordered-collections" , "partial" diff --git a/src/ReadDTS.js b/src/ReadDTS.js deleted file mode 100644 index 4ea2916..0000000 --- a/src/ReadDTS.js +++ /dev/null @@ -1,358 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports._readTypes = void 0; -var ts = __importStar(require("typescript")); -var formatHost = { - getCanonicalFileName: function (path) { return path; }, - getCurrentDirectory: ts.sys.getCurrentDirectory, - getNewLine: function () { return ts.sys.newLine; } -}; -var log = function (msg) { console.log(msg); }; -function _readTypes(_a) { - var options = _a.options, visit = _a.visit, rootNames = _a.rootNames, inMemoryFiles = _a.inMemoryFiles, compilerHost = _a.compilerHost, either = _a.either; - log = options.debug ? log : (function (msg) { msg; }); - var program = createProgram(rootNames, inMemoryFiles, compilerHost); - var checker = program.getTypeChecker(); - var onDeclaration = visit.onDeclaration; - var onTypeNode = visit.onTypeNode; - var declarations = []; - if (options.compile) { - var emitResult = program.emit(); - if (emitResult.emitSkipped) { - var allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics); - var errors_1 = []; - allDiagnostics.forEach(function (d) { - if (d.category === ts.DiagnosticCategory.Error) { - errors_1.push(ts.formatDiagnostic(d, formatHost)); - } - }); - if (errors_1.length > 0) { - return either.left(errors_1); - } - } - } - var _loop_1 = function (sourceFile) { - if (!(rootNames.some(function (n) { return sourceFile.fileName === n; }))) - return "continue"; - if (!options.compile && sourceFile !== undefined) { - var x = program.getSyntacticDiagnostics(sourceFile); - var errors_2 = []; - x.forEach(function (d) { - if (d.category === ts.DiagnosticCategory.Error) { - errors_2.push(ts.formatDiagnostic(d, formatHost)); - } - }); - if (errors_2.length > 0) { - return { value: either.left(errors_2) }; - } - } - try { - ts.forEachChild(sourceFile, function (d) { - // if (isNodeExported(check//App/appcler, d)) - declarations.push(visitDeclaration(d)); - }); - } - catch (e) { - return { value: either.left(e) }; - } - }; - for (var _i = 0, _b = program.getSourceFiles(); _i < _b.length; _i++) { - var sourceFile = _b[_i]; - var state_1 = _loop_1(sourceFile); - if (typeof state_1 === "object") - return state_1.value; - } - return either.right({ - topLevel: declarations, - readDeclaration: function (v) { return function () { return visitDeclaration(v); }; } - }); - function processTypeParameters(typeParameters) { - return (!typeParameters) ? [] : typeParameters.map(function (p) { - var d = p.default ? getTSType(checker.getTypeAtLocation(p.default)) : null; - return { name: p.name.escapedText, default: d }; - }); - } - ; - function visitDeclaration(node) { - var t, fqn; - var typeNode = checker.getTypeAtLocation(node); - if (ts.isInterfaceDeclaration(node)) { - var props = typeNode.getProperties().map(function (sym) { return property(sym, node); }); - var body = onTypeNode.interface(props); - var params = processTypeParameters(node.typeParameters); - t = onTypeNode.parametric({ body: body, params: params }); - fqn = checker.getFullyQualifiedName(typeNode.symbol); - // } else if(ts.isClassDeclaration(node) && node.name) { - // let props = typeNode.getProperties().map((sym: ts.Symbol) => property(sym, node)); - // let body = onTypeNode.class(props); - // let params = processTypeParameters(node.typeParameters) - // t = onTypeNode.parametric({ body, params }) - // fqn = checker.getFullyQualifiedName(typeNode.symbol); - } - else if (ts.isTypeAliasDeclaration(node)) { - // let AllMeanings = ts.SymbolFlags.Value | ts.SymbolFlags.Type | ts.SymbolFlags.Namespace | ts.SymbolFlags.Alias; - // let node2:number = checker.getTypeAtLocation(node); - // TODO: would this work in the case of external modules? - var symbol = checker.getSymbolAtLocation(node.name); - log("Alias symbol:"); - log(symbol); - // log("Alias symbol is undefined:"); - // log(symbol == undefined); - // log("typeNode:"); - // log(typeNode); - // log("fqn of symbol:") - // log(symbol?checker.getFullyQualifiedName(symbol):"WTF");//node.name.text; - // log("fqn of exported symbol:") - // log(symbol?checker.getFullyQualifiedName(checker.getExportSymbolOfSymbol(symbol)):"WTF");//node.name.text; - t = getTSType(typeNode); - fqn = symbol ? checker.getFullyQualifiedName(checker.getExportSymbolOfSymbol(symbol)) : "WTF"; //node.name.text; - // } else if(ts.isModuleDeclaration(node)) { - // log("Module declaration found:" + node.name); - // // let moduleType = checker.getTypeAtLocation(node.name) - // let declarations:d[] = []; - // // let m = checker.getSymbolAtLocation(moduleType); - // console.log("Iterating module: " + node.name) - // 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)); - // }); - // } - // }) - // let symbol = node.name?checker.getSymbolAtLocation(node.name):undefined; - // let fqn = symbol?checker.getFullyQualifiedName(symbol):undefined; - // if(fqn) { - // return onDeclaration.module_({ - // fqn, - // declarations - // }); - // } - // } - //} else if(ts.isMethodDeclaration(node)) { - // let signature = checker.getSignatureFromDeclaration(node); - } - if (t && fqn) { - return onDeclaration(fqn)(t); - } - if (typeNode) { - try { - fqn = checker.getFullyQualifiedName(typeNode.symbol); - } - catch (e) { - throw (["Unable to resolve node: " + fqn]); - } - } - throw (["Unable to resolve node: " + node]); - } - function property(sym, dec) { - var optional = (sym.flags & ts.SymbolFlags.Optional) == ts.SymbolFlags.Optional; - var memType = dec ? checker.getTypeOfSymbolAtLocation(sym, dec) : checker.getDeclaredTypeOfSymbol(sym); - var t = getTSType(memType, true); - return { name: sym.name, type: t, optional: optional }; - } - function getMemberTSType(memType) { - return getTSType(memType, true); - } - function getTSType(memType, member) { - var _a; - if (member === void 0) { member = false; } - if (memType.isLiteral()) { - memType.isStringLiteral(); - } - if (memType.aliasSymbol && member) { - var s = memType.aliasSymbol; - var fqn = checker.getFullyQualifiedName(s); - var params = ((_a = memType.aliasTypeArguments) === null || _a === void 0 ? void 0 : _a.map(getMemberTSType)) || []; - var ref = (s && s.valueDeclaration) ? s.valueDeclaration : (s && s.declarations && s.declarations.length === 1) ? s.declarations[0] : null; - if (ref) { - var t = onTypeNode.ref({ fqn: fqn, ref: ref }); - if (params) { - t = onTypeNode.application({ params: params, t: t }); - } - return t; - } - } - else if (memType.isStringLiteral()) { - return onTypeNode.stringLiteral(memType.value); - } - else if (memType.isNumberLiteral()) { - return onTypeNode.numberLiteral(memType.value); - } - else if ((memType.flags & ts.TypeFlags.BooleanLiteral) && - (memType.intrinsicName == "true" || - memType.intrinsicName == "false")) { - if (memType.intrinsicName == "true") { - return onTypeNode.booleanLiteral(true); - } - else { - return onTypeNode.booleanLiteral(false); - } - } - else if (memType.isUnion()) { - var types = memType.types.map(function (t) { - return getMemberTSType(t); - }); - return onTypeNode.union(types); - } - else if (memType.isIntersection()) { - var types = memType.types.map(getMemberTSType); - return onTypeNode.intersection(types); - } - else if (memType.flags & ts.TypeFlags.Any) { - return onTypeNode.any; - // } else if (memType.flags & (ts.TypeFlags.String - // | ts.TypeFlags.BooleanLike | ts.TypeFlags.Number - // | ts.TypeFlags.Null | ts.TypeFlags.VoidLike | ts.TypeFlags.Any)) { - // // XXX: I haven't found any other way to access - // // BooleanLiteral value... - // return onTypeNode.primitive(checker.typeToString(memType)); - } - else if (memType.flags & (ts.TypeFlags.Object | ts.TypeFlags.NonPrimitive)) { - var memObjectType = memType; - // let onInterfaceReference = function(target: ts.InterfaceType, typeArguments: t[]) { - // let ref = (target.symbol && target.symbol.valueDeclaration) - // ?target.symbol.valueDeclaration - // :(target.symbol && target.symbol.declarations.length === 1) - // ?target.symbol.declarations[0] - // :null; - // let fqn = checker.getFullyQualifiedName(target.symbol); - // return ref - // ?onTypeNode.typeReference({typeArguments, fqn, ref}) - // :onTypeNode.unknown("Unable to get type declaration for:" + fqn + "<" + typeArguments + ">") - // } - // if(memObjectType.objectFlags & ts.ObjectFlags.Reference) { - // let reference = memObjectType; - // if(checker.isArrayType(reference)) { - // log("Trying array") - // let elem = checker.getElementTypeOfArrayType(reference); - // if(elem) - // return onTypeNode.array(getMemberTSType(elem)); - // } - // if(checker.isTupleType(reference)) { - // log("Tuple") - // let e: string, elem:ts.Type | undefined, elems:t[] = []; - // let MAX_TUPLE_LENGTH = 20; - // for(let i=0; i and Record<..> - // } else if((memObjectType.objectFlags & ts.ObjectFlags.Mapped) && - // (memObjectType.objectFlags & ts.ObjectFlags.Instantiated)) { - // let objDeclarations = memObjectType.symbol.getDeclarations(); - // let props = memObjectType.getProperties().map((sym: ts.Symbol) => - // property(sym, objDeclarations?objDeclarations[0]:sym.declarations?sym.declarations[1]:sym.valueDeclaration) - // ); - // return onTypeNode.object(props); - // } - // } else - if (memObjectType.objectFlags & ts.ObjectFlags.Anonymous) { - // log("Anonymous") - // // TODO: Currently any object which is "callable" is interpreted - // // as a plain function - // let signature = memObjectType.getCallSignatures()[0]; - // if(signature) { - // log("Treating this as function: " + memObjectType.symbol.getName()); - // let functionType = { - // parameters: signature.parameters.map((parameterSymbol) => { - // return { - // name: parameterSymbol.getName(), - // type: getMemberTSType( - // checker.getTypeOfSymbolAtLocation(parameterSymbol, parameterSymbol?.valueDeclaration), - // ) - // }; - // }), - // returnType: getMemberTSType(signature.getReturnType()) - // }; - // log("Returning funciton type:" + functionType); - // return onTypeNode.function(functionType); - // } - var props = memObjectType.getProperties().map(function (sym) { return property(sym, sym.valueDeclaration); }); - return onTypeNode.object(props); - } - // return onTypeNode.unknown("Uknown object type node (flags = " + memObjectType.objectFlags + "):" + checker.typeToString(memObjectType)); - // } - // else if (memType.isTypeParameter()) { - // log("Type parameter?") - // let d = memType.getDefault(); - // return onTypeNode.typeParameter({ name: memType.symbol.escapedName, default: d?getTSType(d):null }); - } - throw (["Unable to process type: " + checker.typeToString(memType)]); - } -} -exports._readTypes = _readTypes; -// https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API#using-the-type-checker -function isNodeExported(checker, node) { - var sym = checker.getSymbolAtLocation(node); - return (sym ? ((ts.getCombinedModifierFlags(sym.valueDeclaration) & ts.ModifierFlags.Export) !== 0) : false || - (!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile && node.kind !== ts.SyntaxKind.EndOfFileToken)); -} -; -// https://stackoverflow.com/questions/53733138/how-do-i-type-check-a-snippet-of-typescript-code-in-memory -function createProgram(rootNames, inMemoryFiles, realHost) { - var host = realHost; - if (inMemoryFiles) { - var paths_1 = inMemoryFiles.map(function (m) { return m.path; }); - var sourceFiles_1 = inMemoryFiles.map(function (m) { return { module: m, file: ts.createSourceFile(m.path, m.source, ts.ScriptTarget.ES5, true) }; }); - host = { - fileExists: function (filePath) { return paths_1.some(function (p) { return filePath == p; }) || realHost.fileExists(filePath); }, - directoryExists: realHost.directoryExists && realHost.directoryExists.bind(realHost), - getCurrentDirectory: realHost.getCurrentDirectory.bind(realHost), - getDirectories: realHost.getDirectories ? realHost.getDirectories.bind(realHost) : undefined, - getCanonicalFileName: function (fileName) { return realHost.getCanonicalFileName(fileName); }, - getNewLine: realHost.getNewLine.bind(realHost), - getDefaultLibFileName: realHost.getDefaultLibFileName.bind(realHost), - getSourceFile: function (fileName, languageVersion, onError, shouldCreateNewSourceFile) { - var m = sourceFiles_1.find(function (f) { return f.module.path == fileName; }); - return m ? m.file : realHost.getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile); - }, - readFile: function (fileName) { - var f = sourceFiles_1.find(function (f) { return f.module.path == fileName; }); - return f ? f.module.source : realHost.readFile(fileName); - }, - useCaseSensitiveFileNames: function () { return realHost.useCaseSensitiveFileNames(); }, - writeFile: function (_, data) { data; }, - }; - } - var options = {}; - return ts.createProgram(rootNames, options, host); -} -//# sourceMappingURL=ReadDTS.js.map \ No newline at end of file diff --git a/src/ReadDTS.purs b/src/ReadDTS.purs index dcd3b85..bf26b07 100644 --- a/src/ReadDTS.purs +++ b/src/ReadDTS.purs @@ -2,54 +2,36 @@ module ReadDTS where import Prelude -import Control.Alt ((<|>)) import Data.Array (catMaybes, elem, filter, uncons) as Array -import Data.Array (length, null) import Data.Array.NonEmpty (NonEmptyArray) import Data.Array.NonEmpty (fromArray) as Array.NonEmpty import Data.Foldable (foldMap) import Data.Int.Bits ((.&.)) import Data.Map (Map) import Data.Map (fromFoldable) as Map -import Data.Maybe (Maybe(..), fromMaybe, isNothing) -import Data.Monoid (guard) as Monoid +import Data.Maybe (Maybe(..), fromMaybe, isJust, isNothing) import Data.Traversable (sequence, traverse) import Data.Tuple.Nested (type (/\), (/\)) import Data.Undefined.NoProblem ((!)) import Data.Undefined.NoProblem (toMaybe) as NoProblem -import Debug (traceM) -import Effect (Effect) -import Foreign (F) -import ReadDTS.TypeScript (asDeclarationStatement, formatSyntaxKind, formatTypeFlags, formatTypeFlags', isNodeExported, showSyntaxKind, toDeclarationStatement) +import ReadDTS.TypeScript (asDeclarationStatement, isNodeExported) +import TypeScript.Compier.Types (getParameters, getReturnType) as Signature import TypeScript.Compiler.Checker (getFullyQualifiedName, getSymbolAtLocation, getTypeArguments, getTypeAtLocation, getTypeOfSymbolAtLocation) import TypeScript.Compiler.Checker.Internal (getElementTypeOfArrayType, isAnyType, isBooleanType, isNullType, isNumberType, isStringType, isTupleType, isUndefinedType) -import TypeScript.Compiler.Factory.NodeTests (asClassDeclaration, asEmptyStatement, asInterfaceDeclaration, asTypeAliasDeclaration) +import TypeScript.Compiler.Factory.NodeTests (asClassDeclaration, asEmptyStatement, asFunctionDeclaration, asInterfaceDeclaration, asParameterDeclaration, asTypeAliasDeclaration) import TypeScript.Compiler.Program (getRootFileNames, getSourceFiles, getTypeChecker) import TypeScript.Compiler.Types (FullyQualifiedName(..), Node, Program, Typ, TypeChecker) -import TypeScript.Compiler.Types.Nodes (Declaration, TypeParameterDeclaration, DeclarationStatement, interface) as Nodes +import TypeScript.Compiler.Types.Nodes (DeclarationStatement, TypeParameterDeclaration, interface) as Nodes import TypeScript.Compiler.Types.Nodes (getChildren) import TypeScript.Compiler.Types.Nodes (interface) as Node import TypeScript.Compiler.Types.Symbol (getDeclarations, getFlags, getName, symbolFlags) as Symbol -import TypeScript.Compiler.Types.Typs (TypeReference, asClassType, asInterfaceType, asIntersectionType, asNumberLiteralType, asObjectType, asStringLiteralType, asTypeParameter, asTypeReference, asUnionType, getProperties, getSymbol) -import TypeScript.Compiler.Types.Typs (asTypeReference, forget, interface) as Typs +import TypeScript.Compiler.Types.Typs (TypeReference, asClassType, asInterfaceType, asIntersectionType, asNumberLiteralType, asObjectType, asStringLiteralType, asTypeParameter, asTypeReference, asUnionType, getCallSignatures, getProperties, getSymbol) +import TypeScript.Compiler.Types.Typs (interface) as Typs import TypeScript.Compiler.Types.Typs (interface) as Typ import TypeScript.Compiler.Types.Typs.Internal (reflectBooleanLiteralType) import TypeScript.Compiler.UtilitiesPublic (idText) -data Fix :: forall k. ((k -> Type) -> Type) -> k -> Type -data Fix f a = In (f (Fix f)) - -foreign import data TsSourceFile :: Type - -type Application t = - { params :: Array t - , t :: t - } - -type Function t = - { params :: Array { name :: String, "type" :: t } - , return :: t - } +type FQN = FullyQualifiedName type Param :: Type -> Type type Param t = @@ -74,14 +56,18 @@ sequenceProp { name, type: t, optional } = { type: _, name, optional } <$> t type Props t = Array (Prop t) -type FQN = FullyQualifiedName +-- | `Arg` and `Prop` share the same structure so we reuse them. +type Arg t = Prop t +type Args t = Props t + +sequenceArg :: forall m ref. Applicative m => Arg (m ref) -> m (Arg ref) +sequenceArg = sequenceProp -- | XXX: Is there a way to pass `Maybe` constructors to FFI -- | but preserving typechecking on typescript side and drop this -- | `nullable` parameter? type OnDeclaration d t = FQN -> t -> d - type OnType :: Type -> Type type OnType t = { any :: t @@ -90,7 +76,7 @@ type OnType t = , application ∷ FQN -> Nodes.DeclarationStatement -> NonEmptyArray (Typ ()) → t , booleanLiteral ∷ Boolean → t , class ∷ Props (Typ ()) → t - -- , function ∷ Function t → t + , function ∷ Args (Typ ()) → Typ () -> t , interface ∷ Props (Typ ()) → t , intersection ∷ Array (Typ ()) → t , object :: Props (Typ ()) -> t @@ -174,7 +160,17 @@ readDeclaration checker = do let params :: forall k. { typeParameters :: _ | k } -> _ params n = map (readTypeParameterDeclaration checker) (n.typeParameters ! []) + readDeclaration' node + | Just name <- NoProblem.toMaybe <<< _.name <<< Node.interface =<< asFunctionDeclaration node = do + typ <- getTypeAtLocation checker name + s <- getSymbol typ + let + fqn = getFullyQualifiedName checker s + -- | TODO: Type parameters can be extracted from the `Signature` + -- | when function type is constructed (they aren't at the moment + -- | because I don't know the exact meaning of them. + pure { fqn, params: [], typ } | Just n <- Node.interface <$> asInterfaceDeclaration node = do typ <- getTypeAtLocation checker node s <- getSymbol typ @@ -215,9 +211,9 @@ readTypeParameterDeclaration checker n = do , default } +fqnToString :: FullyQualifiedName -> String fqnToString (FullyQualifiedName fqn) = fqn - asTypeRef :: forall t. TypeChecker -> Typ () -> OnType t -> Maybe t asTypeRef checker t onType = do let @@ -241,7 +237,12 @@ asTypeRef checker t onType = do Nothing -> do pure $ onType.typeRef fqn ref' -asApplication :: forall i. TypeChecker -> Typ i -> Maybe (FQN /\ _ /\ NonEmptyArray (Typ ())) +asApplication + :: forall i + . TypeChecker + -> Typ i + -> Maybe + (FQN /\ Nodes.DeclarationStatement /\ NonEmptyArray (Typ ())) asApplication checker t = do { target: body, typeArguments } <- asTypeReference t <#> Typs.interface ta <- NoProblem.toMaybe typeArguments @@ -281,6 +282,8 @@ readType checker t params onType | Just r <- asTupleTypeReference checker t = onType.tuple (getTypeArguments checker r) | isUndefinedType checker t = onType.undefined | Just (fqn /\ decl /\ args) <- asApplication checker t = onType.application fqn decl args + | Just { args, returnType } <- asFunction checker t = + onType.function args returnType -- | At this point we are sure that this type is not a tuple, array etc. -- | Let's assume it is `ObjectType` which represents "object"... | Just o <- asObjectType t = do @@ -295,6 +298,24 @@ readType checker t params onType | otherwise = do onType.unknown t +asFunction + :: forall r + . TypeChecker + -> Typ r + -> Maybe { args :: Args (Typ ()), returnType :: Typ () } +asFunction checker t = do + { head: sig } <- Array.uncons (getCallSignatures t) + -- | We should probabl extract type params here from signature and + -- | ignore the `params` arg. + let + param s = do + { head: decl } <- Array.uncons <<< Symbol.getDeclarations $ s + pd <- Node.interface <$> asParameterDeclaration decl + pt <- getTypeOfSymbolAtLocation checker s decl + pure { name: Symbol.getName s, type: pt, optional: isJust (NoProblem.toMaybe pd.questionToken) } + args <- traverse param $ Signature.getParameters sig + pure { args, returnType: Signature.getReturnType sig } + asTupleTypeReference :: forall i. TypeChecker -> Typ i -> Maybe TypeReference asTupleTypeReference checker t = if isTupleType checker t then asTypeReference t diff --git a/src/ReadDTS.ts b/src/ReadDTS.ts deleted file mode 100644 index b6bcbef..0000000 --- a/src/ReadDTS.ts +++ /dev/null @@ -1,416 +0,0 @@ -import * as ts from "typescript"; - -const formatHost: ts.FormatDiagnosticsHost = { - getCanonicalFileName: path => path, - getCurrentDirectory: ts.sys.getCurrentDirectory, - getNewLine: () => ts.sys.newLine -}; - -type Effect = () => a; -type FQN = string; -type Nullable = a | null; -type Result = { topLevel: d[], readDeclaration: (v: ts.Declaration) => Effect } - -type Application = { params: t[], t: t }; -type Declaration = { fqn: FQN, ref: ts.Declaration }; -type Function = { - params: { type: t, name: string }[], - "return": t, - } -type Param = { name: ts.__String, default: Nullable }; -type Parametric = { body: t, params: Param[]}; -type Prop = { name: string, type: t, optional: boolean }; -type Props = Prop[]; - -let log = function(msg: any) { console.log(msg); }; - -export function _readTypes( -{ options, visit, rootNames, inMemoryFiles, compilerHost, either }: { - options: { - compile: boolean; - debug: boolean; - }; - visit: { - onDeclaration: - (fqn: FQN) => (t: t) => d; - onTypeNode: { - any: t; - application: (a: Application) => t; - array: (e: t) => t; - booleanLiteral: (v: boolean) => t; - class: (ps: Props) => t; - function: (s: Function) => t; - interface: (ps: Props) => t; - intersection: (ts: t[]) => t; - numberLiteral: (v: number) => t; - object: (ps: Props) => t; - param: (tp: Param) => t; - parametric: (p: Parametric) => t; - primitive: (name: string) => t; - ref: (d: Declaration) => t; - stringLiteral: (value: string) => t; - tuple: (types: t[]) => t; - union: (members: t[]) => t; - unknown: (err: string) => t; - }; - }; - rootNames: string[]; - inMemoryFiles: { path: string; source: string; }[]; - compilerHost: ts.CompilerHost; - either: { - left: (err: String[]) => either; - right: (result: Result) => either; - }; -}): either { - log = options.debug?log:(function(msg: any) { msg; }); - - let program = createProgram(rootNames, inMemoryFiles, compilerHost); - let checker = program.getTypeChecker(); - let onDeclaration = visit.onDeclaration; - let onTypeNode = visit.onTypeNode; - let declarations:d[] = []; - - if(options.compile) { - let emitResult = program.emit(); - if(emitResult.emitSkipped) { - let allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics); - let errors:any[] = []; - allDiagnostics.forEach(function(d) { - if(d.category === ts.DiagnosticCategory.Error) { - errors.push(ts.formatDiagnostic(d, formatHost)); - } - }) - if(errors.length > 0) { - return either.left(errors); - } - } - } - for (const sourceFile of program.getSourceFiles()) { - if (!(rootNames.some((n) => sourceFile.fileName === n))) - continue; - - if(!options.compile && sourceFile !== undefined) { - let x = program.getSyntacticDiagnostics(sourceFile); - let errors:any[] = []; - x.forEach(function(d) { - if(d.category === ts.DiagnosticCategory.Error) { - errors.push(ts.formatDiagnostic(d, formatHost)); - } - }) - if(errors.length > 0) { - return either.left(errors); - } - } - try { - ts.forEachChild(sourceFile, function(d) { - // if (isNodeExported(check//App/appcler, d)) - declarations.push(visitDeclaration(d)); - }); - } catch(e) { - return either.left(e); - } - } - return either.right({ - topLevel: declarations, - readDeclaration: (v:ts.Declaration) => () => visitDeclaration(v) - }) - // It is probably better to use some internal checker machinery - // than to use heuristics like `fqn == "Array"` - interface MyChecker extends ts.TypeChecker { - // Hack source: - // https://github.com/microsoft/TypeScript/blob/v3.6.3/src/compiler/checker.ts - // I've additionally restricted some signatures. - getElementTypeOfArrayType: (type: ts.TypeReference) => ts.Type | undefined; - isArrayType: (type: ts.TypeReference) => boolean; - isTupleType: (type: ts.TypeReference) => boolean; - isReadonlyArrayType: (type: ts.TypeReference) => boolean; - getTypeOfPropertyOfType: (type: ts.TypeReference, propertyName: string) => ts.Type | undefined; - getNullType: () => ts.Type, - getUndefinedType: () => ts.Type, - - // Some other possible helpers - // isTupleLikeType: (type: ts.Type) => boolean; - // isArrayLikeType: (type: ts.Type) => boolean; - // isEmptyArrayLiteralType: (type: ts.Type) => boolean; - // isArrayOrTupleLikeType: (type: ts.Type) => boolean; - // isNeitherUnitTypeNorNever: (type: ts.Type) => boolean; - // isUnitType: (type: ts.Type) => boolean; - // isLiteralType: (type: ts.Type) => boolean; - } - function processTypeParameters( typeParameters: ts.NodeArray | undefined): Param[] { - return (!typeParameters)?[]:typeParameters.map(function(p: ts.TypeParameterDeclaration) { - let d = p.default?getTSType(checker.getTypeAtLocation(p.default)):null; - return { name: p.name.escapedText, default: d }; - }) - }; - - function visitDeclaration(node: ts.Node): d { - let t, fqn; - let typeNode = checker.getTypeAtLocation(node); - - if(ts.isInterfaceDeclaration(node)) { - let props = typeNode.getProperties().map((sym: ts.Symbol) => property(sym, node)); - let body = onTypeNode.interface(props); - - let params = processTypeParameters(node.typeParameters) - t = onTypeNode.parametric({ body, params }) - - fqn = checker.getFullyQualifiedName(typeNode.symbol); - // } else if(ts.isClassDeclaration(node) && node.name) { - // let props = typeNode.getProperties().map((sym: ts.Symbol) => property(sym, node)); - // let body = onTypeNode.class(props); - // let params = processTypeParameters(node.typeParameters) - // t = onTypeNode.parametric({ body, params }) - // fqn = checker.getFullyQualifiedName(typeNode.symbol); - } else if (ts.isTypeAliasDeclaration(node)) { - // let AllMeanings = ts.SymbolFlags.Value | ts.SymbolFlags.Type | ts.SymbolFlags.Namespace | ts.SymbolFlags.Alias; - // let node2:number = checker.getTypeAtLocation(node); - - // TODO: would this work in the case of external modules? - let symbol = checker.getSymbolAtLocation(node.name); - - log("Alias symbol:"); - log(symbol); - - // log("Alias symbol is undefined:"); - // log(symbol == undefined); - - // log("typeNode:"); - // log(typeNode); - - // log("fqn of symbol:") - // log(symbol?checker.getFullyQualifiedName(symbol):"WTF");//node.name.text; - - // log("fqn of exported symbol:") - // log(symbol?checker.getFullyQualifiedName(checker.getExportSymbolOfSymbol(symbol)):"WTF");//node.name.text; - - t = getTSType(typeNode); - fqn = symbol?checker.getFullyQualifiedName(checker.getExportSymbolOfSymbol(symbol)):"WTF";//node.name.text; - - // } else if(ts.isModuleDeclaration(node)) { - // log("Module declaration found:" + node.name); - // // let moduleType = checker.getTypeAtLocation(node.name) - // let declarations:d[] = []; - // // let m = checker.getSymbolAtLocation(moduleType); - // console.log("Iterating module: " + node.name) - // 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)); - // }); - // } - // }) - // let symbol = node.name?checker.getSymbolAtLocation(node.name):undefined; - // let fqn = symbol?checker.getFullyQualifiedName(symbol):undefined; - // if(fqn) { - // return onDeclaration.module_({ - // fqn, - // declarations - // }); - // } - // } - //} else if(ts.isMethodDeclaration(node)) { - // let signature = checker.getSignatureFromDeclaration(node); - } - if(t && fqn) { - return onDeclaration(fqn)(t); - } - if(typeNode) { - try { - fqn = checker.getFullyQualifiedName(typeNode.symbol); - } catch(e) { - throw(["Unable to resolve node: " + fqn]); - } - } - throw(["Unable to resolve node: " + node]); - } - - function property(sym: ts.Symbol, dec?: ts.Declaration): Prop { - let optional = (sym.flags & ts.SymbolFlags.Optional) == ts.SymbolFlags.Optional; - let memType = dec?checker.getTypeOfSymbolAtLocation(sym, dec):checker.getDeclaredTypeOfSymbol(sym); - let t = getTSType(memType, true); - return { name: sym.name, type: t, optional } - } - - function getMemberTSType(memType: ts.Type): t { - return getTSType(memType, true); - } - - function getTSType(memType: ts.Type, member = false): t { - if(memType.isLiteral()) { - memType.isStringLiteral() - } - if(memType.aliasSymbol && member) { - let s = memType.aliasSymbol; - let fqn = checker.getFullyQualifiedName(s); - let params = memType.aliasTypeArguments?.map(getMemberTSType) || []; - let ref = (s && s.valueDeclaration)?s.valueDeclaration:(s && s.declarations && s.declarations.length === 1)?s.declarations[0]:null; - if(ref) { - let t = onTypeNode.ref({fqn, ref}); - if(params) { - t = onTypeNode.application({params, t}); - } - return t; - } - } else if(memType.isStringLiteral()) { - return onTypeNode.stringLiteral(memType.value); - } else if(memType.isNumberLiteral()) { - return onTypeNode.numberLiteral(memType.value); - } else if((memType.flags & ts.TypeFlags.BooleanLiteral) && - ((memType as any).intrinsicName == "true" || - (memType as any).intrinsicName == "false" )) { - if((memType as any).intrinsicName == "true") { - return onTypeNode.booleanLiteral(true); - } else { - return onTypeNode.booleanLiteral(false); - } - } else if (memType.isUnion()) { - let types = memType.types.map((t) => { - return getMemberTSType(t); - }); - return onTypeNode.union(types); - } else if (memType.isIntersection()) { - let types = memType.types.map(getMemberTSType); - return onTypeNode.intersection(types); - } else if (memType.flags & ts.TypeFlags.Any) { - return onTypeNode.any; - // } else if (memType.flags & (ts.TypeFlags.String - // | ts.TypeFlags.BooleanLike | ts.TypeFlags.Number - // | ts.TypeFlags.Null | ts.TypeFlags.VoidLike | ts.TypeFlags.Any)) { - // // XXX: I haven't found any other way to access - // // BooleanLiteral value... - // return onTypeNode.primitive(checker.typeToString(memType)); - - } else if (memType.flags & (ts.TypeFlags.Object | ts.TypeFlags.NonPrimitive)) { - let memObjectType = memType; - // let onInterfaceReference = function(target: ts.InterfaceType, typeArguments: t[]) { - // let ref = (target.symbol && target.symbol.valueDeclaration) - // ?target.symbol.valueDeclaration - // :(target.symbol && target.symbol.declarations.length === 1) - // ?target.symbol.declarations[0] - // :null; - // let fqn = checker.getFullyQualifiedName(target.symbol); - // return ref - // ?onTypeNode.typeReference({typeArguments, fqn, ref}) - // :onTypeNode.unknown("Unable to get type declaration for:" + fqn + "<" + typeArguments + ">") - // } - // if(memObjectType.objectFlags & ts.ObjectFlags.Reference) { - // let reference = memObjectType; - // if(checker.isArrayType(reference)) { - // log("Trying array") - // let elem = checker.getElementTypeOfArrayType(reference); - // if(elem) - // return onTypeNode.array(getMemberTSType(elem)); - // } - // if(checker.isTupleType(reference)) { - // log("Tuple") - // let e: string, elem:ts.Type | undefined, elems:t[] = []; - // let MAX_TUPLE_LENGTH = 20; - // for(let i=0; i and Record<..> - // } else if((memObjectType.objectFlags & ts.ObjectFlags.Mapped) && - // (memObjectType.objectFlags & ts.ObjectFlags.Instantiated)) { - - // let objDeclarations = memObjectType.symbol.getDeclarations(); - // let props = memObjectType.getProperties().map((sym: ts.Symbol) => - // property(sym, objDeclarations?objDeclarations[0]:sym.declarations?sym.declarations[1]:sym.valueDeclaration) - // ); - // return onTypeNode.object(props); - // } - // } else - if(memObjectType.objectFlags & ts.ObjectFlags.Anonymous) { - // log("Anonymous") - // // TODO: Currently any object which is "callable" is interpreted - // // as a plain function - // let signature = memObjectType.getCallSignatures()[0]; - // if(signature) { - // log("Treating this as function: " + memObjectType.symbol.getName()); - // let functionType = { - // parameters: signature.parameters.map((parameterSymbol) => { - // return { - // name: parameterSymbol.getName(), - // type: getMemberTSType( - // checker.getTypeOfSymbolAtLocation(parameterSymbol, parameterSymbol?.valueDeclaration), - // ) - // }; - // }), - // returnType: getMemberTSType(signature.getReturnType()) - // }; - // log("Returning funciton type:" + functionType); - // return onTypeNode.function(functionType); - // } - let props = memObjectType.getProperties().map((sym: ts.Symbol) => property(sym, sym.valueDeclaration)); - return onTypeNode.object(props); - } - // return onTypeNode.unknown("Uknown object type node (flags = " + memObjectType.objectFlags + "):" + checker.typeToString(memObjectType)); - // } - // else if (memType.isTypeParameter()) { - // log("Type parameter?") - // let d = memType.getDefault(); - // return onTypeNode.typeParameter({ name: memType.symbol.escapedName, default: d?getTSType(d):null }); - } - throw(["Unable to process type: " + checker.typeToString(memType)]); - } -} - -// https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API#using-the-type-checker -function isNodeExported(checker:ts.TypeChecker, node: ts.Node): boolean { - let sym = checker.getSymbolAtLocation(node); - return ( - sym? ((ts.getCombinedModifierFlags(sym.valueDeclaration) & ts.ModifierFlags.Export) !== 0):false || - (!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile && node.kind !== ts.SyntaxKind.EndOfFileToken) - ) -}; - -// https://stackoverflow.com/questions/53733138/how-do-i-type-check-a-snippet-of-typescript-code-in-memory -function createProgram(rootNames: string[], inMemoryFiles: {path: string, source: string}[], realHost: ts.CompilerHost): ts.Program { - let host = realHost; - if(inMemoryFiles) { - let paths = inMemoryFiles.map((m) => m.path); - let sourceFiles = inMemoryFiles.map((m) => { return { module: m, file: ts.createSourceFile(m.path, m.source, ts.ScriptTarget.ES5, true)}}); - - host = { - fileExists: filePath => paths.some((p) => filePath == p) || realHost.fileExists(filePath), - directoryExists: realHost.directoryExists && realHost.directoryExists.bind(realHost), - getCurrentDirectory: realHost.getCurrentDirectory.bind(realHost), - getDirectories: realHost.getDirectories?realHost.getDirectories.bind(realHost):undefined, - getCanonicalFileName: fileName => realHost.getCanonicalFileName(fileName), - getNewLine: realHost.getNewLine.bind(realHost), - getDefaultLibFileName: realHost.getDefaultLibFileName.bind(realHost), - getSourceFile: function(fileName, languageVersion, onError, shouldCreateNewSourceFile) { - var m = sourceFiles.find((f) => f.module.path == fileName); - return m?m.file:realHost.getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile); - }, - readFile: function(fileName) { - var f = sourceFiles.find((f) => f.module.path == fileName); - return f?f.module.source:realHost.readFile(fileName); - }, - useCaseSensitiveFileNames: () => realHost.useCaseSensitiveFileNames(), - writeFile: (_, data) => { data }, - }; - } - let options = {}; - return ts.createProgram(rootNames, options, host); -} diff --git a/src/ReadDTS/AST.purs b/src/ReadDTS/AST.purs index 7cf295f..021f793 100644 --- a/src/ReadDTS/AST.purs +++ b/src/ReadDTS/AST.purs @@ -3,60 +3,41 @@ module ReadDTS.AST where import Prelude import Control.Monad.Error.Class (class MonadError, throwError) -import Control.Monad.Except (ExceptT(..), runExcept, runExceptT) -import Control.Monad.Rec.Loops (unfoldrM) as Rec.Loops +import Control.Monad.Except (runExceptT) import Control.Monad.Rec.Loops (whileJust_) -import Control.Monad.State (StateT, execState, execStateT, runStateT) -import Control.Monad.State.Class (gets, modify_) -import Data.Array (catMaybes) -import Data.Array (catMaybes) as Array -import Data.Array as A +import Control.Monad.State (execStateT) import Data.Array.NonEmpty (NonEmptyArray) -import Data.Bifoldable (class Bifoldable, bifoldMap, bifoldl, bifoldlDefault, bifoldr, bifoldrDefault) +import Data.Bifoldable (class Bifoldable, bifoldMap, bifoldlDefault, bifoldrDefault) import Data.Bifunctor (class Bifunctor, lmap) -import Data.Bifunctor (lmap) as Bifunctor -import Data.Divide (divided) -import Data.Either (Either(..)) -import Data.Eq (class Eq1, eq1) -import Data.Foldable (class Foldable, fold, foldMap, foldlDefault, foldr, foldrDefault) -import Data.FoldableWithIndex (forWithIndex_) -import Data.Functor.Contravariant ((>$<)) +import Data.Either (Either) +import Data.Eq (class Eq1) +import Data.Foldable (class Foldable, foldMap, foldlDefault, foldrDefault) import Data.Functor.Mu (Mu(..)) as Mu -import Data.Functor.Mu (Mu(..), roll) +import Data.Functor.Mu (Mu) import Data.Generic.Rep (class Generic) -import Data.Lens (Lens, over, use) -import Data.Lens (over) as Lens +import Data.Identity (Identity(..)) +import Data.Lens (use) import Data.Lens.Record (prop) -import Data.Lens.Setter ((.=), (.~), (%=)) +import Data.Lens.Setter ((%=), (.=)) import Data.List (List(..), fromFoldable, singleton) as List import Data.List (List, (:)) -import Data.Map (Map) -import Data.Map (empty, filter, filterKeys, fromFoldable, insert, keys, lookup, singleton, toUnfoldableUnordered, union) as Map -import Data.Maybe (Maybe(..), isJust, isNothing) -import Data.Monoid (guard) as Monoid +import Data.Maybe (Maybe(..)) import Data.Newtype (un) -import Data.Predicate (Predicate(..)) import Data.Profunctor.Strong ((&&&)) -import Data.Set (Set) -import Data.Set (empty, fromFoldable, insert, member) as Set +import Data.Set (fromFoldable, insert, member) as Set import Data.Show.Generic (genericShow) import Data.Traversable (class Traversable, for, sequence, traverse, traverseDefault) -import Data.TraversableWithIndex (forWithIndex, traverseWithIndex) import Data.Tuple (fst) -import Data.Tuple.Nested ((/\)) -import Debug (traceM) -import Effect (Effect) -import Effect.Class (liftEffect) +import Data.Tuple.Nested ((/\), type (/\)) import Matryoshka (CoalgebraM, anaM, cata) -import ReadDTS (Application, Param, Prop, Props, asTypeRef, readDeclaration, readType, sequenceProp) as ReadDTS -import ReadDTS (Options, Visit, foldMapParam, fqnToString, readRootDeclarationNodes, readRootDeclarations, readType, sequenceParam) +import ReadDTS (Args, Param, Prop, Props, asTypeRef, readDeclaration, readType, sequenceArg, sequenceProp) as ReadDTS +import ReadDTS (Visit, foldMapParam, fqnToString, readRootDeclarationNodes, sequenceParam) import ReadDTS.TypeScript (getDeclarationStatementFqn) -import Type.Prelude (Proxy(..), SProxy(..)) +import Type.Prelude (Proxy(..)) import TypeScript.Compiler.Program (getTypeChecker) -import TypeScript.Compiler.Types (FullyQualifiedName(..), Typ) +import TypeScript.Compiler.Types (FullyQualifiedName, Program, Typ, TypeChecker) import TypeScript.Compiler.Types (Typ) as TS -import TypeScript.Compiler.Types.Nodes (Declaration, DeclarationStatement) as Nodes -import TypeScript.Compiler.Types.Nodes (DeclarationStatement) +import TypeScript.Compiler.Types.Nodes (DeclarationStatement) as Nodes import Unsafe.Coerce (unsafeCoerce) import Unsafe.Reference (unsafeRefEq) @@ -77,14 +58,14 @@ data TsType decl ref | TsArray ref | TsBoolean | TsBooleanLiteral Boolean - | TsClass (Array (ReadDTS.Prop ref)) - -- | Function (ReadDTS.Function ref) - | TsInterface (Array (ReadDTS.Prop ref)) + | TsClass (ReadDTS.Props ref) + | TsFunction (ReadDTS.Args ref) ref + | TsInterface (ReadDTS.Props ref) | TsIntersection (Array ref) | TsNull | TsNumber | TsNumberLiteral Number - | TsObject (Array (ReadDTS.Prop ref)) + | TsObject (ReadDTS.Props ref) | TsString | TsStringLiteral String | TsTuple (Array ref) @@ -94,13 +75,12 @@ data TsType decl ref | TsUndefined | TsUnion (Array ref) | TsUnknown TSTyp --- | Void +-- | TsVoid derive instance functorTsType :: Functor (TsType decl) derive instance eqTsType :: (Eq decl, Eq ref) => Eq (TsType decl ref) instance eq1TsType :: Eq decl => Eq1 (TsType decl) where eq1 = eq --- derive instance (Ord decl, Ord ref) => Ord (TsType decl ref) derive instance genericTsType :: Generic (TsType decl ref) _ instance showTsType :: (Show decl, Show ref) => Show (TsType decl ref) where show s = genericShow s @@ -116,11 +96,9 @@ instance Foldable (TsType decl) where foldMap _ TsBoolean = mempty foldMap _ (TsBooleanLiteral _) = mempty foldMap f (TsClass ts) = foldMap (f <<< _.type) ts - -- foldMap f (Function r) - -- = foldMap (foldMap f <<< _.type) r.parameters - -- <> foldMap f r.returnType - -- -- foldMap f (Intersection ts) = fold (map (foldMap f) ts) + foldMap f (TsFunction args r) = foldMap (f <<< _.type) args <> f r foldMap f (TsInterface ts) = foldMap (f <<< _.type) ts + -- foldMap f (Intersection ts) = fold (map (foldMap f) ts) foldMap f (TsApplication _ ps) = foldMap f ps foldMap f (TsIntersection ts) = foldMap f ts foldMap _ TsNull = mempty @@ -136,8 +114,6 @@ instance Foldable (TsType decl) where foldMap _ TsUndefined = mempty foldMap f (TsUnion ts) = foldMap f ts foldMap _ (TsUnknown _) = mempty - -- foldMap _ Void = mempty - foldr f t = foldrDefault f t foldl f t = foldlDefault f t @@ -155,12 +131,9 @@ instance Traversable (TsType decl) where sequence TsBoolean = pure TsBoolean sequence (TsBooleanLiteral b) = pure $ TsBooleanLiteral b sequence (TsClass props) = map TsClass $ sequence $ map ReadDTS.sequenceProp props - -- sequence (Function r) = map Function - -- $ { parameters: _, returnType: _ } - -- <$> traverse sequenceParameter r.parameters - -- <*> sequence r.returnType - -- where - -- sequenceParameter { name, "type": t } = { name, "type": _ } <$> sequence t + sequence (TsFunction args r) = TsFunction + <$> traverse ReadDTS.sequenceArg args + <*> r sequence (TsInterface props) = map TsInterface $ sequence $ map ReadDTS.sequenceProp props sequence (TsIntersection ts) = TsIntersection <$> sequence ts sequence TsNull = pure TsNull @@ -201,6 +174,7 @@ visitor = , boolean: TsBoolean , booleanLiteral: TsBooleanLiteral , class: TsClass + , function: TsFunction , intersection: TsIntersection , interface: TsInterface , object: TsObject @@ -221,29 +195,16 @@ visitor = type TsType' = TsType { fullyQualifiedName :: FullyQualifiedName, ref :: Nodes.DeclarationStatement } -type KnownDeclarations = Map FullyQualifiedName (Mu TsType') - -type UnknownDeclarations = Map FullyQualifiedName DeclarationStatement - -type Context = { current :: Maybe FullyQualifiedName, known :: KnownDeclarations, unknown :: UnknownDeclarations } - --- seen :: Context -> Set FullyQualifiedName --- seen { current, known, unknown } = Set.fromFoldable current <> Map.keys known <> Map.keys unknown - -type VisitMonad a = ExceptT (Array String) (StateT Context Effect) a - -type Seed = - { level :: Int, ref :: TS.Typ () } +type Seed = { level :: Int, ref :: TS.Typ () } type ReadTsType m = Seed -> m (TsType' (TS.Typ ())) - coalgebra :: forall m. MonadError (Array String) m => ReadTsType m -> - CoalgebraM m _ Seed -coalgebra readTsType seed@{ level, ref } = do + CoalgebraM m (TsType') Seed +coalgebra readTsType seed@{ level } = do let maxDepth = 10 seed' = { level: level + 1, ref: _ } @@ -254,12 +215,26 @@ coalgebra readTsType seed@{ level, ref } = do else throwError [ "Maximum recursion depth (max depth is " <> show maxDepth <> ") in ReadDTS.AST.visitor coalgebra" ] -_unknowns = prop (Proxy :: Proxy "unknowns") -_knowns = prop (Proxy :: Proxy "knowns") -_seen = prop (Proxy :: Proxy "seen") +unfoldType + :: forall m + . MonadError (Array String) m + => TypeChecker + -> { level :: Int , ref :: Typ () } + -> m (Mu TsType') +unfoldType checker = anaM $ coalgebra \{ level, ref: t } -> pure + if level == 0 + then ReadDTS.readType checker t [] visitor.onType + else case ReadDTS.asTypeRef checker t visitor.onType of + Just t' -> t' + Nothing -> ReadDTS.readType checker t [] visitor.onType -visit program = do +types :: Program -> Either (Array String) (List (FullyQualifiedName /\ Mu (TsType FullyQualifiedName))) +types program = un Identity $ runExceptT do let + _unknowns = prop (Proxy :: Proxy "unknowns") + _knowns = prop (Proxy :: Proxy "knowns") + _seen = prop (Proxy :: Proxy "seen") + checker = getTypeChecker program roots = List.fromFoldable $ readRootDeclarationNodes program unknowns = map (getDeclarationStatementFqn checker &&& identity) roots @@ -276,27 +251,20 @@ visit program = do pure $ Just head List.Nil -> pure Nothing - foldTypeDecls = bifoldMap + foldTypeDecls = cata $ bifoldMap (\{ fullyQualifiedName: fqn, ref } -> List.singleton (fqn /\ ref)) (identity) - stripTypeRefs = lmap _.fullyQualifiedName + stripTypeRefs = cata $ Mu.In <<< lmap _.fullyQualifiedName - flip execStateT ctx $ whileJust_ getUnknown \(fqn /\ node) -> do + map _.knowns $ flip execStateT ctx $ whileJust_ getUnknown \(fqn /\ node) -> do case ReadDTS.readDeclaration checker node of Nothing -> throwError ["Problem reading " <> fqnToString fqn ] Just { typ } -> do - typ'@(In _) <- unfoldType checker { level: 0, ref: typ } - _knowns %= List.Cons (fqn /\ cata (\t -> Mu.In (stripTypeRefs t)) typ') - for (cata foldTypeDecls typ') \decl@(fqn' /\ _) -> do + typ' <- unfoldType checker { level: 0, ref: typ } + _knowns %= List.Cons (fqn /\ stripTypeRefs typ') + for (foldTypeDecls typ') \decl@(fqn' /\ _) -> do seen <- use _seen when (not $ fqn' `Set.member` seen) do _seen %= Set.insert fqn' _unknowns %= List.Cons decl -unfoldType checker = anaM $ coalgebra \{ level, ref: t } -> pure - if level == 0 - then ReadDTS.readType checker t [] visitor.onType - else case ReadDTS.asTypeRef checker t visitor.onType of - Just t' -> t' - Nothing -> ReadDTS.readType checker t [] visitor.onType - diff --git a/src/ReadDTS/TypeScript.js b/src/ReadDTS/TypeScript.js index f8234cc..66d0842 100644 --- a/src/ReadDTS/TypeScript.js +++ b/src/ReadDTS/TypeScript.js @@ -19,7 +19,7 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.toDeclarationStatementImpl = exports.formatSyntaxKind = exports.formatTypeFlags = exports.showSyntaxKind = exports.isNodeExportedImpl = void 0; +exports.toDeclarationStatementImpl = exports.formatObjectFlags = exports.formatSyntaxKind = exports.formatTypeFlags = exports.showSyntaxKind = exports.isNodeExportedImpl = void 0; var ts = __importStar(require("typescript")); function isNodeExportedImpl(checker, node) { var sym = checker.getSymbolAtLocation(node); @@ -40,6 +40,12 @@ var formatSyntaxKind = function (node) { return Debug.formatSyntaxKind(node.kind); }; exports.formatSyntaxKind = formatSyntaxKind; +var formatObjectFlags = function (o) { + var Debug = ts.Debug; + var objectFlags = ts.isObject(o) ? o.objectFlags : undefined; + return Debug.formatObjectFlags(objectFlags); +}; +exports.formatObjectFlags = formatObjectFlags; var toDeclarationStatementImpl = function (n) { return n; }; diff --git a/src/ReadDTS/TypeScript.purs b/src/ReadDTS/TypeScript.purs index e51ed17..a4d6d27 100644 --- a/src/ReadDTS/TypeScript.purs +++ b/src/ReadDTS/TypeScript.purs @@ -8,7 +8,7 @@ import Data.Maybe (Maybe, fromJust) import Partial.Unsafe (unsafePartial) import Prim.Row (class Cons, class Union) as Row import TypeScript.Compiler.Checker (getFullyQualifiedName, getSymbolAtLocation) -import TypeScript.Compiler.Factory.NodeTests (asClassDeclaration, asInterfaceDeclaration, asTypeAliasDeclaration) +import TypeScript.Compiler.Factory.NodeTests (asClassDeclaration, asFunctionDeclaration, asInterfaceDeclaration, asTypeAliasDeclaration) import TypeScript.Compiler.Types (FullyQualifiedName, Node, Typ, TypeChecker, TypeFlags) import TypeScript.Compiler.Types.Nodes (DeclarationStatement) import TypeScript.Compiler.Types.Nodes (DeclarationStatement, interface) as Nodes @@ -36,6 +36,7 @@ toDeclarationStatement :: tagRow_ ( "ClassDeclaration" :: Void , "ClassLikeDeclaration" :: Void + , "FunctionDeclaration" :: Void , "InterfaceDeclaration" :: Void , "TypeAliasDeclaration" :: Void ) => @@ -51,6 +52,7 @@ asDeclarationStatement node = (toDeclarationStatement <$> asTypeAliasDeclaration node) <|> (toDeclarationStatement <$> asInterfaceDeclaration node) <|> (toDeclarationStatement <$> asClassDeclaration node) + <|> (toDeclarationStatement <$> asFunctionDeclaration node) getDeclarationStatementFqn :: TypeChecker -> DeclarationStatement -> FullyQualifiedName getDeclarationStatementFqn checker node = unsafePartial $ fromJust do @@ -58,6 +60,7 @@ getDeclarationStatementFqn checker node = unsafePartial $ fromJust do (getSymbolAtLocation checker <<< _.name <<< Nodes.interface =<< asTypeAliasDeclaration node) <|> (getSymbolAtLocation checker =<< asInterfaceDeclaration node) <|> (getSymbolAtLocation checker =<< asClassDeclaration node) + <|> (getSymbolAtLocation checker =<< asFunctionDeclaration node) pure $ getFullyQualifiedName checker symbol diff --git a/src/ReadDTS/TypeScript.ts b/src/ReadDTS/TypeScript.ts index 6a8c0aa..44bb164 100644 --- a/src/ReadDTS/TypeScript.ts +++ b/src/ReadDTS/TypeScript.ts @@ -13,6 +13,7 @@ export const showSyntaxKind = (node: ts.Node): string => ts.SyntaxKind[node.kind type Debug = { formatTypeFlags: (t: ts.Type) => string formatSyntaxKind: (kind: ts.SyntaxKind) => string + formatObjectFlags: (o: ts.ObjectFlags | undefined) => string }; export const formatTypeFlags = (t: ts.Type): string => { @@ -25,6 +26,12 @@ export const formatSyntaxKind = (node: ts.Node): string => { return Debug.formatSyntaxKind(node.kind); } +export const formatObjectFlags = (o: ts.Node): string => { + let Debug = (<{ Debug: Debug }>(ts)).Debug; + let objectFlags = ts.isObject(o)?o.objectFlags:undefined; + return Debug.formatObjectFlags(objectFlags); +} + export const toDeclarationStatementImpl = (n: ts.ClassDeclaration | ts.InterfaceDeclaration | ts.TypeAliasDeclaration): ts.DeclarationStatement => { return n; } diff --git a/src/TypeScript/Compiler/Checker/Internal.purs b/src/TypeScript/Compiler/Checker/Internal.purs index 974a81f..75fb03e 100644 --- a/src/TypeScript/Compiler/Checker/Internal.purs +++ b/src/TypeScript/Compiler/Checker/Internal.purs @@ -5,7 +5,7 @@ import Prelude import Data.Function.Uncurried (Fn2, Fn3, runFn2, runFn3) import Data.Maybe (Maybe) import Data.Nullable (Nullable, toMaybe) -import TypeScript.Compiler.Types (TypeChecker, Typ) +import TypeScript.Compiler.Types (Typ, TypeChecker) isAnyType ∷ forall i. TypeChecker -> Typ i -> Boolean isAnyType = runFn2 isAnyTypeImpl diff --git a/src/TypeScript/Compiler/Checker/Internal.ts b/src/TypeScript/Compiler/Checker/Internal.ts index 8e8fbf6..ade686a 100644 --- a/src/TypeScript/Compiler/Checker/Internal.ts +++ b/src/TypeScript/Compiler/Checker/Internal.ts @@ -47,5 +47,3 @@ export const isUndefinedTypeImpl = (checker: TypeChecker, type: ts.Type): boolea export const getTypeOfPropertyOfTypeImpl = (checker: TypeChecker, type: ts.Type, propertyName: string): ts.Type | null => checker.getTypeOfPropertyOfType(type, propertyName) || null; - - diff --git a/src/TypeScript/Compiler/Types.purs b/src/TypeScript/Compiler/Types.purs index 7196c16..07a1e3c 100644 --- a/src/TypeScript/Compiler/Types.purs +++ b/src/TypeScript/Compiler/Types.purs @@ -91,3 +91,5 @@ derive instance Eq FullyQualifiedName derive instance Ord FullyQualifiedName derive newtype instance Show FullyQualifiedName +foreign import data Signature :: Type + diff --git a/src/TypeScript/Compiler/Types/Nodes.purs b/src/TypeScript/Compiler/Types/Nodes.purs index b017c2f..8252e20 100644 --- a/src/TypeScript/Compiler/Types/Nodes.purs +++ b/src/TypeScript/Compiler/Types/Nodes.purs @@ -96,7 +96,15 @@ type ExternalModuleReference = Node "ExternalModuleReference" () type ForInStatement = Node "ForInStatement" () type ForOfStatement = Node "ForOfStatement" () type ForStatement = Node "ForStatement" () -type FunctionDeclaration = Node "FunctionDeclaration" () + +-- | There is more to FunctionDeclaration +-- export interface FunctionLikeDeclarationBase extends SignatureDeclarationBase { +-- readonly asteriskToken?: AsteriskToken; +-- readonly questionToken?: QuestionToken; +-- readonly exclamationToken?: ExclamationToken; +-- readonly body?: Block | Expression; +-- } +type FunctionDeclaration = Node "FunctionDeclaration" (name :: Opt Identifier) type FunctionExpression = Node "FunctionExpression" () type FunctionTypeNode = Node "FunctionTypeNode" () type GetAccessorDeclaration = Node "GetAccessorDeclaration" () @@ -179,7 +187,13 @@ type ObjectBindingPattern = Node "ObjectBindingPattern" () type ObjectLiteralExpression = Node "ObjectLiteralExpression" () type OmittedExpression = Node "OmittedExpression" () type OptionalTypeNode = Node "OptionalTypeNode" () -type ParameterDeclaration = Node "ParameterDeclaration" () + +-- type BindingName = Identifier | BindingPattern; +type ParameterDeclaration = Node "ParameterDeclaration" + ( name :: Node "BindingName" () + , questionToken :: Opt QuestionToken + , "type" :: Opt TypeNode + ) type ParenthesizedExpression = Node "ParenthesizedExpression" () type ParenthesizedTypeNode = Node "ParenthesizedTypeNode" () type PartiallyEmittedExpression = Node "PartiallyEmittedExpression" () @@ -261,6 +275,17 @@ type TypeParameterDeclaration = Node "TypeParameterDeclaration" type TypePredicateNode = Node "TypePredicateNode" () type TypeQueryNode = Node "TypeQueryNode" () type TypeReferenceNode = Node "TypeReferenceNode" () + +-- | Not a real node kind but rather a union +-- | export type SignatureDeclaration = +-- | | CallSignatureDeclaration | ConstructSignatureDeclaration +-- | | MethodSignature | IndexSignatureDeclaration +-- | | FunctionTypeNode | ConstructorTypeNode +-- | | JSDocFunctionType | FunctionDeclaration +-- | | MethodDeclaration | ConstructorDeclaration +-- | | AccessorDeclaration | FunctionExpression +-- | | ArrowFunction +type SignatureDeclaration = Node "SignatureDeclaration" () type UnionTypeNode = Node "UnionTypeNode" () type Unparsed = Node "Unparsed" () type UnparsedPrepend = Node "UnparsedPrepend" () diff --git a/src/TypeScript/Compiler/Types/Signature.js b/src/TypeScript/Compiler/Types/Signature.js new file mode 100644 index 0000000..0003e78 --- /dev/null +++ b/src/TypeScript/Compiler/Types/Signature.js @@ -0,0 +1,23 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getReturnType = exports.getParameters = exports.getTypeParameters = exports.getDeclaration = void 0; +function getDeclaration(s) { + return s.getDeclaration() || null; +} +exports.getDeclaration = getDeclaration; +function getTypeParameters(s) { + return s.getTypeParameters() || []; +} +exports.getTypeParameters = getTypeParameters; +function getParameters(s) { + return s.getParameters(); +} +exports.getParameters = getParameters; +function getReturnType(s) { + return s.getReturnType(); +} +exports.getReturnType = getReturnType; +// export function getDocumentationComment(): SymbolDisplayPart[] { +// return this.documentationComment || (this.documentationComment = getDocumentationComment(singleElementArray(this.declaration), this.checker)); +// } +//# sourceMappingURL=Signature.js.map \ No newline at end of file diff --git a/src/TypeScript/Compiler/Types/Signature.purs b/src/TypeScript/Compiler/Types/Signature.purs new file mode 100644 index 0000000..83083ef --- /dev/null +++ b/src/TypeScript/Compiler/Types/Signature.purs @@ -0,0 +1,16 @@ +module TypeScript.Compier.Types where + +import TypeScript.Compiler.Types (Signature, Symbol_, Typ) +import TypeScript.Compiler.Types.Nodes (SignatureDeclaration) as Nodes +import TypeScript.Compiler.Types.Typs (TypeParameter) as Typs + +foreign import getDeclaration :: Signature -> Nodes.SignatureDeclaration + +foreign import getTypeParameters :: Signature -> Array Typs.TypeParameter + +foreign import getParameters :: Signature -> Array Symbol_ + +foreign import getReturnType :: Signature -> Typ () + +-- foreign import getDocumentationComment :: Signature -> --(): SymbolDisplayPart[] { + diff --git a/src/TypeScript/Compiler/Types/Signature.ts b/src/TypeScript/Compiler/Types/Signature.ts new file mode 100644 index 0000000..7213c3e --- /dev/null +++ b/src/TypeScript/Compiler/Types/Signature.ts @@ -0,0 +1,20 @@ +import * as ts from "typescript"; + +export function getDeclaration(s: ts.Signature): ts.SignatureDeclaration { + return s.getDeclaration() || null; +} +export function getTypeParameters(s: ts.Signature): ts.TypeParameter[] { + return s.getTypeParameters() || []; +} + +export function getParameters(s: ts.Signature): ts.Symbol[] { + return s.getParameters(); +} + +export function getReturnType(s: ts.Signature): ts.Type { + return s.getReturnType(); +} + +// export function getDocumentationComment(): SymbolDisplayPart[] { +// return this.documentationComment || (this.documentationComment = getDocumentationComment(singleElementArray(this.declaration), this.checker)); +// } diff --git a/src/TypeScript/Compiler/Types/Typs.js b/src/TypeScript/Compiler/Types/Typs.js index 60ea129..11f0455 100644 --- a/src/TypeScript/Compiler/Types/Typs.js +++ b/src/TypeScript/Compiler/Types/Typs.js @@ -19,14 +19,16 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.getDefaultImpl = exports.getSymbolImpl = exports.asClassTypeImpl = exports.asInterfaceTypeImpl = exports.getPropertiesImpl = exports.asTypeReferenceImpl = exports.asTypeParameterImpl = exports.asUnionTypeImpl = exports.asStringLiteralTypeImpl = exports.asObjectTypeImpl = exports.asIntersectionTypeImpl = exports.asNumberLiteralTypeImpl = void 0; +exports.getDefaultImpl = exports.getSymbolImpl = exports.asClassTypeImpl = exports.asInterfaceTypeImpl = exports.getPropertiesImpl = exports.getCallSignatures = exports.asTypeReferenceImpl = exports.asTypeParameterImpl = exports.asUnionTypeImpl = exports.asStringLiteralTypeImpl = exports.asObjectTypeImpl = exports.asIntersectionTypeImpl = exports.asNumberLiteralTypeImpl = void 0; var ts = __importStar(require("typescript")); var asNumberLiteralTypeImpl = function (t) { return t.isNumberLiteral() ? t : null; }; exports.asNumberLiteralTypeImpl = asNumberLiteralTypeImpl; var asIntersectionTypeImpl = function (t) { return t.isIntersection() ? t : null; }; exports.asIntersectionTypeImpl = asIntersectionTypeImpl; var asObjectTypeImpl = function (t) { - if (t.flags & ts.TypeFlags.Object) + var Nullable = ts.TypeFlags.Undefined | ts.TypeFlags.Null; + var ObjectFlagsType = ts.TypeFlags.Any | Nullable | ts.TypeFlags.Never | ts.TypeFlags.Object | ts.TypeFlags.Union | ts.TypeFlags.Intersection; + if (t.flags & ObjectFlagsType) return t; return null; }; @@ -39,21 +41,14 @@ var asTypeParameterImpl = function (t) { return t.isTypeParameter() ? t : null; exports.asTypeParameterImpl = asTypeParameterImpl; var asTypeReferenceImpl = function (t) { // There is no sens at the moment to expose ObjectType casting I think to the PS side... - var isObjectType = function (t) { - var Nullable = ts.TypeFlags.Undefined | ts.TypeFlags.Null; - var ObjectFlagsType = ts.TypeFlags.Any | Nullable | ts.TypeFlags.Never | ts.TypeFlags.Object | ts.TypeFlags.Union | ts.TypeFlags.Intersection; - return !!(t.flags & ObjectFlagsType); - }; - // console.log("isObjectType(t):"); - // console.log(isObjectType(t)); - // console.log("t.objectFlags & ts.ObjectFlags.Reference:"); - // console.log(t.objectFlags & ts.ObjectFlags.Reference); - - if (isObjectType(t) && !!(t.objectFlags & ts.ObjectFlags.Reference)) - return t; + var s = (0, exports.asObjectTypeImpl)(t); + if (s && s.objectFlags & ts.ObjectFlags.Reference) + return s; return null; }; exports.asTypeReferenceImpl = asTypeReferenceImpl; +var getCallSignatures = function (t) { return t.getCallSignatures(); }; +exports.getCallSignatures = getCallSignatures; var getPropertiesImpl = function (t) { return t.getProperties(); }; exports.getPropertiesImpl = getPropertiesImpl; var asInterfaceTypeImpl = function (t) { @@ -74,4 +69,4 @@ var getSymbolImpl = function (t) { return t.getSymbol() || null; }; exports.getSymbolImpl = getSymbolImpl; var getDefaultImpl = function (t) { return t.getDefault() || null; }; exports.getDefaultImpl = getDefaultImpl; -//# sourceMappingURL=Typs.js.map +//# sourceMappingURL=Typs.js.map \ No newline at end of file diff --git a/src/TypeScript/Compiler/Types/Typs.purs b/src/TypeScript/Compiler/Types/Typs.purs index 4342de4..6b15ce5 100644 --- a/src/TypeScript/Compiler/Types/Typs.purs +++ b/src/TypeScript/Compiler/Types/Typs.purs @@ -7,7 +7,7 @@ import Data.Maybe (Maybe) import Data.Nullable (Nullable, toMaybe) import Data.Undefined.NoProblem (Opt) import Type.Row (type (+)) -import TypeScript.Compiler.Types (Symbol_, Typ, TypeFlags) +import TypeScript.Compiler.Types (Signature, Symbol_, Typ, TypeFlags) import Unsafe.Coerce (unsafeCoerce) type TypRow r = @@ -76,6 +76,8 @@ asObjectType = toMaybe <<< runFn1 asObjectTypeImpl foreign import asObjectTypeImpl :: forall r. Fn1 (Typ r) (Nullable ObjectType) +foreign import getCallSignatures :: forall r. Typ r -> Array Signature + type StringLiteralType = Typ (value :: String) asStringLiteralType :: forall r. Typ r -> Maybe StringLiteralType @@ -123,3 +125,4 @@ getDefault :: forall i. Typ i -> Maybe (Typ ()) getDefault = toMaybe <<< runFn1 getDefaultImpl foreign import getDefaultImpl :: forall i. Fn1 (Typ i) (Nullable (Typ ())) + diff --git a/src/TypeScript/Compiler/Types/Typs.ts b/src/TypeScript/Compiler/Types/Typs.ts index 57a69d8..e262dad 100644 --- a/src/TypeScript/Compiler/Types/Typs.ts +++ b/src/TypeScript/Compiler/Types/Typs.ts @@ -12,7 +12,10 @@ export interface PlainObjectType extends ts.Type { export const asObjectTypeImpl = (t: ts.Type): ts.ObjectType | null => { - if (t.flags & ts.TypeFlags.Object) + let Nullable = ts.TypeFlags.Undefined | ts.TypeFlags.Null; + let ObjectFlagsType = ts.TypeFlags.Any | Nullable | ts.TypeFlags.Never | ts.TypeFlags.Object | ts.TypeFlags.Union | ts.TypeFlags.Intersection; + + if (t.flags & ObjectFlagsType) return t; return null; } @@ -25,18 +28,15 @@ export const asTypeParameterImpl = (t: ts.Type): ts.TypeParameter | null => t.is export const asTypeReferenceImpl = (t: ts.Type): ts.TypeReference | null => { // There is no sens at the moment to expose ObjectType casting I think to the PS side... - const isObjectType = (t: ts.Type): t is ts.ObjectType => { - let Nullable = ts.TypeFlags.Undefined | ts.TypeFlags.Null; - let ObjectFlagsType = ts.TypeFlags.Any | Nullable | ts.TypeFlags.Never | ts.TypeFlags.Object | ts.TypeFlags.Union | ts.TypeFlags.Intersection; - return !!(t.flags & ObjectFlagsType); - } - - if(isObjectType(t) && !!(t.objectFlags & ts.ObjectFlags.Reference)) - return t; + let s = asObjectTypeImpl(t); + if(s && s.objectFlags & ts.ObjectFlags.Reference) + return s; return null; } +export const getCallSignatures = (t: ts.Type): readonly ts.Signature[] => t.getCallSignatures(); + export const getPropertiesImpl = (t: ts.Type): ts.Symbol[] => t.getProperties(); export const asInterfaceTypeImpl = (t: ts.ObjectType): ts.InterfaceType | null => { diff --git a/src/TypeScript/Testing/InMemory.js b/src/TypeScript/Testing/InMemory.js index 9047aa0..ec1637a 100644 --- a/src/TypeScript/Testing/InMemory.js +++ b/src/TypeScript/Testing/InMemory.js @@ -17,4 +17,4 @@ var bindCompilerHost = function (host) { }; }; exports.bindCompilerHost = bindCompilerHost; -//# sourceMappingURL=Testing.js.map \ No newline at end of file +//# sourceMappingURL=InMemory.js.map \ No newline at end of file diff --git a/src/TypeScript/Testing/InMemory.purs b/src/TypeScript/Testing/InMemory.purs index 3a6074c..214157e 100644 --- a/src/TypeScript/Testing/InMemory.purs +++ b/src/TypeScript/Testing/InMemory.purs @@ -66,7 +66,7 @@ handleMemoryFiles realHost inMemoryFiles = do host :: CompilerHost' host = realHost' - { fileExists = mkEffectFn1 \p -> + { fileExists = mkEffectFn1 \p -> do ((Array.any (eq p) paths) || _) <$> runEffectFn1 realHost'.fileExists p , getSourceFile = mkEffectFn2 \fileName scriptTarget -> do case find (eq fileName <<< _.fileName <<< Node.interface) sourceFiles of @@ -109,10 +109,12 @@ compilerHost opts = do let host :: CompilerHost' host = - { fileExists: mkEffectFn1 \p -> pure (Array.any (eq p <<< _.path) sourceFiles) - , directoryExists: opt $ mkEffectFn1 \d -> pure (d == "/") - , getCurrentDirectory: opt $ pure "/" - , getDirectories: opt $ mkEffectFn1 $ const (pure []) + { fileExists: mkEffectFn1 \p -> do + pure (Array.any (eq p <<< _.path) sourceFiles) + , directoryExists: opt $ mkEffectFn1 \d -> do + pure (d == "") + , getCurrentDirectory: opt $ pure "" + , getDirectories: opt $ mkEffectFn1 $ const (pure [""]) , getCanonicalFileName: mkEffectFn1 pure , getNewLine: pure "\n" , getDefaultLibFileName: mkEffectFn1 (const $ pure defaultLibFile) @@ -129,29 +131,3 @@ compilerHost opts = do } pure $ toCompilerHost host -type RootModule = String - - --- exportedNodes :: forall d t. Program -> List (SourceFile /\ List (Node ())) --- exportedNodes program visit = do --- let --- checker = getTypeChecker program --- rootNames = getRootFileNames program --- fileName = Node.interface >>> _.fileName --- rootFiles = Array.filter ((\fn -> fn `Array.elem` rootNames) <<< fileName) $ getSourceFiles program --- -- | `SourceFile` "usually" has as a single root child of type `SyntaxList`. --- -- | * We are not interested in this particular child. --- -- | * We should probably recurse into any container like --- -- node (`Block`, `ModuleDeclaration` etc.) down the stream too. --- rootFiles >>= traverse_ \sf -> do --- nodes <- for (getChildren sf >>= getChildren) \node -> do --- when (isNodeExported checker node) do --- pure node --- pure $ sf /\ nodes --- -- --- -- traceM $ "Reading node: " <> showSyntaxKind node --- -- case readDeclaration checker node visit of --- -- Just (fqn /\ d) -> modify_ (Map.insert fqn d) --- -- Nothing -> do --- -- traceM "Unable to parse node as declaration. Skipping node..." --- diff --git a/test/Compile.purs b/test/Compile.purs index 9401d1f..aae5ad7 100644 --- a/test/Compile.purs +++ b/test/Compile.purs @@ -9,6 +9,8 @@ import Data.Maybe (Maybe(..)) import Data.String (joinWith) as String import Data.Tuple (fst, snd) import Data.Tuple.Nested (type (/\)) +import Data.Undefined.NoProblem (Opt, (!)) +import Data.Undefined.NoProblem.Closed (class Coerce, coerce) as Closed import Data.Undefined.NoProblem.Closed (coerce) as NoProblem import Effect (Effect) import Effect.Aff (Aff) @@ -30,25 +32,32 @@ newtype TypeName = TypeName String type CompileOpts = { roots :: Array FilePath , modules :: Array InMemory.File + , strictNullChecks :: Opt Boolean } compile - :: CompileOpts + :: forall opts + . Closed.Coerce opts CompileOpts + => opts -> Effect Program compile opts = do let + opts' = Closed.coerce opts :: CompileOpts compilerOpts :: CompilerOptions compilerOpts = NoProblem.coerce - { module: moduleKind."CommonJS" + { module: moduleKind."ES2015" , target: scriptTarget."ES5" - , strictNullChecks: true + , strictNullChecks: opts'.strictNullChecks ! false } -- | We have to load es library because without it we are not -- | able to handle even `Array` type. - host <- liftEffect $ InMemory.compilerHost { files: opts.modules } - createProgram opts.roots compilerOpts (Just host) + host <- liftEffect $ InMemory.compilerHost { files: opts'.modules } + createProgram opts'.roots compilerOpts (Just host) +newtype StrictNullChecks = StrictNullChecks Boolean + +-- | Compile source code and extract particular type information. compileType :: TypeName -> SourceCode -> @@ -74,7 +83,11 @@ compileType (TypeName name) source = do <<< Map.toUnfoldableUnordered rootFile = InMemory.mkFile rootName source - program <- liftEffect $ compile { roots: [rootName], modules: [rootFile] } + program <- liftEffect $ compile + { roots: [rootName] + , modules: [rootFile] + , strictNullChecks: false + } let decls = readRootDeclarations program pure { type: snd <$> getType decls, program } diff --git a/test/Main.purs b/test/Main.purs index 2411ea3..a1ca79e 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -2,18 +2,14 @@ module Test.Main where import Prelude -import Data.Maybe (Maybe(..)) -import Data.Undefined.NoProblem.Closed (coerce) as NoProblem import Effect (Effect) -import Effect.Class (liftEffect) +import Test.MultiModule (suite) as Test.MultiModule import Test.NonRecType (suite) as Test.NonRecType import Test.RecType (suite) as Test.RecType import Test.Unit.Main (runTest) -import TypeScript.Class.Compiler.Program (createCompilerHost) -import TypeScript.Compiler.Program (createProgram) -import TypeScript.Compiler.Types (CompilerOptions, moduleKind, scriptTarget) main :: Effect Unit main = runTest do Test.NonRecType.suite Test.RecType.suite + Test.MultiModule.suite diff --git a/test/MultiModule.purs b/test/MultiModule.purs new file mode 100644 index 0000000..9f5573c --- /dev/null +++ b/test/MultiModule.purs @@ -0,0 +1,92 @@ +module Test.MultiModule where + +import Prelude + +import Data.Array (cons, singleton) as Array +import Data.Either (Either(..)) +import Data.Foldable (length) +import Data.Functor.Mu (Mu, roll) +import Data.Lens (_1, over) +import Data.List (List) +import Data.Map (fromFoldable) as Map +import Data.String (Pattern(..), Replacement(..), joinWith, replace) as String +import Data.Tuple.Nested (type (/\)) +import Effect.Aff (Aff) +import Effect.Class (liftEffect) +import Foreign.Object (fromHomogeneous, toUnfoldable) as Object +import ReadDTS.AST (TsType(..)) as AST +import ReadDTS.AST (TsType) +import ReadDTS.AST (types) as ReadDTS.AST +import Test.Compile (compile) +import Test.Unit (TestSuite, failure) +import Test.Unit (suite, test) as Test +import Test.Unit.Assert (shouldEqual) +import TypeScript.Compiler.Parser (SourceCode(..)) +import TypeScript.Compiler.Types (FullyQualifiedName(..)) +import TypeScript.Testing.InMemory (mkFile) + +testOnTypes + :: { modules :: Array + { path :: String + , source :: String + } + , roots :: Array String + } + -> (List (FullyQualifiedName /\ Mu (TsType FullyQualifiedName)) -> Aff Unit) + -> TestSuite +testOnTypes opts test = do + let + labelStep ({ path, source: source }) + = "/* " + <> path + <> " */" + <> String.replace (String.Pattern "\n") (String.Replacement ";") source + + label = String.joinWith ";" $ map labelStep opts.modules + + toFile { path, source } = mkFile path (SourceCode source) + opts' = opts + { modules = map toFile opts.modules + , roots = opts.roots + } + + Test.test label $ do + program <- liftEffect $ compile opts' + case ReadDTS.AST.types program of + Right types -> test types + Left err -> failure $ "FAILURE: " <> show err + +suite :: TestSuite +suite = Test.suite "Recursive type repr" do + let + fromHomogeneous + = (Map.fromFoldable :: Array _ -> _) + <<< map (over _1 FullyQualifiedName) + <<< Object.toUnfoldable + <<< Object.fromHomogeneous + + mkModule path source = { path, source } + + testFromRootTypes rootSource modules test = do + let + root = { source: rootSource, path: "Root.ts" } + testOnTypes { roots: ["Root.ts"], modules: Array.cons root modules } test + do + let + modules = Array.singleton $ mkModule + "A.ts" + "export type A = { p1: number, p2: string };" + + testFromRootTypes """import { A } from "A"; export type B = { a: A }""" modules \types -> do + shouldEqual 2 (length types) + Map.fromFoldable types `flip shouldEqual` fromHomogeneous + { "\"A\".A": roll $ AST.TsObject + [ { type: roll AST.TsNumber, name: "p1", optional: false } + , { type: roll AST.TsString, name: "p2", optional: false } + ] + , "\"Root\".B": roll $ AST.TsObject $ Array.singleton $ + { type: roll $ AST.TsTypeRef $ FullyQualifiedName "\"A\".A" + , name: "a" + , optional: false + } + } diff --git a/test/NonRecType.purs b/test/NonRecType.purs index 53c3a22..d97738f 100644 --- a/test/NonRecType.purs +++ b/test/NonRecType.purs @@ -53,7 +53,13 @@ suite = Test.suite "Non recursive ts type layer" do testXShouldEqual = testTypeShouldEqual (TypeName "X") testOnX = testOnType (TypeName "X") - testXShouldEqual "export type X = any" AST.TsAny + testXShouldEqual "export function X(nonOpt: number, opt?: string): number { return 0; }" $ + AST.TsFunction + [{ name: "nonOpt", type: unit, optional: false } + ,{ name: "opt", type: unit, optional: true } + ] + unit + testXShouldEqual "export type X = Array;" (AST.TsArray unit) testXShouldEqual "export type X = boolean" AST.TsBoolean testXShouldEqual "export type X = true" (AST.TsBooleanLiteral true) diff --git a/test/RecType.purs b/test/RecType.purs index 648007e..8397faf 100644 --- a/test/RecType.purs +++ b/test/RecType.purs @@ -13,7 +13,6 @@ import Data.String (joinWith) as String import Effect.Aff (Aff) import Matryoshka (cata) import ReadDTS.AST (TsType) -import ReadDTS.AST (unfoldType) as ReadDTS.AST import ReadDTS.AST as AST import Test.Compile (TypeName(..), compileType) import Test.Unit (TestSuite, failure) @@ -32,7 +31,7 @@ testOnType -> Aff Unit ) -> TestSuite -testOnType typeName source test = Test.test (source <> " /* rec version */") do +testOnType typeName source test = Test.test source do r <- compileType typeName (SourceCode source) case r of { type: Nothing } -> failure "Unable to find exported type X" @@ -41,7 +40,7 @@ testOnType typeName source test = Test.test (source <> " /* rec version */") do checker = getTypeChecker program stripTypeRefs = cata (Mu.In <<< lmap _.fullyQualifiedName) - case ReadDTS.AST.unfoldType checker { level: 0, ref: typ } of + case AST.unfoldType checker { level: 0, ref: typ } of Right (type'@(In _)) -> test { type: stripTypeRefs type', program } Left err -> failure $ "FAILURE: " <> show err @@ -150,17 +149,25 @@ suite = Test.suite "Recursive type repr" do , { name: "right", optional: false, type: roll $ AST.TsTypeRef fqn } ] ] - - - testXShouldEqual "export interface X{ m: { n: X }}" $ - roll $ AST.TsInterface $ Array.singleton - { name: "m" - , optional: false - , type: roll $ AST.TsObject $ Array.singleton - { name: "n" - , optional: false - , type: roll AST.TsNumber - } - } + testXShouldEqual "export function X(nonOpt: number, opt?: string): number { return 0; }" $ + roll $ AST.TsFunction + [{ name: "nonOpt", type: roll AST.TsNumber, optional: false } + ,{ name: "opt", type: roll AST.TsString, optional: true } + ] + (roll AST.TsNumber) + + -- | This fails but on our unfold recursion... + -- | It should probably anyway or maybe... + -- | we want to handle this invalid type? + -- testXShouldEqual "export interface X{ m: { n: X }}" $ + -- roll $ AST.TsInterface $ Array.singleton + -- { name: "m" + -- , optional: false + -- , type: roll $ AST.TsObject $ Array.singleton + -- { name: "n" + -- , optional: false + -- , type: roll AST.TsNumber + -- } + -- }