Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Add support for Mapped Types #124

Merged
merged 1 commit into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<PackageVersion Include="Feliz" Version="2.7.0" />
<PackageVersion Include="Feliz.Bulma" Version="3.0.0" />
<PackageVersion Include="FSharp.Core" Version="8.0.101" />
<PackageVersion Include="FsToolkit.ErrorHandling" Version="4.16.0" />
<PackageVersion Include="Glutinum.Chalk" Version="1.0.0" />
<PackageVersion Include="Glutinum.Feliz.Iconify" Version="2.0.0" />
<PackageVersion Include="Glutinum.IconifyIcons.Lucide" Version="1.0.0" />
Expand Down
10 changes: 10 additions & 0 deletions src/Glutinum.Converter.CLI/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"glutinum.converter": {
"type": "Project",
"dependencies": {
"FSToolkit.ErrorHandling": "[4.16.0, )",
"Fable.Core": "[4.3.0, )",
"Fable.Node": "[1.2.0, )",
"Fable.Promise": "[3.2.0, )",
Expand All @@ -57,6 +58,15 @@
"Fable.Core": "3.7.1"
}
},
"FsToolkit.ErrorHandling": {
"type": "CentralTransitive",
"requested": "[4.16.0, )",
"resolved": "4.16.0",
"contentHash": "GG4LtvJ/UkdyjGQUd+uPYN/g1GNlHyzMPDmwcnYvBn0V1dD4xJiiRE5N8UCgq+TSRQP11NNQUbzpU0zdxkrNbw==",
"dependencies": {
"FSharp.Core": "4.7.2"
}
},
"Glutinum.Chalk": {
"type": "CentralTransitive",
"requested": "[1.0.0, )",
Expand Down
16 changes: 15 additions & 1 deletion src/Glutinum.Converter/GlueAST.fs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,18 @@ type GlueUtilityType =
| Record of GlueRecord
// | ReadOnly of GlueType

type GlueMappedType =
{
TypeParameter: GlueTypeParameter
Type: GlueType option
}

type GlueIndexedAccessType =
{
IndexType: GlueType
ObjectType: GlueType
}

[<RequireQualifiedAccess>]
type GlueType =
| Discard
Expand All @@ -278,7 +290,7 @@ type GlueType =
| Union of GlueTypeUnion
| Literal of GlueLiteral
| KeyOf of GlueType
| IndexedAccessType of GlueType
| IndexedAccessType of GlueIndexedAccessType
| ModuleDeclaration of GlueModuleDeclaration
| ClassDeclaration of GlueClassDeclaration
| TypeReference of GlueTypeReference
Expand All @@ -295,6 +307,7 @@ type GlueType =
| ExportDefault of GlueType
| TemplateLiteral
| UtilityType of GlueUtilityType
| MappedType of GlueMappedType

member this.Name =
match this with
Expand Down Expand Up @@ -340,3 +353,4 @@ type GlueType =
match utilityType with
| GlueUtilityType.Partial _
| GlueUtilityType.Record _ -> "obj"
| MappedType _ -> "obj"
4 changes: 3 additions & 1 deletion src/Glutinum.Converter/Glutinum.Converter.fsproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>0.1.0-alpha-001</Version>
Expand Down Expand Up @@ -27,10 +26,12 @@
<Compile Include="Reader/Node.fs" />
<Compile Include="Reader/Parameters.fs" />
<Compile Include="Reader/TypeAliasDeclaration.fs" />
<Compile Include="Reader/TypeQueryNode.fs" />
<Compile Include="Reader/TypeNode.fs" />
<Compile Include="Reader/TypeOperatorNode.fs" />
<Compile Include="Reader/TypeParameters.fs" />
<Compile Include="Reader/UnionTypeNode.fs" />
<Compile Include="Reader/MappedTypeNode.fs" />
<Compile Include="Reader/VariableStatement.fs" />
<Compile Include="Reader/ExportAssignment.fs" />
<Compile Include="Reader/Documentation.fs" />
Expand All @@ -47,6 +48,7 @@
<PackageReference Include="Fable.Core" />
<PackageReference Include="Fable.Node" />
<PackageReference Include="Fable.Promise" />
<PackageReference Include="FSToolkit.ErrorHandling" />
<PackageReference Include="Glutinum.Chalk" />
</ItemGroup>
</Project>
38 changes: 33 additions & 5 deletions src/Glutinum.Converter/Reader/IndexedAccessType.fs
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,52 @@ let readIndexedAccessType
: GlueType
=

let nodeType = declaration.indexType :?> Ts.TypeNode
let indexType =
let idxNodeType = declaration.indexType :?> Ts.TypeNode

let typ =
match nodeType.kind with
match idxNodeType.kind with
| Ts.SyntaxKind.TypeOperator ->
let typeOperatorNode = declaration.indexType :?> Ts.TypeOperatorNode
reader.ReadTypeOperatorNode typeOperatorNode

| Ts.SyntaxKind.NumberKeyword ->
let numberKeywordNode = declaration.indexType :?> Ts.KeywordTypeNode
reader.ReadTypeNode(numberKeywordNode)

| unsupported ->
let warning =
Report.readerError (
"readIndexedAccessType",
$"Unsupported node kind {unsupported.Name}",
idxNodeType
)

reader.Warnings.Add warning

GlueType.Discard

let objectType =
let objNodeType = declaration.objectType :?> Ts.TypeNode

match objNodeType.kind with
| Ts.SyntaxKind.ParenthesizedType ->
let pType = declaration.objectType :?> Ts.ParenthesizedTypeNode
reader.ReadTypeNode pType

| unsupported ->
let warning =
Report.readerError (
"readIndexedAccessType",
$"Unsupported node kind %s{unsupported.Name}",
nodeType
objNodeType
)

reader.Warnings.Add warning

GlueType.Discard

GlueType.IndexedAccessType typ
GlueType.IndexedAccessType
{
IndexType = indexType
ObjectType = objectType
}
42 changes: 42 additions & 0 deletions src/Glutinum.Converter/Reader/MappedTypeNode.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module Glutinum.Converter.Reader.MappedTypeNode

open Glutinum.Converter.GlueAST
open Glutinum.Converter.Reader.Types
open TypeScript
open FsToolkit.ErrorHandling

let readMappedTypeNode
(reader: ITypeScriptReader)
(mappedTypeNode: Ts.MappedTypeNode)
: GlueType
=
result {
let! typParam =
// TODO: Make a single reader.ReadTypeParameter method
let typeParameters =
reader.ReadTypeParameters(
Some(ResizeArray([ mappedTypeNode.typeParameter ]))
)

match typeParameters with
| [ tp ] -> Ok tp
| _ ->
Report.readerError (
"readMappedTypeNode",
$"Expected exactly one type parameter but was {List.length typeParameters}",
mappedTypeNode
)
|> Error

return
{
TypeParameter = typParam
Type = mappedTypeNode.``type`` |> Option.map reader.ReadNode
}
|> GlueType.MappedType
}
|> function
| Ok glueType -> glueType
| Error warning ->
reader.Warnings.Add warning
GlueType.Discard
2 changes: 2 additions & 0 deletions src/Glutinum.Converter/Reader/Node.fs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ let readNode (reader: ITypeScriptReader) (node: Ts.Node) : GlueType =

| Ts.SyntaxKind.ExportDeclaration -> GlueType.Discard

| Ts.SyntaxKind.BooleanKeyword -> reader.ReadTypeNode(node :?> Ts.TypeNode)

| unsupported ->
let warning =
Report.readerError (
Expand Down
4 changes: 4 additions & 0 deletions src/Glutinum.Converter/Reader/TypeAliasDeclaration.fs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ let readTypeAliasDeclaration
let declaration = declaration.``type`` :?> Ts.IndexedAccessType
reader.ReadIndexedAccessType declaration

| Ts.SyntaxKind.MappedType ->
let mappedTypeNode = declaration.``type`` :?> Ts.MappedTypeNode
reader.ReadMappedTypeNode mappedTypeNode

| _ -> reader.ReadTypeNode declaration.``type``

{
Expand Down
11 changes: 6 additions & 5 deletions src/Glutinum.Converter/Reader/TypeNode.fs
Original file line number Diff line number Diff line change
Expand Up @@ -219,11 +219,8 @@ let readTypeNode
|> GlueType.FunctionType

| Ts.SyntaxKind.TypeQuery ->
let typeNodeQuery = typeNode :?> Ts.TypeQueryNode

let typ = checker.getTypeAtLocation !!typeNodeQuery.exprName

readTypeUsingFlags reader typ
let typeQueryNode = typeNode :?> Ts.TypeQueryNode
TypeQueryNode.readTypeQueryNode reader typeQueryNode

| Ts.SyntaxKind.LiteralType ->
let literalTypeNode = typeNode :?> Ts.LiteralTypeNode
Expand Down Expand Up @@ -392,6 +389,10 @@ let readTypeNode

| Ts.SyntaxKind.TemplateLiteralType -> GlueType.TemplateLiteral

| Ts.SyntaxKind.IndexedAccessType ->
let indexedAccessType = typeNode :?> Ts.IndexedAccessType
reader.ReadIndexedAccessType indexedAccessType

| _ ->
Report.readerError (
"type node",
Expand Down
153 changes: 153 additions & 0 deletions src/Glutinum.Converter/Reader/TypeQueryNode.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
module Glutinum.Converter.Reader.TypeQueryNode

open Glutinum.Converter.GlueAST
open Glutinum.Converter.Reader.Types
open TypeScript
open Fable.Core.JsInterop
open Glutinum.Converter.Reader.Utils
open FsToolkit.ErrorHandling

let readTypeQueryNode
(reader: ITypeScriptReader)
(typeQueryNode: Ts.TypeQueryNode)
=

let checker = reader.checker
let typ = checker.getTypeAtLocation !!typeQueryNode.exprName

match typ.flags with
| HasTypeFlags Ts.TypeFlags.Object ->
// This is safe as both cases have a `kind` field
let exprNameKind: Ts.SyntaxKind = typeQueryNode.exprName?kind

match typ.getSymbol (), exprNameKind with
| None, Ts.SyntaxKind.Identifier ->

let exprName: Ts.Identifier = !!typeQueryNode.exprName

result {
let! aliasSymbol =
checker.getSymbolAtLocation exprName
|> Result.requireSome (
Report.readerError (
"type node (TypeQuery)",
"Missing symbol",
typeQueryNode
)
)

let! declarations =
aliasSymbol.declarations
|> Result.requireSome (
Report.readerError (
"type node (TypeQuery)",
"Missing declarations",
typeQueryNode
)
)

let! declaration =
if declarations.Count <> 1 then
Report.readerError (

"type node (TypeQuery)",
"Expected exactly one declaration",
typeQueryNode
)
|> Error

else
Ok(declarations.[0])

let! variableDeclaration =
match declaration.kind with
| Ts.SyntaxKind.VariableDeclaration ->
Ok(declaration :?> Ts.VariableDeclaration)

| unsupported ->
Report.readerError (
"type node (TypeQuery)",
$"Unsupported declaration kind {unsupported.Name}",
typeQueryNode
)
|> Error

let! typeNode =
variableDeclaration.``type``
|> Result.requireSome (
Report.readerError (
"type node (TypeQuery)",
"Missing type",
typeQueryNode
)
)

match typeNode.kind with
| Ts.SyntaxKind.TypeOperator ->
let typeOperatorNode = typeNode :?> Ts.TypeOperatorNode
return reader.ReadTypeOperatorNode typeOperatorNode

| unsupported ->
return!
Report.readerError (
"type node (TypeQuery)",
$"Unsupported declaration kind {unsupported.Name}",
typeQueryNode
)
|> Error

}
|> function
| Ok glueType -> glueType
| Error warning ->
reader.Warnings.Add warning
GlueType.Discard

| None, _ ->
let warning =
Report.readerError (
"type node (TypeQuery)",
"Expected an Identifier",
typeQueryNode
)

reader.Warnings.Add warning
GlueType.Primitive GluePrimitive.Any

| Some symbol, _ ->
// Try to find the declaration of the type, to get more information about it
match symbol.declarations with
| Some declarations ->
let declaration = declarations.[0]

match declaration.kind with
| Ts.SyntaxKind.ClassDeclaration ->
{
Name = symbol.name
Constructors = []
Members = []
TypeParameters = []
HeritageClauses = []
}
|> GlueType.ClassDeclaration

// We don't support TypeQuery for ModuleDeclaration yet
// See https://github.com/glutinum-org/cli/issues/70 for a possible solution
| Ts.SyntaxKind.ModuleDeclaration -> GlueType.Discard
| _ -> reader.ReadNode declaration

| None -> GlueType.Primitive GluePrimitive.Any

| HasTypeFlags Ts.TypeFlags.String ->
GlueType.Primitive GluePrimitive.String

| HasTypeFlags Ts.TypeFlags.Number ->
GlueType.Primitive GluePrimitive.Number

| HasTypeFlags Ts.TypeFlags.Boolean -> GlueType.Primitive GluePrimitive.Bool

| HasTypeFlags Ts.TypeFlags.Any -> GlueType.Primitive GluePrimitive.Any

| HasTypeFlags Ts.TypeFlags.Void -> GlueType.Primitive GluePrimitive.Unit

| _ -> GlueType.Primitive GluePrimitive.Any
Loading
Loading