Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Server error "Could not find source file" on opening second file in a project with large amount of code #803

Closed
@amagee

Description

@amagee

In a project with about 20MB of Javascript / Typescript code, opening the second file will reliably trigger an error looking like this:

Error: Could not find source file: '/path/file1.ts'.
    at getValidSourceFile (/path/node_modules/typescript/lib/tsserver.js:143801:22)
    at Object.getEncodedSemanticClassifications3 [as getEncodedSemanticClassifications] (/path/node_modules/typescript/lib/tsserver.js:144328:77)
    at IpcIOSession.getEncodedSemanticClassifications (/path/node_modules/typescript/lib/tsserver.js:183203:41)
    at encodedSemanticClassifications-full (/path/node_modules/typescript/lib/tsserver.js:182505:43)
    at /path/node_modules/typescript/lib/tsserver.js:184838:69
    at IpcIOSession.executeWithRequestId (/path/node_modules/typescript/lib/tsserver.js:184830:14)
    at IpcIOSession.executeCommand (/path/node_modules/typescript/lib/tsserver.js:184838:29)
    at IpcIOSession.onMessage (/path/node_modules/typescript/lib/tsserver.js:184880:51)
    at process.<anonymous> (/path/node_modules/typescript/lib/tsserver.js:186461:14)
    at process.emit (node:events:517:28)
    at emit (node:internal/child_process:944:14)
    at process.processTicksAndRejections (node:internal/process/task_queues:83:21)

even though the file exists.

In my case this size of code existed because we had a large amount of vendor code in dist directories which we had not excluded via the tsconfig. I'm mostly interested to see if the error reporting can be improved.

I put together a standalone script which generates two "legit" files, file1.ts and file2.ts, and a huge file garbage-0.js (39MB). It then opens typescript-language-server and plays back the commands I recorded when I was experiencing this problem from my editor (Neovim).

Saving test.js and tsconfig.json to an empty directory and running node test.js should be sufficient to see the error and stack trace in the output.

Here is the script test.js.

const {spawn} = require("child_process");
const fs = require("fs");

