Repository: TypeScript
Issue: Locally scoped & typed jsxFactory
Labels: Suggestion,Awaiting More Feedback

# Suggestion

<!--
  Please fill in each section completely. Thank you!
-->

## 🔍 Search Terms

<!--
  💡 Did you know? TypeScript has over 2,000 open suggestions!
  🔎 Please search thoroughly before logging new feature requests as most common ideas already have a proposal in progress.
  The "Common Feature Requests" section of the FAQ lists many popular requests: https://github.com/Microsoft/TypeScript/wiki/FAQ#common-feature-requests
  
  

  Replace the text below:
-->

jsx jsxFactory

I found related tickets, but this ticket information is very long winded and I believe this requires its own ticket:
- https://github.com/microsoft/TypeScript/issues/21699 
- https://github.com/microsoft/TypeScript/issues/14729 
- https://github.com/microsoft/TypeScript/issues/15217 
- https://github.com/microsoft/TypeScript/issues/29376

## ✅ Viability Checklist

<!--
   Suggestions that don't meet all these criteria are very, very unlikely to be accepted.
   We always recommend reviewing the TypeScript design goals before investing time writing
   a proposal for ideas outside the scope of the project.
-->
My suggestion meets these guidelines:

* [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals). 

> This would be a type level only change, updating existing behaviour to be more inline with direct local scope types instead of reliance on global scope, for this reason, I believe all of the above items are true. 

## ⭐ Suggestion

<!-- A summary of what you'd like to see added or changed -->

Type completion from a defined `jsxFactory` function definition 

## 📃 Motivating Example

<!--
  If you were announcing this feature in a blog post, what's a short explanation that shows
  a developer why this feature improves the language?
-->

Here is a type definition covering the internal process that occurs within the defined `createNode` function in the same file. 

