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

Error: Unknown node "Primitive" (ts.SyntaxKind = 162) in specific Type example #1790

Open
Philzen opened this issue Oct 8, 2023 · 8 comments

Comments

@Philzen
Copy link

Philzen commented Oct 8, 2023

Initially i though the type i wanted to import simply had a too complex module hierarchy and i'd be missing something.

I wanted to use { RegisterOptions } from 'react-hook-forms' and after i found out with type packages i was missing and how to configure a tsconfig.ts with typeRoots is ended up with an error message. So i extracted the type into a single file and still ended up with the same error message as below:

Error: Unknown node "Primitive" (ts.SyntaxKind = 162) at …path…/validation.d.ts(110,72)
Full content of `validation.d.ts`:

/* eslint-disable prettier/prettier */
/* eslint-disable @typescript-eslint/no-explicit-any */

/**
 * Type which can be used to index an array or tuple type.
 */
type ArrayKey = number;

/**
 * Type which eagerly collects all paths through a type which point to an array
 * type.
 * @typeParam T - type which should be introspected.
 * @example
 * ```
 * Path<{foo: {bar: string[], baz: number[]}}> = 'foo.bar' | 'foo.baz'
 * ```
 */
type ArrayPath<T> = T extends any ? ArrayPathInternal<T> : never;


/**
 * Helper type for recursively constructing paths through a type.
 * This actually constructs the strings and recurses into nested
 * object types.
 *
 * See {@link ArrayPath}
 */
type ArrayPathImpl<K extends string | number, V, TraversedTypes> = V extends Primitive | BrowserNativeObject ? IsAny<V> extends true ? string : never : V extends ReadonlyArray<infer U> ? U extends Primitive | BrowserNativeObject ? IsAny<V> extends true ? string : never : true extends AnyIsEqual<TraversedTypes, V> ? never : `${K}` | `${K}.${ArrayPathInternal<V, TraversedTypes | V>}` : true extends AnyIsEqual<TraversedTypes, V> ? never : `${K}.${ArrayPathInternal<V, TraversedTypes | V>}`;

/**
 * Helper type for recursively constructing paths through a type.
 * This obscures the internal type param TraversedTypes from exported contract.
 *
 * See {@link ArrayPath}
 */
type ArrayPathInternal<T, TraversedTypes = T> = T extends ReadonlyArray<infer V> ? IsTuple<T> extends true ? {
  [K in TupleKeys<T>]-?: ArrayPathImpl<K & string, T[K], TraversedTypes>;
}[TupleKeys<T>] : ArrayPathImpl<ArrayKey, V, TraversedTypes> : {
  [K in keyof T]-?: ArrayPathImpl<K & string, T[K], TraversedTypes>;
}[keyof T];


type BrowserNativeObject = Date | FileList | File;

type Primitive = null | undefined | string | number | boolean | symbol | bigint;

/**
 * Helper function to break apart T1 and check if any are equal to T2
 *
 * See {@link IsEqual}
 */
type AnyIsEqual<T1, T2> = T1 extends T2 ? IsEqual<T1, T2> extends true ? true : never : never;

type InternalFieldName = string;

/**
 * Checks whether the type is any
 * See {@link https://stackoverflow.com/a/49928360/3406963}
 * @typeParam T - type which may be any
 * ```
 * IsAny<any> = true
 * IsAny<string> = false
 * ```
 */
type IsAny<T> = 0 extends 1 & T ? true : false;

/**
 * Type to query whether an array type T is a tuple type.
 * @typeParam T - type which may be an array or tuple
 * @example
 * ```
 * IsTuple<[number]> = true
 * IsTuple<number[]> = false
 * ```
 */
type IsTuple<T extends ReadonlyArray<any>> = number extends T['length'] ? false : true;

/**
 * Checks whether T1 can be exactly (mutually) assigned to T2
 * @typeParam T1 - type to check
 * @typeParam T2 - type to check against
 * ```
 * IsEqual<string, string> = true
 * IsEqual<'foo', 'foo'> = true
 * IsEqual<string, number> = false
 * IsEqual<string, number> = false
 * IsEqual<string, 'foo'> = false
 * IsEqual<'foo', string> = false
 * IsEqual<'foo' | 'bar', 'foo'> = boolean // 'foo' is assignable, but 'bar' is not (true | false) -> boolean
 * ```
 */
type IsEqual<T1, T2> = T1 extends T2 ? (<G>() => G extends T1 ? 1 : 2) extends <G>() => G extends T2 ? 1 : 2 ? true : false : false;

type Message = string;

type FieldPath<TFieldValues extends FieldValues> = Path<TFieldValues>
/**
 * See {@link PathValue}
 */
type FieldPathValue<TFieldValues extends FieldValues, TFieldPath extends FieldPath<TFieldValues>> = PathValue<TFieldValues, TFieldPath>
type FieldValues = Record<string, any>

/**
 * Helper type for recursively constructing paths through a type.
 * This actually constructs the strings and recurses into nested
 * object types.
 *
 * See {@link Path}
 */
type PathImpl<K extends string | number, V, TraversedTypes> = V extends Primitive | BrowserNativeObject ? `${K}` : true extends AnyIsEqual<TraversedTypes, V> ? `${K}` : `${K}` | `${K}.${PathInternal<V, TraversedTypes | V>}`;
/**
 * Helper type for recursively constructing paths through a type.
 * This obscures the internal type param TraversedTypes from exported contract.
 *
 * See {@link Path}
 */