async function main() {
  const commands = [
    {
      method: "initialize",
      jsonrpc: "2.0",
      params: {
        trace: "off",
        rootPath: __dirname,
        rootUri: `file://${__dirname}`,
        workspaceFolders: [
          {
            name: __dirname,
            uri: `file://${__dirname}`,
          },
        ],
        initializationOptions: { hostInfo: "neovim" },
        capabilities: {
          workspace: {
            symbol: {
              symbolKind: {
                valueSet: [
                  1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
                  19, 20, 21, 22, 23, 24, 25, 26,
                ],
              },
              dynamicRegistration: false,
            },
            semanticTokens: { refreshSupport: true },
            configuration: true,
            applyEdit: true,
            inlayHint: { refreshSupport: true },
            workspaceFolders: true,
            workspaceEdit: {
              resourceOperations: ["rename", "create", "delete"],
            },
            didChangeWatchedFiles: {
              relativePatternSupport: true,
              dynamicRegistration: true,
            },
          },
          general: { positionEncodings: ["utf-16"] },
          window: {
            showMessage: {
              messageActionItem: { additionalPropertiesSupport: false },
            },
            showDocument: { support: true },
            workDoneProgress: true,
          },
          textDocument: {
            documentHighlight: { dynamicRegistration: false },
            documentSymbol: {
              hierarchicalDocumentSymbolSupport: true,
              symbolKind: {
                valueSet: [
                  1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
                  19, 20, 21, 22, 23, 24, 25, 26,
                ],
              },
              dynamicRegistration: false,
            },
            synchronization: {
              willSave: true,
              willSaveWaitUntil: true,
              didSave: true,
              dynamicRegistration: false,
            },
            codeAction: {
              isPreferredSupport: true,
              dataSupport: true,
              resolveSupport: { properties: ["edit"] },
              codeActionLiteralSupport: {
                codeActionKind: {
                  valueSet: [
                    "",
                    "quickfix",
                    "refactor",
                    "refactor.extract",
                    "refactor.inline",
                    "refactor.rewrite",
                    "source",
                    "source.organizeImports",
                  ],
                },
              },
              dynamicRegistration: true,
            },
            callHierarchy: { dynamicRegistration: false },
            hover: {
              contentFormat: ["markdown", "plaintext"],
              dynamicRegistration: true,
            },
            formatting: { dynamicRegistration: true },
            rangeFormatting: { dynamicRegistration: true },
            rename: { prepareSupport: true, dynamicRegistration: true },
            declaration: { linkSupport: true },
            definition: { linkSupport: true, dynamicRegistration: true },
            implementation: { linkSupport: true },
            typeDefinition: { linkSupport: true },
            publishDiagnostics: {
              dataSupport: true,
              relatedInformation: true,
              tagSupport: { valueSet: [1, 2] },
            },
            signatureHelp: {
              signatureInformation: {
                activeParameterSupport: true,
                parameterInformation: { labelOffsetSupport: true },
                documentationFormat: ["markdown", "plaintext"],
              },
              dynamicRegistration: false,
            },
            semanticTokens: {
              tokenTypes: [
                "namespace",
                "type",
                "class",
                "enum",
                "interface",
                "struct",
                "typeParameter",
                "parameter",
                "variable",
                "property",
                "enumMember",
                "event",
                "function",
                "method",
                "macro",
                "keyword",
                "modifier",
                "comment",
                "string",
                "number",
                "regexp",
                "operator",
                "decorator",
              ],
              serverCancelSupport: false,
              tokenModifiers: [
                "declaration",
                "definition",
                "readonly",
                "static",
                "deprecated",
                "abstract",
                "async",
                "modification",
                "documentation",
                "defaultLibrary",
              ],
              requests: { range: false, full: { delta: true } },
              dynamicRegistration: false,
              augmentsSyntaxTokens: true,
              formats: ["relative"],
              multilineTokenSupport: false,
              overlappingTokenSupport: true,
            },
            inlayHint: {
              resolveSupport: { properties: [] },
              dynamicRegistration: true,
            },
            completion: {
              completionItemKind: {
                valueSet: [
                  1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
                  19, 20, 21, 22, 23, 24, 25,
                ],
              },
              contextSupport: false,
              completionItem: {
                commitCharactersSupport: false,
                preselectSupport: false,
                deprecatedSupport: false,
                documentationFormat: ["markdown", "plaintext"],
                snippetSupport: false,
              },
              dynamicRegistration: false,
            },
            diagnostic: { dynamicRegistration: false },
            references: { dynamicRegistration: false },
          },
        },
        processId: 40770,
        clientInfo: { name: "Neovim", version: "0.10.0-dev+gd7359a874" },
      },
      id: 1,
    },
    { jsonrpc: "2.0", result: null, id: 0 },
    { method: "initialized", jsonrpc: "2.0", params: {} },
    {
      method: "textDocument/didOpen",
      jsonrpc: "2.0",
      params: {
        textDocument: {
          version: 0,
          text: 'console.log("lol")\n',
          languageId: "typescript",
          uri: `file://${__dirname}/file1.ts`,
        },
      },
    },
    {
      method: "textDocument/semanticTokens/full",
      jsonrpc: "2.0",
      params: {
        textDocument: {
          uri: `file://${__dirname}/file1.ts`,
        },
      },
      id: 2,
    },
    { jsonrpc: "2.0", result: null, id: 1 },
    {
      method: "textDocument/didOpen",
      jsonrpc: "2.0",
      params: {
        textDocument: {
          version: 0,
          text: 'console.log("lol 2")\n',
          languageId: "typescript",
          uri: `file://${__dirname}/file2.ts`,
        },
      },
    },
    {
      method: "textDocument/semanticTokens/full",
      jsonrpc: "2.0",
      params: {
        textDocument: {
          uri: `file://${__dirname}/file2.ts`,
        },
      },
      id: 3,
    },
  ];

  fs.writeFileSync("file1.ts", `console.log("lol")\n`);
  fs.writeFileSync("file2.ts", `console.log("lol 2")\n`);

  // Delete any existing garbage-* files.
  const files = fs.readdirSync('.');
  const garbageFiles = files.filter(file => file.startsWith('garbage-'));
  for (const f of garbageFiles) {
    fs.unlinkSync(f);
  }

  for (let i = 0; i < 400; i++) {
    // For me the error happens somewhere between 1 million (20MB)
    // and 2 million (40MB). It's also reproducible when there are many
    // small files adding up to roughly the same amount, hence the (now-redundant)
    // loop.
    fs.writeFileSync(`garbage-${i}.js`, "console.log('lol');\n".repeat(4000));
  }

  const process = spawn(
    "typescript-language-server",
    ['--stdio'],
    {
      shell: true,
      stdio: 'pipe'
    }
  );

  process.stdout.on("data", (chunk) => {
    console.log(chunk.toString());
  });

  process.stderr.on("data", (chunk) => {
    console.error(chunk.toString());
  });

  let i = 1;
  for (const cmd of commands) {
    console.log("command", i++, "of", commands.length);
    const cmdString = JSON.stringify(cmd);
    process.stdin.write(`Content-Length: ${cmdString.length}\r\n\r\n${cmdString}`);

    // Some small delay is necessary to reproduce the problem. Increasing
    // this to 10 seconds did not fix the problem for me.
    await new Promise(r => setTimeout(r, 1000));
  }
}

main()

Here is the tsconfig.json:

{
  "compilerOptions": {
    "lib": ["ESNext", "DOM"],
    "module": "esnext",
    "target": "esnext",
    "moduleResolution": "bundler",
    "moduleDetection": "force",
    "allowImportingTsExtensions": true,
    "noEmit": true,
    "composite": true,
    "strict": true,
    "downlevelIteration": true,
    "skipLibCheck": true,
    "jsx": "react-jsx",
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "allowJs": true
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions