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

Cannot extend info.languageService asynchronously with createAsyncLanguageServicePlugin #257

Open
mizdra opened this issue Dec 26, 2024 · 0 comments

Comments

@mizdra
Copy link

mizdra commented Dec 26, 2024

Summary

I am developing TypeScript Language Service Plugin with createAsyncLanguageServicePlugin. I want to extend info.languageService to add language features. For example, I want to always return the keyword "Hello" from getCompletionsAtPosition.

However, when I do that, tsserver doesn't seem to load the extended functionality.

Reproduction

repository: https://github.com/mizdra/repro-volar-setup-with-async-language-service

  1. Execute npm i in the terminal.
  2. Open this repository in VS Code.
  3. Open example/index.ts in the editor.
  4. When "Would you like to use the workspace TypeScript version for TypeScript and JavaScript language features?" prompt appears, select "Allow".
  5. Press Ctrl+Space on example/index.ts and check the completion items

src/index.cjs:

const { proxyLanguageService } = require('./language-service.cjs');
const { createAsyncLanguageServicePlugin } = require('@volar/typescript/lib/quickstart/createAsyncLanguageServicePlugin.js');
const ts = require('typescript');

module.exports = createAsyncLanguageServicePlugin([], ts.ScriptKind.TS, async (ts, info) => {
  if (info.project.projectKind !== ts.server.ProjectKind.Configured) {
    return { languagePlugins: [] };
  }

  return {
    languagePlugins: [],
    setup: (language) => {
      info.languageService = proxyLanguageService(info.languageService);
    },
  };
});

src/language-service.cjs:

const ts = require('typescript');

/**
 * @param {import('typescript/lib/tsserverlibrary').LanguageService} languageService 
 * @returns {import('typescript/lib/tsserverlibrary').LanguageService}
 */
exports.proxyLanguageService = function proxyLanguageService(
  languageService,
) {
  /** @type {import('typescript/lib/tsserverlibrary').LanguageService} */
  const proxy = Object.create(null);
  for (const k of Object.keys(languageService)) {
    // @ts-ignore
    const x = (languageService)[k];
    // @ts-ignore
    proxy[k] = (...args) => x.apply(languageService, args);
  }

  proxy.getCompletionsAtPosition = (fileName, position, options) => {
    const prior = languageService.getCompletionsAtPosition(fileName, position, options) ?? {
      isGlobalCompletion: false,
      isMemberCompletion: false,
      isNewIdentifierLocation: false,
      entries: [],
    };
    prior.entries.push({
      name: 'Hello',
      kind: ts.ScriptElementKind.keyword,
      sortText: '0',
    });
    return prior;
  };
  return proxy;
}

Expected behavior

The completion items contain "Hello".

Actual behavior

The completion items do not contain "Hello".

image

Additional context

Apparently, extending info.languageService asynchronously reproduces the problem. If I extend it synchronously as follows, the problem does not reproduce.

--- a/src/index.cjs
+++ b/src/index.cjs
@@ -6,11 +6,11 @@ module.exports = createAsyncLanguageServicePlugin([], ts.ScriptKind.TS, async (t
   if (info.project.projectKind !== ts.server.ProjectKind.Configured) {
     return { languagePlugins: [] };
   }
+  info.languageService = proxyLanguageService(info.languageService);
 
   return {
     languagePlugins: [],
     setup: (language) => {
-      info.languageService = proxyLanguageService(info.languageService);
     },
   };
 });
image

However, if I insert asynchronous operations as follows, the problem reproduces.

--- a/src/index.cjs
+++ b/src/index.cjs
@@ -1,3 +1,4 @@
+const { setTimeout } = require('node:timers/promises');
 const { proxyLanguageService } = require('./language-service.cjs');
 const { createAsyncLanguageServicePlugin } = require('@volar/typescript/lib/quickstart/createAsyncLanguageServicePlugin.js');
 const ts = require('typescript');
@@ -6,11 +7,12 @@ module.exports = createAsyncLanguageServicePlugin([], ts.ScriptKind.TS, async (t
   if (info.project.projectKind !== ts.server.ProjectKind.Configured) {
     return { languagePlugins: [] };
   }
+  await setTimeout(0);
+  info.languageService = proxyLanguageService(info.languageService);
 
   return {
     languagePlugins: [],
     setup: (language) => {
-      info.languageService = proxyLanguageService(info.languageService);
     },
image

Environment

  • VSCode: 1.96.2
  • OS: Darwin arm64 24.1.0
  • @volar/typescript: 2.4.11
  • @volar/language-core: 2.4.11
@mizdra mizdra changed the title Cannot extend info.languageService with createAsyncLanguageServicePlugin Cannot extend info.languageService asynchronously with createAsyncLanguageServicePlugin Dec 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant