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

Typescript compilation fails #4371

Closed
ananni13 opened this issue Oct 15, 2022 · 25 comments
Closed

Typescript compilation fails #4371

ananni13 opened this issue Oct 15, 2022 · 25 comments

Comments

@ananni13
Copy link

ananni13 commented Oct 15, 2022

What version of Remix are you using?

1.7.2, Node v16.18.0, npm 8.19.2

Steps to Reproduce

Run npx create-remix@latest, pick Just the basics, any template, Typescript.

Run npx tsc --build inside the app folder.

Expected Behavior

Typescript compiles without errors.

Actual Behavior

Output with Remix App Server, Express, Architect, Fly, Netlify and Vercel templates:

Expand
node_modules/@remix-run/node/dist/upload/fileUploadHandler.d.ts:47:22 - error TS2420: Class 'NodeOnDiskFile' incorrectly implements interface 'File'.
  Property 'prototype' is missing in type 'NodeOnDiskFile' but required in type 'File'.

47 export declare class NodeOnDiskFile implements File {
                        ~~~~~~~~~~~~~~
  
  node_modules/typescript/lib/lib.dom.d.ts:2491:5
    2491     prototype: Blob;
             ~~~~~~~~~
    'prototype' is declared here.
  
  
Found 1 error.

Output with Cloudflare Pages and Workers templates:

Expand
node_modules/typescript/lib/lib.dom.d.ts:25:1 - error TS6200: Definitions of the following identifiers conflict with those in another file: AbortController, AbortSignal, Blob, BodyInit, ByteLengthQueuingStrategy, Cache, CacheStorage, CloseEvent, CountQueuingStrategy, Crypto, CryptoKey, DOMException, Event, EventListener, EventListenerOrEventListenerObject, EventTarget, File, FormData, Headers, HeadersInit, MessageEvent, Navigator, PromiseRejectionEvent, ReadableByteStreamController, ReadableStream, ReadableStreamBYOBReader, ReadableStreamBYOBRequest, ReadableStreamDefaultController, ReadableStreamDefaultReader, ReadableStreamReadResult, Request, Response, StreamPipeOptions, SubtleCrypto, TextDecoder, TextDecoderStream, TextEncoder, TextEncoderStream, TransformStream, start, transform, flush, URL, URLSearchParams, write, abort, close, pull, cancel, WebSocket, WebSocketEventMap, WritableStream, WritableStreamDefaultController, WritableStreamDefaultWriter, caches, console, crypto, navigator, origin, self

25 interface AddEventListenerOptions extends EventListenerOptions {
   ~~~~~~~~~

  node_modules/@cloudflare/workers-types/index.d.ts:4:1
    4 declare class AbortController {
      ~~~~~~~
    Conflicts are in this file.

node_modules/typescript/lib/lib.dom.d.ts:666:5 - error TS2687: All declarations of 'kty' must have identical modifiers.

666     kty?: string;
        ~~~

node_modules/typescript/lib/lib.dom.d.ts:918:5 - error TS2687: All declarations of 'data' must have identical modifiers.

918     data?: T;
        ~~~~

node_modules/typescript/lib/lib.dom.d.ts:1736:5 - error TS2687: All declarations of 'read' must have identical modifiers.

1736     read?: number;
         ~~~~

node_modules/typescript/lib/lib.dom.d.ts:1737:5 - error TS2687: All declarations of 'written' must have identical modifiers.

1737     written?: number;
         ~~~~~~~

node_modules/typescript/lib/lib.dom.d.ts:3653:11 - error TS2430: Interface 'Comment' incorrectly extends interface 'CharacterData'.
  Types of property 'after' are incompatible.
    Type '(content: Content, options?: ContentOptions | undefined) => Comment' is not assignable to type '(...nodes: (string | Node)[]) => void'.
      Types of parameters 'content' and 'nodes' are incompatible.
        Type 'string | Node' is not assignable to type 'Content'.
          Type 'Node' is not assignable to type 'Content'.

3653 interface Comment extends CharacterData {
               ~~~~~~~

node_modules/typescript/lib/lib.dom.d.ts:4621:101 - error TS2344: Type 'HTMLElementTagNameMap[K]' does not satisfy the constraint 'Element'.
  Type 'HTMLElement | HTMLAnchorElement | HTMLAreaElement | HTMLAudioElement | HTMLBaseElement | ... 57 more ... | HTMLPictureElement' is not assignable to type 'Element'.
    Type 'HTMLSelectElement' is not assignable to type 'Element'.
      Types of property 'remove' are incompatible.
        Type '{ (): void; (index: number): void; }' is not assignable to type '() => Element'.

4621     getElementsByTagName<K extends keyof HTMLElementTagNameMap>(qualifiedName: K): HTMLCollectionOf<HTMLElementTagNameMap[K]>;
                                                                                                         ~~~~~~~~~~~~~~~~~~~~~~~~

node_modules/typescript/lib/lib.dom.d.ts:4859:11 - error TS2430: Interface 'Element' incorrectly extends interface 'ChildNode'.
  Types of property 'after' are incompatible.
    Type '(content: Content, options?: ContentOptions | undefined) => Element' is not assignable to type '(...nodes: (string | Node)[]) => void'.
      Types of parameters 'content' and 'nodes' are incompatible.
        Type 'string | Node' is not assignable to type 'Content'.

4859 interface Element extends Node, ARIAMixin, Animatable, ChildNode, InnerHTML, NonDocumentTypeChildNode, ParentNode, Slottable {
               ~~~~~~~

node_modules/typescript/lib/lib.dom.d.ts:4859:11 - error TS2430: Interface 'Element' incorrectly extends interface 'ParentNode'.
  Types of property 'append' are incompatible.
    Type '(content: Content, options?: ContentOptions | undefined) => Element' is not assignable to type '(...nodes: (string | Node)[]) => void'.
      Types of parameters 'content' and 'nodes' are incompatible.
        Type 'string | Node' is not assignable to type 'Content'.

4859 interface Element extends Node, ARIAMixin, Animatable, ChildNode, InnerHTML, NonDocumentTypeChildNode, ParentNode, Slottable {
               ~~~~~~~

node_modules/typescript/lib/lib.dom.d.ts:4891:14 - error TS2687: All declarations of 'tagName' must have identical modifiers.

4891     readonly tagName: string;
                  ~~~~~~~

node_modules/typescript/lib/lib.dom.d.ts:4910:101 - error TS2344: Type 'HTMLElementTagNameMap[K]' does not satisfy the constraint 'Element'.
  Type 'HTMLElement | HTMLAnchorElement | HTMLAreaElement | HTMLAudioElement | HTMLBaseElement | ... 57 more ... | HTMLPictureElement' is not assignable to type 'Element'.

4910     getElementsByTagName<K extends keyof HTMLElementTagNameMap>(qualifiedName: K): HTMLCollectionOf<HTMLElementTagNameMap[K]>;
                                                                                                         ~~~~~~~~~~~~~~~~~~~~~~~~

node_modules/typescript/lib/lib.dom.d.ts:7769:11 - error TS2430: Interface 'HTMLSelectElement' incorrectly extends interface 'HTMLElement'.

7769 interface HTMLSelectElement extends HTMLElement {
               ~~~~~~~~~~~~~~~~~

node_modules/typescript/lib/lib.dom.d.ts:7769:11 - error TS2430: Interface 'HTMLSelectElement' incorrectly extends interface 'HTMLElement'.
  The types returned by 'remove()' are incompatible between these types.
    Type 'void' is not assignable to type 'Element'.

7769 interface HTMLSelectElement extends HTMLElement {
               ~~~~~~~~~~~~~~~~~

node_modules/typescript/lib/lib.dom.d.ts:13875:11 - error TS2430: Interface 'Text' incorrectly extends interface 'CharacterData'.
  Types of property 'after' are incompatible.
    Type '(content: Content, options?: ContentOptions | undefined) => Text' is not assignable to type '(...nodes: (string | Node)[]) => void'.
      Types of parameters 'content' and 'nodes' are incompatible.
        Type 'string | Node' is not assignable to type 'Content'.

13875 interface Text extends CharacterData, Slottable {
                ~~~~

node_modules/@types/react/index.d.ts:2854:78 - error TS2344: Type 'HTMLSelectElement' does not satisfy the constraint 'HTMLElement'.
  The types returned by 'remove()' are incompatible between these types.
    Type 'void' is not assignable to type 'Element'.

2854         select: DetailedHTMLFactory<SelectHTMLAttributes<HTMLSelectElement>, HTMLSelectElement>;
                                                                                  ~~~~~~~~~~~~~~~~~

node_modules/@cloudflare/workers-types/index.d.ts:4:1 - error TS6200: Definitions of the following identifiers conflict with those in another file: AbortController, AbortSignal, Blob, BodyInit, ByteLengthQueuingStrategy, Cache, CacheStorage, CloseEvent, CountQueuingStrategy, Crypto, CryptoKey, DOMException, Event, EventListener, EventListenerOrEventListenerObject, EventTarget, File, FormData, Headers, HeadersInit, MessageEvent, Navigator, PromiseRejectionEvent, ReadableByteStreamController, ReadableStream, ReadableStreamBYOBReader, ReadableStreamBYOBRequest, ReadableStreamDefaultController, ReadableStreamDefaultReader, ReadableStreamReadResult, Request, Response, StreamPipeOptions, SubtleCrypto, TextDecoder, TextDecoderStream, TextEncoder, TextEncoderStream, TransformStream, start, transform, flush, URL, URLSearchParams, write, abort, close, pull, cancel, WebSocket, WebSocketEventMap, WritableStream, WritableStreamDefaultController, WritableStreamDefaultWriter, caches, console, crypto, navigator, origin, self

4 declare class AbortController {
  ~~~~~~~

  node_modules/typescript/lib/lib.dom.d.ts:25:1
    25 interface AddEventListenerOptions extends EventListenerOptions {
       ~~~~~~~~~
    Conflicts are in this file.

node_modules/@cloudflare/workers-types/index.d.ts:108:12 - error TS2717: Subsequent property declarations must have the same type.  Property 'body' must be of type 'ReadableStream<Uint8Array> | null', but here has type 'ReadableStream<any> | null'.

108   readonly body: ReadableStream | null;
               ~~~~

  node_modules/typescript/lib/lib.dom.d.ts:2506:14
    2506     readonly body: ReadableStream<Uint8Array> | null;
                      ~~~~
    'body' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:517:3 - error TS2687: All declarations of 'tagName' must have identical modifiers.

517   tagName: string;
      ~~~~~~~

node_modules/@cloudflare/workers-types/index.d.ts:518:12 - error TS2717: Subsequent property declarations must have the same type.  Property 'attributes' must be of type 'NamedNodeMap', but here has type 'IterableIterator<string[]>'.

518   readonly attributes: IterableIterator<string[]>;
               ~~~~~~~~~~

  node_modules/typescript/lib/lib.dom.d.ts:4860:14
    4860     readonly attributes: NamedNodeMap;
                      ~~~~~~~~~~
    'attributes' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:520:12 - error TS2717: Subsequent property declarations must have the same type.  Property 'namespaceURI' must be of type 'string | null', but here has type 'string'.

520   readonly namespaceURI: string;
               ~~~~~~~~~~~~

  node_modules/typescript/lib/lib.dom.d.ts:4874:14
    4874     readonly namespaceURI: string | null;
                      ~~~~~~~~~~~~
    'namespaceURI' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:591:5 - error TS2315: Type 'EventListener' is not generic.

591 > = EventListener<EventType> | EventListenerObject<EventType>;
        ~~~~~~~~~~~~~~~~~~~~~~~~

node_modules/@cloudflare/workers-types/index.d.ts:599:14 - error TS2315: Type 'EventListenerOrEventListenerObject' is not generic.

599     handler: EventListenerOrEventListenerObject<EventMap[Type]>,
                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

node_modules/@cloudflare/workers-types/index.d.ts:604:14 - error TS2315: Type 'EventListenerOrEventListenerObject' is not generic.

604     handler: EventListenerOrEventListenerObject<EventMap[Type]>,
                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

node_modules/@cloudflare/workers-types/index.d.ts:862:3 - error TS2687: All declarations of 'kty' must have identical modifiers.

862   kty: string;
      ~~~

node_modules/@cloudflare/workers-types/index.d.ts:862:3 - error TS2717: Subsequent property declarations must have the same type.  Property 'kty' must be of type 'string | undefined', but here has type 'string'.

862   kty: string;
      ~~~

  node_modules/typescript/lib/lib.dom.d.ts:666:5
    666     kty?: string;
            ~~~
    'kty' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:1006:3 - error TS2687: All declarations of 'data' must have identical modifiers.

1006   data: ArrayBuffer | string;
       ~~~~

node_modules/@cloudflare/workers-types/index.d.ts:1006:3 - error TS2717: Subsequent property declarations must have the same type.  Property 'data' must be of type 'T | undefined', but here has type 'string | ArrayBuffer'.

1006   data: ArrayBuffer | string;
       ~~~~

  node_modules/typescript/lib/lib.dom.d.ts:918:5
    918     data?: T;
            ~~~~
    'data' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:1317:3 - error TS2717: Subsequent property declarations must have the same type.  Property 'redirect' must be of type 'RequestRedirect | undefined', but here has type 'string | undefined'.

1317   redirect?: string;
       ~~~~~~~~

  node_modules/typescript/lib/lib.dom.d.ts:1552:5
    1552     redirect?: RequestRedirect;
             ~~~~~~~~
    'redirect' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:1802:3 - error TS2687: All declarations of 'read' must have identical modifiers.

1802   read: number;
       ~~~~

node_modules/@cloudflare/workers-types/index.d.ts:1802:3 - error TS2717: Subsequent property declarations must have the same type.  Property 'read' must be of type 'number | undefined', but here has type 'number'.

1802   read: number;
       ~~~~

  node_modules/typescript/lib/lib.dom.d.ts:1736:5
    1736     read?: number;
             ~~~~
    'read' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:1803:3 - error TS2687: All declarations of 'written' must have identical modifiers.

1803   written: number;
       ~~~~~~~

node_modules/@cloudflare/workers-types/index.d.ts:1803:3 - error TS2717: Subsequent property declarations must have the same type.  Property 'written' must be of type 'number | undefined', but here has type 'number'.

1803   written: number;
       ~~~~~~~

  node_modules/typescript/lib/lib.dom.d.ts:1737:5
    1737     written?: number;
             ~~~~~~~
    'written' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:1890:3 - error TS2717: Subsequent property declarations must have the same type.  Property 'readableType' must be of type 'undefined', but here has type 'string | undefined'.

1890   readableType?: string;
       ~~~~~~~~~~~~

  node_modules/typescript/lib/lib.dom.d.ts:1770:5
    1770     readableType?: undefined;
             ~~~~~~~~~~~~
    'readableType' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:1891:3 - error TS2717: Subsequent property declarations must have the same type.  Property 'writableType' must be of type 'undefined', but here has type 'string | undefined'.

1891   writableType?: string;
       ~~~~~~~~~~~~

  node_modules/typescript/lib/lib.dom.d.ts:1773:5
    1773     writableType?: undefined;
             ~~~~~~~~~~~~
    'writableType' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:2001:3 - error TS2717: Subsequent property declarations must have the same type.  Property 'type' must be of type 'undefined', but here has type 'string | undefined'.

2001   type?: string;
       ~~~~

  node_modules/typescript/lib/lib.dom.d.ts:1798:5
    1798     type?: undefined;
             ~~~~
    'type' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:2009:3 - error TS2717: Subsequent property declarations must have the same type.  Property 'type' must be of type 'undefined', but here has type 'string | undefined'.

2009   type?: string;
       ~~~~

  node_modules/typescript/lib/lib.dom.d.ts:1806:5
    1806     type?: undefined;
             ~~~~
    'type' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:2024:33 - error TS2508: No base constructor has the specified number of type arguments.

2024 declare class WebSocket extends EventTarget<WebSocketEventMap> {
                                     ~~~~~~~~~~~

node_modules/@cloudflare/workers-types/index.d.ts:2048:50 - error TS2508: No base constructor has the specified number of type arguments.

2048 declare abstract class WorkerGlobalScope extends EventTarget<WorkerGlobalScopeEventMap> {}
                                                      ~~~~~~~~~~~

node_modules/@cloudflare/workers-types/index.d.ts:2093:12 - error TS2315: Type 'EventListenerOrEventListenerObject' is not generic.

2093   handler: EventListenerOrEventListenerObject<WorkerGlobalScopeEventMap[Type]>,
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

node_modules/@cloudflare/workers-types/index.d.ts:2130:12 - error TS2315: Type 'EventListenerOrEventListenerObject' is not generic.

2130   handler: EventListenerOrEventListenerObject<WorkerGlobalScopeEventMap[Type]>,
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


Found 40 errors.
@ananni13 ananni13 changed the title Typescript compilation fails with "Class 'NodeOnDiskFile' incorrectly implements interface 'File'" Typescript compilation fails Oct 15, 2022
@machour
Copy link
Collaborator

machour commented Oct 18, 2022

I successfully reproduced the issue with NodeFileOnDisk 👍

However, I'm not sure we will be able to fix errors from Cloudflare pages & workers, as they seem to emanate from the @cloudflare packages. Could you fill a report to the relevant repo?

@machour machour added bug Something isn't working and removed bug:unverified labels Oct 18, 2022
@depsimon
Copy link
Contributor

You can add "skipLibCheck": true, to your tsconfig.json

Though I'm not sure if it's a bad practice to ignore libs.

@akomm
Copy link

akomm commented Dec 16, 2022

@depsimon yes this is terrible as it affects not just libs, but simply all d.ts files, including those of your own project, not just node_modules or the default TS "libs".

@Arcath
Copy link

Arcath commented Dec 20, 2022

I'm getting this with a tsc -b on my machine.

node_modules/@remix-run/node/dist/upload/fileUploadHandler.d.ts:47:22 - error TS2420: Class 'NodeOnDiskFile' incorrectly implements interface 'File'.
  Property 'prototype' is missing in type 'NodeOnDiskFile' but required in type 'File'.

47 export declare class NodeOnDiskFile implements File {
                        ~~~~~~~~~~~~~~

  node_modules/typescript/lib/lib.dom.d.ts:2572:5
    2572     prototype: Blob;
             ~~~~~~~~~
    'prototype' is declared here.

Running TS 4.9.4 and Remix 1.9.0

@NutellaPlox
Copy link

A quick workaround I did was to just add define the property as null.

In node_modules/@remix-run/node/dist/upload/fileUploadHandler.d.ts you can just add the property prototype: null.

The class ends up looking something like:

export declare class NodeOnDiskFile implements File {
    prototype: null;
    private filepath;
    type: string;
    private slicer?;
    name: string;
    lastModified: number;
    webkitRelativePath: string;
    constructor(filepath: string, type: string, slicer?: {
        start: number;
        end: number;
    } | undefined);
    get size(): number;
    slice(start?: number, end?: number, type?: string): Blob;
    arrayBuffer(): Promise<ArrayBuffer>;
    stream(): ReadableStream<any>;
    stream(): NodeJS.ReadableStream;
    text(): Promise<string>;
    get [Symbol.toStringTag](): string;
}

@KerryRitter

This comment was marked as duplicate.

@pcattori pcattori self-assigned this Jan 9, 2023
@pcattori
Copy link
Contributor

pcattori commented Jan 9, 2023

Looking into this, the offending line is:

export declare class NodeOnDiskFile implements File {

which references the File interface from typescript/lib/lib.dom.d.ts. Looking at that:

/** A file-like object of immutable, raw data. Blobs represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system. */
interface Blob {
    readonly size: number;
    readonly type: string;
    arrayBuffer(): Promise<ArrayBuffer>;
    slice(start?: number, end?: number, contentType?: string): Blob;
    stream(): ReadableStream<Uint8Array>;
    text(): Promise<string>;
}

declare var Blob: {
    prototype: Blob;
    new(blobParts?: BlobPart[], options?: BlobPropertyBag): Blob;
};

// ...

/** Provides information about files and allows JavaScript in a web page to access their content. */
interface File extends Blob {
    readonly lastModified: number;
    readonly name: string;
    readonly webkitRelativePath: string;
}

declare var File: {
    prototype: File;
    new(fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): File;
};

What's weird is that the interfaces being referenced don't have a prototype field at all. Somehow TS is conflating the Blob interface with the eponymous Blob variable. As I understand it, TS shouldn't be conflating them, but should be able to determine that the interface (not the var declaration) is called for in implements File.


Workaround is to use "skipLibCheck": true in your tsconfig.json (like mentioned by @depsimon ).

@pcattori
Copy link
Contributor

pcattori commented Jan 9, 2023

Downgrading typescript to 4.8 fixes the issue for me. So seems likely to be a bug introduced sometime in 4.9.x.


Workaround is to force TS 4.8: npm install typescript@4.8

@akomm
Copy link

akomm commented Jan 9, 2023

@pcattori this is correct ts behavior.
Ty this:

// MyType.ts
export type MyType = typeof MyType
export const MyType = {
  foo: "bar"
}
// test.ts
import {MyType} from "./MyType"

function takesMyType(myType: MyType) {
  return MyType.foo
}

TS will distinguish between the type and value based on the whether it is used in type or value context.

@pcattori
Copy link
Contributor

pcattori commented Jan 9, 2023

@akomm right, TS should distinguish between type and value based on context. But in this case implements File indicates that File should be an interface, but then TS says in the error (and via the "Go To Definition" feature from the LSP) that its using the declare var File for that instead of the interface File.

@akomm
Copy link

akomm commented Jan 10, 2023

declare var File: {
    prototype: File;
    new(fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): File;
};

new Means File is a constructor, which is a "physical" thing, yet also represents a type, like this:

class MyConstructor {} // syntax sugar for the constructor function

console.log(MyConstructor)
console.log(MyConstructor.prototype)
console.log(MyConstructor.name)

interface Something extends MyConstructor {
}

Regarding the problem itself. The problem is that NodeFileOnDisk isn't really extending File properly on the type level, as it does not carry with the prototype chain. I don't know why it did not yell at you before, maybe they fixed the resolution here to detect this problem, hence only starts being a problem in 4.9.

It might be already enough to change from NodeOnDiskFile implements File to NodeOnDiskFile extends File and remove duplicated API that was previous "implemented", because if there is no other secondary problem this should require a constructor and "pick" the var File constructor` with the prototype.

@akomm
Copy link

akomm commented Jan 10, 2023

Changing to extends in my cases fixes the error.

Because you say implementsit expects that your implement the prototype property, which you did not. When you extend a constructor, TS will assume the prototype is now chained up properly and so it does not need to be implemented.

I think what TS changed is that if checks whether there is a constructor in the same namespace with the same name, to figure out its supposed to be extended to highlight the potentially previously done mistakes.

@pcattori
Copy link
Contributor

pcattori commented Jan 10, 2023

@akomm my main confusion is why TS is happy with NodeOnDiskFile implements File in the playground I posted earlier, but is not ok with the same code within a .d.ts file in node_modules/@remix-run/node.

Not sure why TS points to interface File as the definition for File in the playground, but to declare var File as the definition within node_mdoules/@remix-run/node/dist/upload/fileUploadHandler.ts.

Here's another playground that shows that TS normally uses the interface (not the declare var) definition when using implements: https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgBoCEA2B7ARsgbwChlTlcdcAuZAZzClAHMiBfIogEwgUzihQA3fmix4axMsgAOUbGHkBPaRBr1GIFlJAQA7gAoAlDQyU2HUJFiIUqAGLBMKCAA9IITrVGVCJMjEdVOgZmcy4ePgFkYSg0BycJP1JZeSUVGhAAVwBbXGgk5B0DYzjAsO5efhRK2i8AOWxuAHkQABFgWgBreJRgbOknbIhwL3tA3ykKcWCNLX9AtRDNAoB6FeRObBAAcjBCiAhOZAVkUF5M7mQAAxSFMGUIK-MgA

@akomm
Copy link

akomm commented Jan 10, 2023

I see the confusion. There might be a secondary problem. First, as explained it should be extends regardless. The implements problem was made visible with the secondary problem I assume:

TS Playground does not have all those ambient libraries you have in your code. There is just a preset of "lib" which you can't set in the tsconfig (or I did not find it) and also no files from node packages like @types/node.

Weird things can happen, when wrong global types are picked or declarations become merged.

At least one of those things I could find already:

// lim.dom.d.ts
interface File extends Blob {
    readonly lastModified: number;
    readonly name: string;
    readonly webkitRelativePath: string;
}

The Blob points to node:buffer Blob and not lib.dom.d.ts Blob.

By the way: did you check TS language service trace or just what the IDE makes up of it? There are sometimes multiple pointers to different contexts provided. For example the declaration of prototype: Blob in the error message points to the lib.dom.d.ts and not the node variant, while when inspecting the File extends Blob it resolves Blob to nodejs differently. Sometimes per interaction there are multiple messages that might cause confusion, because you see a path to one file, while also TS has resolved other path's, but the IDE does not show those or show them in different situations.

Another one with Blob:

// lib.dom.d.ts
declare var Blob: {
    prototype: Blob;
    new(blobParts?: BlobPart[], options?: BlobPropertyBag): Blob;
};

Also prototype: Blob points to node.

@akomm
Copy link

akomm commented Jan 10, 2023

Its basically a collision with @types/node/buffer.d.ts

@akomm
Copy link

akomm commented Jan 11, 2023

@pcattori

A small change in the TS Playground example makes the error appear.

I assume the Playground loads node library once it sees any usage of its core modules. You can change "buffer" with "path" or any other core module of node, the effect is the same.

@pcattori
Copy link
Contributor

@akomm I think the issue is that we do want to use implements but targeting the TS built-in lib.dom.d.ts definition of File (and Blob). extends is semantically different so we'd prefer not to use extends.

Talked to @jacob-ebey about this a couple days ago and we didn't know of a good way to disambiguate the different Files in the implements declaration, so the best workaround we came up with was to use extends and simply have a bunch of unimplemented or pass-through methods/properties. Like I said, not ideal and we'd prefer something that lets us use implements instead, but couldn't find any other workarounds or fixes.

sverhoeven added a commit to i-VRESSE/haddock3-webapp that referenced this issue Feb 28, 2023
Lower tsc version to workaround `remix-run/remix#4371
@cliffordfajardo
Copy link
Contributor

Confirming that Typescript 4.9+ is an issue

Steps to Reproduce

Created a fresh remix app. I chose the remix express template
Ran the npm run typecheck and it would fail if I used typescript 4.9.5

CleanShot 2023-03-10 at 12 13 32@2x

After downgrading to typescript 4.8.4 the typecheck command worked:
CleanShot 2023-03-10 at 12 14 37@2x

@uwuru
Copy link

uwuru commented Apr 5, 2023

If you don't want to downgrade Typescript but want to stop the error and get a nudge when this is fixed then put /* @ts-expect-error */ immediately before the line erroring and it will stop complaining. Once it is fixed and stops erroring, it will start complaining again (because there is no error anymore, as expected), just remove the @ts-expect-error and you have type checking back.

You can leave your self a reminder of why too if you like:

  /* @ts-expect-error Temporary workaround for https://github.com/remix-run/remix/issues/4371 */

Just be careful if you have multiple type checks on the next line as it will stop errors for any of the checks.

Reference: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-9.html#-ts-expect-error-comments

@lpsinger
Copy link
Contributor

What about defining prototype in the class?

export class NodeOnDiskFile implements File {
  prototype = {};
  name: string;
  ...
}

@pcattori
Copy link
Contributor

pcattori commented Jul 3, 2023

@lpsinger unfortunately, prototype = {}; is not enough. tsc will then complain that prototype is missing properties from Blob:

Screenshot 2023-07-03 at 1 28 20 PM

To recap for any onlookers, implements File does not require prototype, so any attempts to patch prototype are a workaround at best.

Looks like TS has included this regression as part of their 5.1.0 milestone, though not sure about the timing of that since 5.1.0 is already out? Maybe 5.1 milestone includes things to fix before 5.2? 🤷

@MichaelDeBoey
Copy link
Member

This is now fixed by #6108 and will be released in v2

@dkjym
Copy link

dkjym commented Dec 16, 2023

@MichaelDeBoey I think I'm still getting these issues on remix-run/cloudflare

@machour
Copy link
Collaborator

machour commented Dec 28, 2023

@dkjym Please open a new issue and give more details 🙏🏼

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