Skip to content

Commit

Permalink
feat: add support for Omit<Type, Keys>
Browse files Browse the repository at this point in the history
[converter]

=== changelog ===
```ts
export interface Todo {
    title: string;
    description: string;
    completed: boolean;
    createdAt: number;
}

export type TodoPreview = Omit<Todo, "description">;
```

```fs
[<AllowNullLiteral>]
[<Interface>]
type Todo =
    abstract member title: string with get, set
    abstract member description: string with get, set
    abstract member completed: bool with get, set
    abstract member createdAt: float with get, set

[<AllowNullLiteral>]
[<Interface>]
type TodoPreview =
    abstract member title: string with get, set
    abstract member completed: bool with get, set
    abstract member createdAt: float with get, set
```
=== changelog ===
  • Loading branch information
MangelMaxime committed Nov 30, 2024
1 parent 73d6cd7 commit 33955e3
Show file tree
Hide file tree
Showing 20 changed files with 348 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/Glutinum.Converter/GlueAST.fs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ type GlueUtilityType =
| Record of GlueRecord
| ReturnType of GlueType
| ThisParameterType of GlueType
| Omit of GlueMember list

type GlueMappedType =
{
Expand Down
87 changes: 85 additions & 2 deletions src/Glutinum.Converter/Reader/TypeNode.fs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ module UtilityType =
|> GlueUtilityType.ReturnType
|> GlueType.UtilityType

let thisParameterType
let readThisParameterType
(reader: ITypeScriptReader)
(typeReferenceNode: Ts.TypeReferenceNode)
=
Expand All @@ -182,6 +182,88 @@ module UtilityType =
|> GlueUtilityType.ThisParameterType
|> GlueType.UtilityType

let readOmit
(reader: ITypeScriptReader)
(typeReferenceNode: Ts.TypeReferenceNode)
=

let keysToOmitType =
typeReferenceNode.typeArguments.Value[1]
|> reader.checker.getTypeFromTypeNode

let tryReadValueOfKeys (typ: Ts.Type) =
match Type.StringLiteral.tryReadValue typ with
| Some value -> Some value
| None ->
Report.readerError (
"keysToOmit",
"Expected a string literal",
typeReferenceNode
)
|> reader.Warnings.Add

None

let keysToOmit =
if keysToOmitType.isUnion () then
(keysToOmitType :?> Ts.UnionOrIntersectionType).types
|> Seq.choose tryReadValueOfKeys
else
tryReadValueOfKeys keysToOmitType
|> Option.map Seq.singleton
|> Option.defaultValue []

let baseType =
typeReferenceNode.typeArguments.Value[0]
|> reader.checker.getTypeFromTypeNode

let baseProperties =
match baseType.flags with
| HasTypeFlags Ts.TypeFlags.Any ->
Report.readerError (
"omit base type",
"Was not able to resolve the base type, and defaulting to any. If the base type is defined, in another file, please make sure to include it in the input files",
typeReferenceNode
)
|> reader.Warnings.Add

ResizeArray []
| _ -> baseType |> reader.checker.getPropertiesOfType

let filteredProperties =
baseProperties
|> Seq.filter (fun prop ->
not (keysToOmit |> Seq.contains prop.name)
)
|> Seq.toList

let members =
filteredProperties
|> List.choose (fun property ->
match property.declarations with
| Some declarations ->
if declarations.Count = 1 then
Some(reader.ReadDeclaration declarations.[0])
else
Report.readerError (
"type node",
"Expected exactly one declaration",
typeReferenceNode
)
|> reader.Warnings.Add

None
| None ->
Report.readerError (
"type node",
"Missing declarations",
typeReferenceNode
)
|> failwith
)

members |> GlueUtilityType.Omit |> GlueType.UtilityType

let readTypeNode
(reader: ITypeScriptReader)
(typeNode: Ts.TypeNode)
Expand Down Expand Up @@ -230,7 +312,8 @@ let readTypeNode
| "ReturnType" ->
UtilityType.readReturnType reader typeReferenceNode
| "ThisParameterType" ->
UtilityType.thisParameterType reader typeReferenceNode
UtilityType.readThisParameterType reader typeReferenceNode
| "Omit" -> UtilityType.readOmit reader typeReferenceNode
| _ -> readTypeReference true
else
readTypeReference false
Expand Down
1 change: 0 additions & 1 deletion src/Glutinum.Converter/Reader/TypeQueryNode.fs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ let readTypeQueryNode
let! declaration =
if declarations.Count <> 1 then
Report.readerError (

"type node (TypeQuery)",
"Expected exactly one declaration",
typeQueryNode
Expand Down
12 changes: 12 additions & 0 deletions src/Glutinum.Converter/Reader/Utils.fs
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,15 @@ let isFromEs5Lib (symbolOpt: Ts.Symbol option) =

sourceFile.fileName.EndsWith("lib/lib.es5.d.ts")
| _ -> false

module Type =

module StringLiteral =

let tryReadValue (literalType: Ts.Type) =
match literalType.flags with
| HasTypeFlags Ts.TypeFlags.StringLiteral ->
let literalType = literalType :?> Ts.LiteralType

Some(unbox<string> literalType.value)
| _ -> None
41 changes: 41 additions & 0 deletions src/Glutinum.Converter/Transform.fs
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,29 @@ let rec private transformType
| GlueUtilityType.ThisParameterType innerType ->
transformType context innerType

| GlueUtilityType.Omit members ->
{
Attributes =
[
FSharpAttribute.AllowNullLiteral
FSharpAttribute.Interface
]
Name = context.CurrentScopeName
OriginalName = context.CurrentScopeName
TypeParameters = []
Members = TransformMembers.toFSharpMember context members
Inheritance = []
}
|> FSharpType.Interface
|> context.ExposeType

({
Name = context.FullName
TypeParameters = []
}
: FSharpMapped)
|> FSharpType.Mapped

| GlueType.TypeAliasDeclaration typeAliasDeclaration ->
({
Name = typeAliasDeclaration.Name
Expand Down Expand Up @@ -2140,6 +2163,24 @@ let private transformTypeAliasDeclaration
| GlueUtilityType.ThisParameterType innerType ->
transformType context innerType |> makeTypeAlias

| GlueUtilityType.Omit members ->
{
Attributes =
[
FSharpAttribute.AllowNullLiteral
FSharpAttribute.Interface
]
Name = typeAliasName
OriginalName = glueTypeAliasDeclaration.Name
TypeParameters =
transformTypeParameters
context
glueTypeAliasDeclaration.TypeParameters
Members = TransformMembers.toFSharpMember context members
Inheritance = []
}
|> FSharpType.Interface

| GlueType.FunctionType functionType ->
{
Attributes =
Expand Down
6 changes: 6 additions & 0 deletions src/Glutinum.Web/Pages/Editors.GlueAST.GlueASTViewer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,12 @@ type GlueASTViewer =
[ GlueASTViewer.Type thisParameterType ]
context

| GlueUtilityType.Omit members ->
ASTViewer.renderNode
"Omit"
(members |> List.map GlueASTViewer.GlueMember)
context

| GlueType.ThisType thisTypeInfo ->
ASTViewer.renderNode
"ThisType"
Expand Down
11 changes: 11 additions & 0 deletions tests/specs/references/omit/intersectionType.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
interface Todo {
title: string;
description: string;
}

interface TodoExtra {
completed: boolean;
createdAt: number;
}

type TodoPreview = Omit<Todo & TodoExtra, "description">;
29 changes: 29 additions & 0 deletions tests/specs/references/omit/intersectionType.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module rec Glutinum

open Fable.Core
open Fable.Core.JsInterop
open System

[<AllowNullLiteral>]
[<Interface>]
type Todo =
abstract member title: string with get, set
abstract member description: string with get, set

[<AllowNullLiteral>]
[<Interface>]
type TodoExtra =
abstract member completed: bool with get, set
abstract member createdAt: float with get, set

[<AllowNullLiteral>]
[<Interface>]
type TodoPreview =
abstract member title: string with get, set
abstract member completed: bool with get, set
abstract member createdAt: float with get, set

(***)
#r "nuget: Fable.Core"
#r "nuget: Glutinum.Types"
(***)
8 changes: 8 additions & 0 deletions tests/specs/references/omit/mutiple.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface Todo {
title: string;
description: string;
completed: boolean;
createdAt: number;
}

export type TodoInfo = Omit<Todo, "completed" | "createdAt">;
24 changes: 24 additions & 0 deletions tests/specs/references/omit/mutiple.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module rec Glutinum

open Fable.Core
open Fable.Core.JsInterop
open System

[<AllowNullLiteral>]
[<Interface>]
type Todo =
abstract member title: string with get, set
abstract member description: string with get, set
abstract member completed: bool with get, set
abstract member createdAt: float with get, set

[<AllowNullLiteral>]
[<Interface>]
type TodoInfo =
abstract member title: string with get, set
abstract member description: string with get, set

(***)
#r "nuget: Fable.Core"
#r "nuget: Glutinum.Types"
(***)
6 changes: 6 additions & 0 deletions tests/specs/references/omit/nonExistingProperty.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface Todo {
completed: boolean;
createdAt: number;
}

export type TodoInfo = Omit<Todo, "completed" | "remove-me-but-i-don-t-exist">;
21 changes: 21 additions & 0 deletions tests/specs/references/omit/nonExistingProperty.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module rec Glutinum

open Fable.Core
open Fable.Core.JsInterop
open System

[<AllowNullLiteral>]
[<Interface>]
type Todo =
abstract member completed: bool with get, set
abstract member createdAt: float with get, set

[<AllowNullLiteral>]
[<Interface>]
type TodoInfo =
abstract member createdAt: float with get, set

(***)
#r "nuget: Fable.Core"
#r "nuget: Glutinum.Types"
(***)
1 change: 1 addition & 0 deletions tests/specs/references/omit/nonExistingType.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type MapboxOverlayProps = Omit<DeckProps, 'width'>
15 changes: 15 additions & 0 deletions tests/specs/references/omit/nonExistingType.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module rec Glutinum

open Fable.Core
open Fable.Core.JsInterop
open System

[<AllowNullLiteral>]
[<Interface>]
type MapboxOverlayProps =
interface end

(***)
#r "nuget: Fable.Core"
#r "nuget: Glutinum.Types"
(***)
6 changes: 6 additions & 0 deletions tests/specs/references/omit/resultInEmptyInterface.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface Todo {
completed: boolean;
createdAt: number;
}

export type TodoInfo = Omit<Todo, "completed" | "createdAt">;
21 changes: 21 additions & 0 deletions tests/specs/references/omit/resultInEmptyInterface.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module rec Glutinum

open Fable.Core
open Fable.Core.JsInterop
open System

[<AllowNullLiteral>]
[<Interface>]
type Todo =
abstract member completed: bool with get, set
abstract member createdAt: float with get, set

[<AllowNullLiteral>]
[<Interface>]
type TodoInfo =
interface end

(***)
#r "nuget: Fable.Core"
#r "nuget: Glutinum.Types"
(***)
8 changes: 8 additions & 0 deletions tests/specs/references/omit/simple.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface Todo {
title: string;
description: string;
completed: boolean;
createdAt: number;
}

export type TodoPreview = Omit<Todo, "description">;
25 changes: 25 additions & 0 deletions tests/specs/references/omit/simple.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module rec Glutinum

open Fable.Core
open Fable.Core.JsInterop
open System

[<AllowNullLiteral>]
[<Interface>]
type Todo =
abstract member title: string with get, set
abstract member description: string with get, set
abstract member completed: bool with get, set
abstract member createdAt: float with get, set

[<AllowNullLiteral>]
[<Interface>]
type TodoPreview =
abstract member title: string with get, set
abstract member completed: bool with get, set
abstract member createdAt: float with get, set

(***)
#r "nuget: Fable.Core"
#r "nuget: Glutinum.Types"
(***)
Loading

0 comments on commit 33955e3

Please sign in to comment.