type PathInternal<T, TraversedTypes = T> = T extends ReadonlyArray<infer V> ? IsTuple<T> extends true ? {
  [K in TupleKeys<T>]-?: PathImpl<K & string, T[K], TraversedTypes>;
}[TupleKeys<T>] : PathImpl<ArrayKey, V, TraversedTypes> : {
  [K in keyof T]-?: PathImpl<K & string, T[K], TraversedTypes>;
}[keyof T];

/**
 * Type which eagerly collects all paths through a type
 * @typeParam T - type which should be introspected
 * @example
 * ```
 * Path<{foo: {bar: string}}> = 'foo' | 'foo.bar'
 * ```
 */
type Path<T> = T extends any ? PathInternal<T> : never;

/**
 * Type to evaluate the type which the given path points to.
 * @typeParam T - deeply nested type which is indexed by the path
 * @typeParam P - path into the deeply nested type
 * @example
 * ```
 * PathValue<{foo: {bar: string}}, 'foo.bar'> = string
 * PathValue<[number, string], '1'> = string
 * ```
 */
type PathValue<T, P extends Path<T> | ArrayPath<T>> = T extends any ? P extends `${infer K}.${infer R}` ? K extends keyof T ? R extends Path<T[K]> ? PathValue<T[K], R> : never : K extends `${ArrayKey}` ? T extends ReadonlyArray<infer V> ? PathValue<V, R & Path<V>> : never : never : P extends keyof T ? T[P] : P extends `${ArrayKey}` ? T extends ReadonlyArray<infer V> ? V : never : never : never;

/**
 * Type which given a tuple type returns its own keys, i.e. only its indices.
 * @typeParam T - tuple type
 * @example
 * ```
 * TupleKeys<[number, string]> = '0' | '1'
 * ```
 */
type TupleKeys<T extends ReadonlyArray<any>> = Exclude<keyof T, keyof any[]>;

type ValidationValue = boolean | number | string | RegExp
type ValidationRule<TValidationValue extends ValidationValue = ValidationValue> = TValidationValue | ValidationValueMessage<TValidationValue>
type ValidationValueMessage<TValidationValue extends ValidationValue = ValidationValue> = {
  value: TValidationValue
  message: Message
}
type ValidateResult = Message | Message[] | boolean | undefined
type Validate<TFieldValue, TFormValues> = (
  value: TFieldValue,
  formValues: TFormValues
) => ValidateResult | Promise<ValidateResult>

export type RegisterOptions<
  TFieldValues extends FieldValues = FieldValues,
  TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = Partial<{
  required: Message | ValidationRule<boolean>
  min: ValidationRule<number | string>
  max: ValidationRule<number | string>
  maxLength: ValidationRule<number>
  minLength: ValidationRule<number>
  validate:
    | Validate<FieldPathValue<TFieldValues, TFieldName>, TFieldValues>
    | Record<
        string,
        Validate<FieldPathValue<TFieldValues, TFieldName>, TFieldValues>
      >
  value: FieldPathValue<TFieldValues, TFieldName>
  setValueAs: (value: any) => any
  shouldUnregister?: boolean
  onChange?: (event: any) => void
  onBlur?: (event: any) => void
  disabled: boolean
  deps: InternalFieldName | InternalFieldName[]
}> &
  (
    | {
        pattern?: ValidationRule<RegExp>
        valueAsNumber?: false
        valueAsDate?: false
      }
    | {
        pattern?: undefined
        valueAsNumber?: false
        valueAsDate?: true
      }
    | {
        pattern?: undefined
        valueAsNumber?: true
        valueAsDate?: false
      }
  )

As you can see, the type Primitive is clearly defined in line 45. The whole file validates fine in VSCode, all types are well defined. Is there anything else i may be doing wrong?

@Philzen
Copy link
Author

Philzen commented Oct 8, 2023

I have also tried --no-type-check as recommended in #1013 to no avail. Same as there typescript-json-schema works fine on the same file, however i do prefer the results from ts-json-schema-generator.

@domoritz
Copy link
Member

domoritz commented Oct 8, 2023

This could be some issue with recursiveness. Unfortunately we'll need a more specific repo with a minimal example for anyone to be a me to fix this.

I'll close the issue but feel free to continue commenting on it here.

@domoritz domoritz closed this as not planned Won't fix, can't repro, duplicate, stale Oct 8, 2023
@Philzen
Copy link
Author

Philzen commented Oct 8, 2023

@domoritz There you go:

https://github.com/Philzen/ts-json-schema-generator-bug-1790

I included both examples, just run npm install or yarn (tried both) and c&p any of the two example commands.

Kindly reopen this issue once you've confirmed this is an unresolved bug.

@domoritz
Copy link
Member

domoritz commented Oct 15, 2023

Thank you for the repo. Can you make it minimal by removing all types that are not contributing to the bug. This will greatly help anyone who is going to try to debug this issue. I would recommend deleting types until the bug disappears. I'll reopen for now but note that the repro is not minimal yet.

@Philzen
Copy link
Author

Philzen commented Oct 21, 2023

@domoritz
Copy link
Member

Validation still has a lot of types.

Have you been able to narrow down the issue?

@Philzen
Copy link
Author

Philzen commented Oct 23, 2023

@domoritz As far as i can tell, not a single type can be removed from ’Validation’ – the type is composed from them and it was a considerable amount of work to pull them together in a single file from the referenced react lib. At the end of the day, the use case would be to reference the type from the original lib directly, that file was merely a way of narrowing it down and trying to reproduce it with a local extract of it.

@domoritz
Copy link
Member

Absolutely. Hopefully this repro helps someone narrow down the issue to find a fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants