microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
99.06k stars 12.29k forks source link

Module augmentation seems to break entirely a class definition/body IntelliSense autocomplete. Debug failure (exception on executing command "completionInfo"). #58907

Open synulux opened 2 weeks ago

synulux commented 2 weeks ago

🔎 Search Terms

"debug failure completionInfo", "module augmentation breaks autocomplete classes"

🕗 Version & Regression Information

⏯ Playground Link

https://www.typescriptlang.org/play/?#code/JYWwDg9gTgLgBAIgCbAM4GNpIHQCtUIDcAUKJLHAN5wDCEIIAhgHZJwC+cAZlPYgAKpGYMAAtgUAKYB6HoxCSA7tADWRYsXQAbRqlRwACsGYBzOgxZtJADxiTW+801ZVicd3GnTDUvXADaNAAqAEoAMnAA1HAAygYAgjQAogC6cDAQcKiSknCMAK4ZmOBaknbYcACS8NmSIPoZcMpQKnAQzFoAnnDGcEGdYJIx6FDAYPBgOp0mvPmsADRwAEaFPVxwnRD56VDdMOL6QgpZ6PaMo5m9AGoxdEiS824eXumi9j01OfXpmcwQNaIIIo8sxuggnJYEHBtLp9Ap9hAkPoAGRwMC8QawYCSVCLFbwYAArZaHBPdzEdhAA

💻 Code

import "discord.js";
import { Command } from "@sapphire/framework";

class PingCommand extends Command {
    // Press [CTRL + SPACE] to see autocomplete. It seems to work only in TypeScript playground, 
    // but if you try this same scenario in VSCode, then it seems to not show any "Command" class 
    // methods & properties, but it should.

}

🙁 Actual behavior

In VSCode, pressing CTRL + SPACE inside the class definition doesn't show the extended class methods & properties, but it should, as it works with any other extended class.


🙂 Expected behavior

VSCode should've shown in the IntelliSense autocomplete the extended class methods & properties, as it seems to do so in the TypeScript playground.

Additional information about the issue

The Command class in the @sapphire/framework package extends a lot of classes; following a hierarchy, goes like this: Command extends AliasPiece, AliasPiece extends Piece.

Before I continue, first I'd like to recall that the Command class is located in another module (@sapphire/framework), and the AliasPiece and Piece classes are located in another module (@sapphire/pieces).

If I remove in the AliasPiece class the extending Piece class, then the autocomplete seems to work as expected, therefore, we can assume that there's something in the Piece class that's causing the issue. And there is.

The Piece command has a get accessor called container that has the type of Container. image

This Container type (to be specific, interface) has the following body: image

The Container type is located in the @sapphire/pieces package. However, if we continue looking, we can see that the @sapphire/framework package types does module augmentation to that Container type: image

Deleting that Container interface in the module augmentation seems to fix the issue with the autocomplete (but obviously, we don't want to delete that interface as it is important to use module augmentation in this case).

Therefore, I came with the conclusion that maybe this could be an issue with module augmentation feature.

I took a look at logs and couldn't find anything related to module augmentation, but at least there's an error that happens exactly when I do CTRL + SPACE in the class definition, specifying that this is a "debug failure"? Unsure how TypeScript server works behind the scenes, but what I find weird is that this works in TypeScript playground and not in VSCode...

Andarist commented 2 weeks ago
/// <reference path="fourslash.ts" />

// @module: nodenext

// @Filename: /node_modules/@sapphire/pieces/index.d.ts
//// interface Container {
////   stores: unknown;
//// }
////
//// declare class Piece {
////   get container(): Container;
//// }
////
//// declare class AliasPiece extends Piece {}
////
//// export { AliasPiece, type Container };

// @Filename: /node_modules/@sapphire/framework/index.d.ts
//// import { AliasPiece } from "@sapphire/pieces";
////
//// declare class Command extends AliasPiece {}
////
//// declare module "@sapphire/pieces" {
////   interface Container {
////     client: unknown;
////   }
//// }
////
//// export { Command };

// @Filename: /index.ts
//// import "@sapphire/pieces";
//// import { Command } from "@sapphire/framework";
//// class PingCommand extends Command {
////   /*1*/
//// }

const preferences = {
  includeCompletionsWithClassMemberSnippets: true,
  includeCompletionsWithInsertText: true,
};

verify.completions({
  marker: "1",
  includes: [
    {
      name: "container",
      insertText: "get container(): Container {\n}",
      filterText: "container",
      hasAction: true,
      source: "ClassMemberSnippet/",
    },
  ],
  preferences,
  isNewIdentifierLocation: true,
});

verify.applyCodeActionFromCompletion("1", {
  name: "container",
  source: "ClassMemberSnippet/",
  description: `Includes imports of types referenced by 'container'`,
  newFileContent: `import "@sapphire/pieces";
import { Command } from "@sapphire/framework";
import { Container } from "@sapphire/pieces";
class PingCommand extends Command {

}`,
  preferences,
});