Sample:
```typescript
interface CreateNodeFn<
  O extends object = object,
  S = Source<O>,
  C extends VNodeRepresentationSource = VNodeRepresentationSource
> {
  <TO extends O, S extends CreateNodeFragmentSourceFirstStage>(source: S, options?: TO, ...children: C[]): FragmentVNode & {
    source: S;
    options: TO;
  };
}
```
[GitHub Link](https://github.com/opennetwork/vnode/blob/8f4433f00852396027a73ed6711e8d1826bee5fc/src/create-node.ts#L46-L110)

The linked type definition allows for complex types to be defined, including static type resolution for leaves or scalar values:

```typescript
it.concurrent.each<[SourceReference]>([
  [Symbol("Unique Symbol")],
  [true],
  [false],
  [1],
  [0],
  [1n],
  [0n],
  [""],
  ["Hello!"],
])("%p should produce a scalar node", async <I extends SourceReference>(input: I ) => {
  const output = createNode(input);
  expect(isScalarVNode(output)).toEqual(true);
  const source: I = output.source;
  expect(source).toEqual(input);
});
```
[GitHub Link](https://github.com/opennetwork/vnode/blob/8f4433f00852396027a73ed6711e8d1826bee5fc/src/create-node.spec.ts#L42-L57)

From the perspective of a consumer, all of the above values will produce an object that contains `source` that matches the original `input` value. These nodes will never produce children so the type definition accounts for this by using 

```typescript
{
  children: never;
}
```

This is done by this section of the definition

```typescript
interface CreateNodeFn<
  O extends object = object,
  S = Source<O>,
  C extends VNodeRepresentationSource = VNodeRepresentationSource
> {
<TO extends O, S extends SourceReference>(source: S): VNode & {
    source: S;
    options: never;
    scalar: true;
    children: never;
  };
  <TO extends O, S extends SourceReference>(source: S, options?: TO): VNode & {
    source: S;
    options: TO;
    scalar: true;
    children: never;
  };
  <TO extends O, S extends SourceReference>(source: S, options?: TO, ...children: C[]): VNode & {
    source: S;
    options: TO;
    scalar: false;
  };
}
```

From a type perspective, the the values that are known by the caller are returned in the return type. We also get whether or not `children` is available (or the other side of this, whether the node is a `scalar` node, where no `children` were passed)

Given `jsxFactory` can be typed using this function, then `children` itself can also be typed 

`children` not typed:

```typescript
interface CreateNodeFn<
  O extends object = object,
  S = Source<O>,
  C extends VNodeRepresentationSource = VNodeRepresentationSource
> {
  <TO extends O, S extends CreateNodeFragmentSourceFirstStage>(source: S, options?: TO, ...children: C[]): 
  FragmentVNode & {
    source: S;
    options: TO;
  }
}
```
[GitHub Link](https://github.com/opennetwork/vnode/blob/8f4433f00852396027a73ed6711e8d1826bee5fc/src/create-node.ts#L67-L70)

`children` typed:

```typescript
interface CreateNodeFn<
  O extends object = object,
  S = Source<O>,
  C extends VNodeRepresentationSource = VNodeRepresentationSource
> {
  <TO extends O, S extends CreateNodeFragmentSourceFirstStage, TC extends C = C>(source: S, options?: TO, ...children: TC[]): FragmentVNode & {
    source: S;
    options: TO;
    children: AsyncIterable<ResolveNodeType<TC>[]>;
  };
}
```

Given the above type was implemented, these types would be able to:

- Know what children will be yielded at type level, meaning this extract this statically using ts-morph or another tool. 
- Restrict at compile time input state machines, if the input types are restricted for a node it could mean a full restriction in types throughout an entire structure. Typescript itself could eliminate code based on `never` alone
- Given the previous point, at development time this can be utilised while typing to improve some dev ex as side processes could be emulating these restrictions. 

While the above code is scalar values, the static leaves of a state tree, functions can be fully typed as well... only if the first point is true. 

While it would make this definition vastly more complex, I believe it still is possible from a pure type perspective

Currently a function returns a fragment node, which produces only state through `children`.


```typescript
it.concurrent.each<[CreateNodeFragmentSourceFirstStage]>([
  [() => {}],
  [Promise.resolve()],
  [Fragment],
])("%p should produce a fragment node", async (input) => {
  const output: FragmentVNode = createNode(input);
  expect(isFragmentVNode(output)).toEqual(true);
});
```
[GitHub Link](https://github.com/opennetwork/vnode/blob/8f4433f00852396027a73ed6711e8d1826bee5fc/src/create-node.spec.ts#L30)

The matching types for functions and promises currently drop the type of `children` once passed. 

```typescript
export type CreateNodeFragmentSourceFirstStage =
  | Function
  | Promise<unknown>
  | typeof Fragment;
  
interface CreateNodeFn<
  O extends object = object,
  S = Source<O>,
  C extends VNodeRepresentationSource = VNodeRepresentationSource
> {
  <TO extends O, S extends CreateNodeFragmentSourceFirstStage>(source: S, options?: TO, ...children: C[]): 
  FragmentVNode & {
    source: S;
    options: TO;
  };
}
```

The retain these we need to type `children` only additionally:

```typescript
export type CreateNodeFragmentSourceFirstStage =
  | Function
  | Promise<unknown>
  | typeof Fragment;
  
interface CreateNodeFn<
  O extends object = object,
  S = Source<O>,
  C extends VNodeRepresentationSource = VNodeRepresentationSource
> {
  <TO extends O, S extends CreateNodeFragmentSourceFirstStage, TC extends C = C>(source: S, options?: TO, ...children: TC[]): FragmentVNode & {
    source: S;
    options: TO;
    children: AsyncIterable<ResolveNodeType<TC>[]>;
  };
}
```

Because the types of all children nodes will be typed by the time they are defined as vnodes, all types will be complete. 

From a top level at this point we would be able to statically resolve a complete type set from a tree of components. 

## 💻 Use Cases

<!--
  What do you want to use this for?
  What shortcomings exist with current approaches?
  What workarounds are you using in the meantime?
-->

### Local `string` types:

https://github.com/microsoft/TypeScript/issues/15217

### Imported `string | number | bigint | symbol | boolean` types:

`vnode` defines "token" types, which allows definition of a partial component with no implementation. 

```typescript
<OfferCatalog>
    <Product
      name="This is my name 1"
      sku="SKU 123"
    >
        <Brand name="Some brand" />
    </Product>
    <Product
      name="This is my name 2"
      sku="SKU 124"
    >
        <Brand name="Some brand" />
    </Product>
    <Product
      name="This is my name 3"
      sku="SKU 125"
    >
        <Brand name="Some other brand" />
    </Product>
</OfferCatalog>
<Order
  identifier="1"
  orderDate={new Date()}
>
    <Invoice identifier="2313132">
        <PaymentMethod identifier="123243234" />
    </Invoice>
    <Product
      sku="SKU 125"
    />
    <DeliveryMethod identifier="123122222">
        <Country name="New Zealand" />
    </DeliveryMethod>
</Order>
```
[GitHub Link](https://github.com/opennetwork/jsx-anything-example/blob/301e408c8c45271519aa139cb4abebb13374223f/src/contents/index.tsx#L15-L48)

This can be used to directly create a set of jsx based components without implementation, if these were fully typed, this could be read statically excluding the value of `Date` (while still knowing it was a `Date`)

```typescript
<scxml>

    <datamodel>
        <data id="eventStamp"/>
        <data id="rectX" expr="0"/>
        <data id="rectY" expr="0"/>
        <data id="dx"/>
        <data id="dy"/>
    </datamodel>

    <state id="idle">
        <transition event="mousedown" target="dragging">
            <assign location="eventStamp" expr="_event.data"/>
        </transition>
    </state>

    <state id="dragging">
        <transition event="mouseup" target="idle"/>
        <transition event="mousemove" target="dragging">
            <assign location="dx" expr="eventStamp.clientX - _event.data.clientX"/>
            <assign location="dy" expr="eventStamp.clientY - _event.data.clientY"/>
            <assign location="rectX" expr="rectX - dx"/>
            <assign location="rectY" expr="rectY - dy"/>
            <assign location="eventStamp" expr="_event.data"/>
        </transition>
    </state>

</scxml>
```
[GitHub Link](https://github.com/opennetwork/vscxml/blob/8b6699004c5bceef2e8e3b0335d0fb1279296f2f/src/example.tsx#L5-L32)

The above code is a jsx depending on types defined within the same module. 

```typescript
scxml: SCXMLAttributes;
datamodel: DataModelAttributes;
data: DataAttributes;
state: StateAttributes;
transition: TransitionAttributes;
assign: AssignAttributes;
```
[GitHub Link](https://github.com/opennetwork/vscxml/blob/8b6699004c5bceef2e8e3b0335d0fb1279296f2f/src/jsx.ts#L37-L42)


Actions can also be defined statically, which if typed, would be readable statically. 

```typescript
export const Action: ActionNode = createToken(ActionSymbol);
```
[GitHub Link](https://github.com/opennetwork/vview/blob/61bd9e198cb4c056fe71b05d50634d345c8c7cf4/src/index.ts#L42)

## Related Tickets

Given the implementation of https://github.com/microsoft/TypeScript/issues/34319   an implementor will be able to use 

```
function h(source, options, ...children) implements CreateNodeFn;
```

Which can then be restricted by providing generics.