diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b0443fdf..c3b340e7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,8 +27,11 @@ jobs: with: node-version: ${{ matrix.node-version }} cache: yarn + - run: corepack enable - name: yarn install run: yarn + - name: prepare yarnpnp + run: cd test-data/yarn-pnp && yarn - name: build run: yarn build - name: Unittest diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index a7a3cf96..6aafcb0c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -21,6 +21,7 @@ jobs: with: node-version: 18 registry-url: https://registry.npmjs.org + - run: corepack enable - name: Lint and Test if: ${{ steps.release.outputs.release_created }} run: yarn && yarn build && yarn lint diff --git a/.github/workflows/size-limit.yaml b/.github/workflows/size-limit.yaml index 4be381e1..7befd7c1 100644 --- a/.github/workflows/size-limit.yaml +++ b/.github/workflows/size-limit.yaml @@ -24,6 +24,7 @@ jobs: with: node-version: ${{ matrix.node-version }} cache: yarn + - run: corepack enable - name: yarn install run: yarn - name: build diff --git a/src/configuration/fileSchemes.test.ts b/src/configuration/fileSchemes.test.ts new file mode 100644 index 00000000..193e3404 --- /dev/null +++ b/src/configuration/fileSchemes.test.ts @@ -0,0 +1,85 @@ +import { URI } from 'vscode-uri'; +import * as lsp from 'vscode-languageserver'; +import { beforeAll, beforeEach, afterAll, describe, it, expect } from 'vitest'; +import { uri, createServer, position, TestLspServer, openDocumentAndWaitForDiagnostics, readContents, filePath, isWindows } from '../test-utils.js'; +import { ZipfileURI } from '../utils/uri.js'; + +const ZIPFILE_URI = 'zipfile:///dir/foo.zip::path/file.ts'; + +describe('uri handling', () => { + it('parses zipfile:// uri', () => { + const parsed = URI.parse(ZIPFILE_URI); + expect(parsed.scheme).toBe('zipfile'); + expect(parsed.authority).toBe(''); + expect(parsed.path).toBe('/dir/foo.zip::path/file.ts'); + expect(parsed.fsPath).toBe(isWindows ? '\\dir\\foo.zip::path\\file.ts' : '/dir/foo.zip::path/file.ts'); + expect(parsed.query).toBe(''); + expect(parsed.fragment).toBe(''); + }); + + it('stringifies zipfile uri without encoding', () => { + const parsed = URI.parse(ZIPFILE_URI); + expect(parsed.toString(true)).toBe('zipfile:/dir/foo.zip::path/file.ts'); + }); + + it('stringifies zipfile uri with encoding', () => { + const parsed = URI.parse(ZIPFILE_URI); + expect(parsed.toString()).toBe('zipfile:/dir/foo.zip%3A%3Apath/file.ts'); + }); +}); + +describe('zipfileuri handling', () => { + it('parses zipfile:// uri', () => { + const parsed = ZipfileURI.parse(ZIPFILE_URI); + expect(parsed.scheme).toBe('zipfile'); + expect(parsed.authority).toBe(''); + expect(parsed.path).toBe('/dir/foo.zip::path/file.ts'); + expect(parsed.fsPath).toBe(isWindows ? '\\dir\\foo.zip::path\\file.ts' : '/dir/foo.zip::path/file.ts'); + expect(parsed.query).toBe(''); + expect(parsed.fragment).toBe(''); + }); + + it('stringifies zipfile uri with and without encoding', () => { + const parsed = ZipfileURI.parse(ZIPFILE_URI); + expect(parsed.toString(true)).toBe('zipfile:///dir/foo.zip::path/file.ts'); + expect(parsed.toString()).toBe('zipfile:///dir/foo.zip::path/file.ts'); + }); +}); + +describe('neovim zipfile scheme handling with yarn pnp', () => { + let server: TestLspServer; + + beforeAll(async () => { + server = await createServer({ + rootUri: uri('yarn-pnp'), + initializationOptionsOverrides: { + hostInfo: 'neovim', + }, + publishDiagnostics() {}, + }); + }); + + beforeEach(() => { + server.closeAllForTesting(); + }); + + afterAll(() => { + server.closeAllForTesting(); + server.shutdown(); + }); + + it('returns zipfile: uri for definition inside node_modules', async () => { + const doc = { + uri: uri('yarn-pnp', 'testfile.ts'), + languageId: 'typescript', + version: 1, + text: readContents(filePath('yarn-pnp', 'testfile.ts')), + }; + await openDocumentAndWaitForDiagnostics(server, doc); + const pos = position(doc, 'AxiosHeaderValue'); + const results = await server.definition({ textDocument: doc, position: pos }); + const defintion = Array.isArray(results) ? results[0] as lsp.Location : null; + expect(defintion).toBeDefined(); + expect(defintion!.uri).toMatch(/zipfile:\/\/.+.zip::node_modules\/axios\/.+/); + }); +}); diff --git a/src/configuration/fileSchemes.ts b/src/configuration/fileSchemes.ts index 29c1fea6..b9b01479 100644 --- a/src/configuration/fileSchemes.ts +++ b/src/configuration/fileSchemes.ts @@ -14,6 +14,10 @@ export const untitled = 'untitled'; export const git = 'git'; export const github = 'github'; export const azurerepos = 'azurerepos'; +// Equivalent of "untitled" in Sublime Text. +export const buffer = 'buffer'; +// For yarn berry support in neovim. +export const zipfile = 'zipfile'; /** Live share scheme */ export const vsls = 'vsls'; @@ -23,6 +27,17 @@ export const memFs = 'memfs'; export const vscodeVfs = 'vscode-vfs'; export const officeScript = 'office-script'; +export function getSemanticSupportedSchemes(): string[] { + return [ + file, + untitled, + buffer, + // walkThroughSnippet, + // vscodeNotebookCell, + zipfile, + ]; +} + /** * File scheme for which JS/TS language feature should be disabled */ diff --git a/src/diagnostic-queue.ts b/src/diagnostic-queue.ts index 4d8d98b0..eb22d243 100644 --- a/src/diagnostic-queue.ts +++ b/src/diagnostic-queue.ts @@ -87,7 +87,7 @@ export class DiagnosticEventQueue { if (this.ignoredDiagnosticCodes.size) { diagnostics = diagnostics.filter(diagnostic => !this.isDiagnosticIgnored(diagnostic)); } - const uri = this.client.toResource(file).toString(); + const uri = this.client.toResourceUri(file); const diagnosticsForFile = this.diagnostics.get(uri) || new FileDiagnostics(uri, this.publishDiagnostics, this.client, this.features); diagnosticsForFile.update(kind, diagnostics); this.diagnostics.set(uri, diagnosticsForFile); @@ -98,12 +98,12 @@ export class DiagnosticEventQueue { } public getDiagnosticsForFile(file: string): lsp.Diagnostic[] { - const uri = this.client.toResource(file).toString(); + const uri = this.client.toResourceUri(file); return this.diagnostics.get(uri)?.getDiagnostics() || []; } public onDidCloseFile(file: string): void { - const uri = this.client.toResource(file).toString(); + const uri = this.client.toResourceUri(file); const diagnosticsForFile = this.diagnostics.get(uri); diagnosticsForFile?.onDidClose(); this.diagnostics.delete(uri); @@ -113,7 +113,7 @@ export class DiagnosticEventQueue { * A testing function to clear existing file diagnostics, request fresh ones and wait for all to arrive. */ public async waitForDiagnosticsForTesting(file: string): Promise { - const uri = this.client.toResource(file).toString(); + const uri = this.client.toResourceUri(file); let diagnosticsForFile = this.diagnostics.get(uri); if (diagnosticsForFile) { diagnosticsForFile.onDidClose(); diff --git a/src/features/call-hierarchy.ts b/src/features/call-hierarchy.ts index 59d61b03..22988d0f 100644 --- a/src/features/call-hierarchy.ts +++ b/src/features/call-hierarchy.ts @@ -26,7 +26,7 @@ export function fromProtocolCallHierarchyItem(item: ts.server.protocol.CallHiera kind: fromProtocolScriptElementKind(item.kind), name, detail, - uri: client.toResource(item.file).toString(), + uri: client.toResourceUri(item.file), range: Range.fromTextSpan(item.span), selectionRange: Range.fromTextSpan(item.selectionSpan), }; diff --git a/src/features/code-lens/implementationsCodeLens.ts b/src/features/code-lens/implementationsCodeLens.ts index 08f31637..774dc79e 100644 --- a/src/features/code-lens/implementationsCodeLens.ts +++ b/src/features/code-lens/implementationsCodeLens.ts @@ -51,7 +51,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip const locations = response.body .map(reference => // Only take first line on implementation: https://github.com/microsoft/vscode/issues/23924 - Location.create(this.client.toResource(reference.file).toString(), + Location.create(this.client.toResourceUri(reference.file), reference.start.line === reference.end.line ? typeConverters.Range.fromTextSpan(reference) : Range.create( diff --git a/src/features/code-lens/referencesCodeLens.ts b/src/features/code-lens/referencesCodeLens.ts index 9f9e2315..9de4a876 100644 --- a/src/features/code-lens/referencesCodeLens.ts +++ b/src/features/code-lens/referencesCodeLens.ts @@ -47,7 +47,7 @@ export class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLens const locations = response.body.refs .filter(reference => !reference.isDefinition) .map(reference => - typeConverters.Location.fromTextSpan(this.client.toResource(reference.file).toString(), reference)); + typeConverters.Location.fromTextSpan(this.client.toResourceUri(reference.file), reference)); codeLens.command = { title: this.getCodeLensLabel(locations), diff --git a/src/features/source-definition.ts b/src/features/source-definition.ts index 13cce73e..a4ffc5d6 100644 --- a/src/features/source-definition.ts +++ b/src/features/source-definition.ts @@ -46,7 +46,7 @@ export class SourceDefinitionCommand { return; } - const document = client.toOpenDocument(client.toResource(file).toString()); + const document = client.toOpenDocument(client.toResourceUri(file)); if (!document) { lspClient.showErrorMessage('Go to Source Definition failed. File not opened in the editor.'); diff --git a/src/lsp-server.ts b/src/lsp-server.ts index 5165a3f7..ef5907d2 100644 --- a/src/lsp-server.ts +++ b/src/lsp-server.ts @@ -147,6 +147,7 @@ export class LspServer { disableAutomaticTypingAcquisition, maxTsServerMemory, npmLocation, + hostInfo, locale, plugins: plugins || [], onEvent: this.onTsEvent.bind(this), @@ -550,7 +551,7 @@ export class LspServer { async completionResolve(item: lsp.CompletionItem, token?: lsp.CancellationToken): Promise { item.data = item.data?.cacheId !== undefined ? this.completionDataCache.get(item.data.cacheId) : item.data; - const uri = this.tsClient.toResource(item.data.file).toString(); + const uri = this.tsClient.toResourceUri(item.data.file); const document = item.data?.file ? this.tsClient.toOpenDocument(uri) : undefined; if (!document) { return item; @@ -636,7 +637,7 @@ export class LspServer { const changes: lsp.WorkspaceEdit['changes'] = {}; result.locs .forEach((spanGroup) => { - const uri = this.tsClient.toResource(spanGroup.file).toString(); + const uri = this.tsClient.toResourceUri(spanGroup.file); const textEdits = changes[uri] || (changes[uri] = []); spanGroup.locs.forEach((textSpan) => { @@ -868,7 +869,7 @@ export class LspServer { if (renameLocation) { await this.options.lspClient.rename({ textDocument: { - uri: this.tsClient.toResource(args.file).toString(), + uri: this.tsClient.toResourceUri(args.file), }, position: Position.fromLocation(renameLocation), }); @@ -878,7 +879,7 @@ export class LspServer { this.tsClient.configurePlugin(pluginName, configuration); } else if (params.command === Commands.ORGANIZE_IMPORTS && params.arguments) { const file = params.arguments[0] as string; - const uri = this.tsClient.toResource(file).toString(); + const uri = this.tsClient.toResourceUri(file); const document = this.tsClient.toOpenDocument(uri); if (!document) { return; @@ -944,7 +945,7 @@ export class LspServer { } const changes: { [uri: string]: lsp.TextEdit[]; } = {}; for (const edit of edits) { - changes[this.tsClient.toResource(edit.fileName).toString()] = edit.textChanges.map(toTextEdit); + changes[this.tsClient.toResourceUri(edit.fileName)] = edit.textChanges.map(toTextEdit); } const { applied } = await this.options.lspClient.applyWorkspaceEdit({ edit: { changes }, @@ -957,7 +958,7 @@ export class LspServer { for (const rename of params.files) { const codeEdits = await this.getEditsForFileRename(rename.oldUri, rename.newUri, token); for (const codeEdit of codeEdits) { - const uri = this.tsClient.toResource(codeEdit.fileName).toString(); + const uri = this.tsClient.toResourceUri(codeEdit.fileName); const textEdits = changes[uri] || (changes[uri] = []); textEdits.push(...codeEdit.textChanges.map(toTextEdit)); } @@ -1061,7 +1062,7 @@ export class LspServer { return response.body.map(item => { return { location: { - uri: this.tsClient.toResource(item.file).toString(), + uri: this.tsClient.toResourceUri(item.file), range: { start: Position.fromLocation(item.start), end: Position.fromLocation(item.end), diff --git a/src/protocol-translation.ts b/src/protocol-translation.ts index 1b239df3..b1b45360 100644 --- a/src/protocol-translation.ts +++ b/src/protocol-translation.ts @@ -12,9 +12,9 @@ import type { ts } from './ts-protocol.js'; import { Position, Range } from './utils/typeConverters.js'; export function toLocation(fileSpan: ts.server.protocol.FileSpan, client: TsClient): lsp.Location { - const uri = client.toResource(fileSpan.file); + const uri = client.toResourceUri(fileSpan.file); return { - uri: uri.toString(), + uri, range: { start: Position.fromLocation(fileSpan.start), end: Position.fromLocation(fileSpan.end), @@ -125,11 +125,11 @@ export function toTextEdit(edit: ts.server.protocol.CodeEdit): lsp.TextEdit { } export function toTextDocumentEdit(change: ts.server.protocol.FileCodeEdits, client: TsClient): lsp.TextDocumentEdit { - const uri = client.toResource(change.fileName); - const document = client.toOpenDocument(uri.toString()); + const uri = client.toResourceUri(change.fileName); + const document = client.toOpenDocument(uri); return { textDocument: { - uri: uri.toString(), + uri, version: document?.version ?? null, }, edits: change.textChanges.map(c => toTextEdit(c)), diff --git a/src/test-utils.ts b/src/test-utils.ts index d705747b..702ddb6e 100644 --- a/src/test-utils.ts +++ b/src/test-utils.ts @@ -72,6 +72,8 @@ const DEFAULT_TEST_CLIENT_INITIALIZATION_OPTIONS: TypeScriptInitializationOption const DEFAULT_WORKSPACE_SETTINGS: WorkspaceConfiguration = {}; +export const isWindows = process.platform === 'win32'; + export async function openDocumentAndWaitForDiagnostics(server: TestLspServer, textDocument: lsp.TextDocumentItem): Promise { server.didOpenTextDocument({ textDocument }); await server.waitForDiagnosticsForFile(textDocument.uri); @@ -204,6 +206,7 @@ interface TestLspServerOptions { rootUri: string | null; publishDiagnostics: (args: lsp.PublishDiagnosticsParams) => void; clientCapabilitiesOverride?: lsp.ClientCapabilities; + initializationOptionsOverrides?: TypeScriptInitializationOptions; } export async function createServer(options: TestLspServerOptions): Promise { @@ -223,7 +226,7 @@ export async function createServer(options: TestLspServerOptions): Promise void; @@ -158,6 +164,7 @@ export class TsClient implements ITypeScriptServiceClient { private readonly logger: Logger; private readonly tsserverLogger: Logger; private readonly loadingIndicator: ServerInitializingIndicator; + private isNeovimHost: boolean = false; private tracer: Tracer | undefined; private workspaceFolders: WorkspaceFolder[] = []; private readonly documents: LspDocuments; @@ -201,7 +208,7 @@ export class TsClient implements ITypeScriptServiceClient { public toTsFilePath(stringUri: string): string | undefined { // Vim may send `zipfile:` URIs which tsserver with Yarn v2+ hook can handle. Keep as-is. // Example: zipfile:///foo/bar/baz.zip::path/to/module - if (stringUri.startsWith('zipfile:')) { + if (this.isNeovimHost && stringUri.startsWith('zipfile:')) { return stringUri; } @@ -215,7 +222,11 @@ export class TsClient implements ITypeScriptServiceClient { return resource.fsPath; } - return undefined; + return inMemoryResourcePrefix + + '/' + resource.scheme + + '/' + (resource.authority || emptyAuthority) + + (resource.path.startsWith('/') ? resource.path : '/' + resource.path) + + (resource.fragment ? '#' + resource.fragment : ''); } public toOpenDocument(textDocumentUri: DocumentUri, options: { suppressAlertOnFailure?: boolean; } = {}): LspDocument | undefined { @@ -245,14 +256,29 @@ export class TsClient implements ITypeScriptServiceClient { public toResource(filepath: string): URI { // Yarn v2+ hooks tsserver and sends `zipfile:` URIs for Vim. Keep as-is. // Example: zipfile:///foo/bar/baz.zip::path/to/module - if (filepath.startsWith('zipfile:')) { - return URI.parse(filepath); + if (this.isNeovimHost && filepath.startsWith('zipfile:')) { + return ZipfileURI.parse(filepath); + } + + if (filepath.startsWith(inMemoryResourcePrefix)) { + const parts = filepath.match(RE_IN_MEMORY_FILEPATH); + if (parts) { + const resource = URI.parse(parts[1] + '://' + (parts[2] === emptyAuthority ? '' : parts[2]) + '/' + parts[3]); + const tsFilepath = this.toTsFilePath(resource.toString()); + const document = tsFilepath && this.documents.get(tsFilepath); + return document ? document.uri : resource; + } } + const fileUri = URI.file(filepath); const document = this.documents.get(fileUri.fsPath); return document ? document.uri : fileUri; } + public toResourceUri(filepath: string): string { + return this.toResource(filepath).toString(); + } + public getWorkspaceRootForResource(resource: URI): URI | undefined { // For notebook cells, we need to use the notebook document to look up the workspace // if (resource.scheme === Schemes.notebookCell) { @@ -303,7 +329,7 @@ export class TsClient implements ITypeScriptServiceClient { switch (capability) { case ClientCapability.Semantic: { - return ['file', 'untitled'].includes(resource.scheme); + return fileSchemes.getSemanticSupportedSchemes().includes(resource.scheme); } case ClientCapability.Syntax: case ClientCapability.EnhancedSyntax: { @@ -324,6 +350,7 @@ export class TsClient implements ITypeScriptServiceClient { ): boolean { this.apiVersion = options.typescriptVersion.version || API.defaultVersion; this.typescriptVersionSource = options.typescriptVersion.source; + this.isNeovimHost = options.hostInfo === 'neovim'; this.tracer = new Tracer(this.tsserverLogger, options.trace); this.workspaceFolders = workspaceRoot ? [{ uri: URI.file(workspaceRoot) }] : []; this.useSyntaxServer = options.useSyntaxServer; diff --git a/src/typescriptService.ts b/src/typescriptService.ts index 7bfad944..465409da 100644 --- a/src/typescriptService.ts +++ b/src/typescriptService.ts @@ -80,6 +80,7 @@ export interface ITypeScriptServiceClient { * Convert a path to a resource. */ toResource(filepath: string): URI; + toResourceUri(filepath: string): string; /** * Tries to ensure that a document is open on the TS server. diff --git a/src/utils/uri.ts b/src/utils/uri.ts new file mode 100644 index 00000000..2c38f5ef --- /dev/null +++ b/src/utils/uri.ts @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 TypeFox and others. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + */ + +import { URI } from 'vscode-uri'; + +export class ZipfileURI extends URI { + private _originalUri: string; + + private constructor(uri: string, components: URI) { + super(components); + + this._originalUri = uri; + } + + override toString(_skipEncoding: boolean = false): string { + return this._originalUri; + } + + static override parse(value: string, _strict: boolean = false): ZipfileURI { + const uri = URI.parse(value, _strict); + + return new ZipfileURI(value, uri); + } +} diff --git a/test-data/yarn-pnp/.editorconfig b/test-data/yarn-pnp/.editorconfig new file mode 100644 index 00000000..1ed453a3 --- /dev/null +++ b/test-data/yarn-pnp/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +[*.{js,json,yml}] +charset = utf-8 +indent_style = space +indent_size = 2 diff --git a/test-data/yarn-pnp/.gitattributes b/test-data/yarn-pnp/.gitattributes new file mode 100644 index 00000000..af3ad128 --- /dev/null +++ b/test-data/yarn-pnp/.gitattributes @@ -0,0 +1,4 @@ +/.yarn/** linguist-vendored +/.yarn/releases/* binary +/.yarn/plugins/**/* binary +/.pnp.* binary linguist-generated diff --git a/test-data/yarn-pnp/.gitignore b/test-data/yarn-pnp/.gitignore new file mode 100644 index 00000000..870eb6a5 --- /dev/null +++ b/test-data/yarn-pnp/.gitignore @@ -0,0 +1,13 @@ +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# Swap the comments on the following lines if you wish to use zero-installs +# In that case, don't forget to run `yarn config set enableGlobalCache false`! +# Documentation here: https://yarnpkg.com/features/caching#zero-installs + +#!.yarn/cache +.pnp.* diff --git a/test-data/yarn-pnp/.vim/coc-settings.json b/test-data/yarn-pnp/.vim/coc-settings.json new file mode 100644 index 00000000..1de47983 --- /dev/null +++ b/test-data/yarn-pnp/.vim/coc-settings.json @@ -0,0 +1,4 @@ +{ + "workspace.workspaceFolderCheckCwd": false, + "tsserver.tsdk": ".yarn/sdks/typescript/lib" +} diff --git a/test-data/yarn-pnp/.yarn/sdks/integrations.yml b/test-data/yarn-pnp/.yarn/sdks/integrations.yml new file mode 100644 index 00000000..231abf42 --- /dev/null +++ b/test-data/yarn-pnp/.yarn/sdks/integrations.yml @@ -0,0 +1,5 @@ +# This file is automatically generated by @yarnpkg/sdks. +# Manual changes might be lost! + +integrations: + - vim diff --git a/test-data/yarn-pnp/.yarn/sdks/typescript/bin/tsc b/test-data/yarn-pnp/.yarn/sdks/typescript/bin/tsc new file mode 100755 index 00000000..454b950b --- /dev/null +++ b/test-data/yarn-pnp/.yarn/sdks/typescript/bin/tsc @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/bin/tsc + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/bin/tsc your application uses +module.exports = absRequire(`typescript/bin/tsc`); diff --git a/test-data/yarn-pnp/.yarn/sdks/typescript/bin/tsserver b/test-data/yarn-pnp/.yarn/sdks/typescript/bin/tsserver new file mode 100755 index 00000000..d7a60568 --- /dev/null +++ b/test-data/yarn-pnp/.yarn/sdks/typescript/bin/tsserver @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/bin/tsserver + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/bin/tsserver your application uses +module.exports = absRequire(`typescript/bin/tsserver`); diff --git a/test-data/yarn-pnp/.yarn/sdks/typescript/lib/tsc.js b/test-data/yarn-pnp/.yarn/sdks/typescript/lib/tsc.js new file mode 100644 index 00000000..2f62fc96 --- /dev/null +++ b/test-data/yarn-pnp/.yarn/sdks/typescript/lib/tsc.js @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/lib/tsc.js + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/lib/tsc.js your application uses +module.exports = absRequire(`typescript/lib/tsc.js`); diff --git a/test-data/yarn-pnp/.yarn/sdks/typescript/lib/tsserver.js b/test-data/yarn-pnp/.yarn/sdks/typescript/lib/tsserver.js new file mode 100644 index 00000000..bbb1e465 --- /dev/null +++ b/test-data/yarn-pnp/.yarn/sdks/typescript/lib/tsserver.js @@ -0,0 +1,225 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +const moduleWrapper = tsserver => { + if (!process.versions.pnp) { + return tsserver; + } + + const {isAbsolute} = require(`path`); + const pnpApi = require(`pnpapi`); + + const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); + const isPortal = str => str.startsWith("portal:/"); + const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); + + const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { + return `${locator.name}@${locator.reference}`; + })); + + // VSCode sends the zip paths to TS using the "zip://" prefix, that TS + // doesn't understand. This layer makes sure to remove the protocol + // before forwarding it to TS, and to add it back on all returned paths. + + function toEditorPath(str) { + // We add the `zip:` prefix to both `.zip/` paths and virtual paths + if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { + // We also take the opportunity to turn virtual paths into physical ones; + // this makes it much easier to work with workspaces that list peer + // dependencies, since otherwise Ctrl+Click would bring us to the virtual + // file instances instead of the real ones. + // + // We only do this to modules owned by the the dependency tree roots. + // This avoids breaking the resolution when jumping inside a vendor + // with peer dep (otherwise jumping into react-dom would show resolution + // errors on react). + // + const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; + if (resolved) { + const locator = pnpApi.findPackageLocator(resolved); + if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { + str = resolved; + } + } + + str = normalize(str); + + if (str.match(/\.zip\//)) { + switch (hostInfo) { + // Absolute VSCode `Uri.fsPath`s need to start with a slash. + // VSCode only adds it automatically for supported schemes, + // so we have to do it manually for the `zip` scheme. + // The path needs to start with a caret otherwise VSCode doesn't handle the protocol + // + // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 + // + // 2021-10-08: VSCode changed the format in 1.61. + // Before | ^zip:/c:/foo/bar.zip/package.json + // After | ^/zip//c:/foo/bar.zip/package.json + // + // 2022-04-06: VSCode changed the format in 1.66. + // Before | ^/zip//c:/foo/bar.zip/package.json + // After | ^/zip/c:/foo/bar.zip/package.json + // + // 2022-05-06: VSCode changed the format in 1.68 + // Before | ^/zip/c:/foo/bar.zip/package.json + // After | ^/zip//c:/foo/bar.zip/package.json + // + case `vscode <1.61`: { + str = `^zip:${str}`; + } break; + + case `vscode <1.66`: { + str = `^/zip/${str}`; + } break; + + case `vscode <1.68`: { + str = `^/zip${str}`; + } break; + + case `vscode`: { + str = `^/zip/${str}`; + } break; + + // To make "go to definition" work, + // We have to resolve the actual file system path from virtual path + // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) + case `coc-nvim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = resolve(`zipfile:${str}`); + } break; + + // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) + // We have to resolve the actual file system path from virtual path, + // everything else is up to neovim + case `neovim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = `zipfile://${str}`; + } break; + + default: { + str = `zip:${str}`; + } break; + } + } else { + str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`); + } + } + + return str; + } + + function fromEditorPath(str) { + switch (hostInfo) { + case `coc-nvim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for coc-nvim is in format of //zipfile://.yarn/... + // So in order to convert it back, we use .* to match all the thing + // before `zipfile:` + return process.platform === `win32` + ? str.replace(/^.*zipfile:\//, ``) + : str.replace(/^.*zipfile:/, ``); + } break; + + case `neovim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for neovim is in format of zipfile:////.yarn/... + return str.replace(/^zipfile:\/\//, ``); + } break; + + case `vscode`: + default: { + return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) + } break; + } + } + + // Force enable 'allowLocalPluginLoads' + // TypeScript tries to resolve plugins using a path relative to itself + // which doesn't work when using the global cache + // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 + // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but + // TypeScript already does local loads and if this code is running the user trusts the workspace + // https://github.com/microsoft/vscode/issues/45856 + const ConfiguredProject = tsserver.server.ConfiguredProject; + const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; + ConfiguredProject.prototype.enablePluginsWithOptions = function() { + this.projectService.allowLocalPluginLoads = true; + return originalEnablePluginsWithOptions.apply(this, arguments); + }; + + // And here is the point where we hijack the VSCode <-> TS communications + // by adding ourselves in the middle. We locate everything that looks + // like an absolute path of ours and normalize it. + + const Session = tsserver.server.Session; + const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; + let hostInfo = `unknown`; + + Object.assign(Session.prototype, { + onMessage(/** @type {string | object} */ message) { + const isStringMessage = typeof message === 'string'; + const parsedMessage = isStringMessage ? JSON.parse(message) : message; + + if ( + parsedMessage != null && + typeof parsedMessage === `object` && + parsedMessage.arguments && + typeof parsedMessage.arguments.hostInfo === `string` + ) { + hostInfo = parsedMessage.arguments.hostInfo; + if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { + const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( + // The RegExp from https://semver.org/ but without the caret at the start + /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ + ) ?? []).map(Number) + + if (major === 1) { + if (minor < 61) { + hostInfo += ` <1.61`; + } else if (minor < 66) { + hostInfo += ` <1.66`; + } else if (minor < 68) { + hostInfo += ` <1.68`; + } + } + } + } + + const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { + return typeof value === 'string' ? fromEditorPath(value) : value; + }); + + return originalOnMessage.call( + this, + isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) + ); + }, + + send(/** @type {any} */ msg) { + return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { + return typeof value === `string` ? toEditorPath(value) : value; + }))); + } + }); + + return tsserver; +}; + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/lib/tsserver.js + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/lib/tsserver.js your application uses +module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`)); diff --git a/test-data/yarn-pnp/.yarn/sdks/typescript/lib/tsserverlibrary.js b/test-data/yarn-pnp/.yarn/sdks/typescript/lib/tsserverlibrary.js new file mode 100644 index 00000000..a68f028f --- /dev/null +++ b/test-data/yarn-pnp/.yarn/sdks/typescript/lib/tsserverlibrary.js @@ -0,0 +1,225 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +const moduleWrapper = tsserver => { + if (!process.versions.pnp) { + return tsserver; + } + + const {isAbsolute} = require(`path`); + const pnpApi = require(`pnpapi`); + + const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); + const isPortal = str => str.startsWith("portal:/"); + const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); + + const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { + return `${locator.name}@${locator.reference}`; + })); + + // VSCode sends the zip paths to TS using the "zip://" prefix, that TS + // doesn't understand. This layer makes sure to remove the protocol + // before forwarding it to TS, and to add it back on all returned paths. + + function toEditorPath(str) { + // We add the `zip:` prefix to both `.zip/` paths and virtual paths + if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { + // We also take the opportunity to turn virtual paths into physical ones; + // this makes it much easier to work with workspaces that list peer + // dependencies, since otherwise Ctrl+Click would bring us to the virtual + // file instances instead of the real ones. + // + // We only do this to modules owned by the the dependency tree roots. + // This avoids breaking the resolution when jumping inside a vendor + // with peer dep (otherwise jumping into react-dom would show resolution + // errors on react). + // + const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; + if (resolved) { + const locator = pnpApi.findPackageLocator(resolved); + if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { + str = resolved; + } + } + + str = normalize(str); + + if (str.match(/\.zip\//)) { + switch (hostInfo) { + // Absolute VSCode `Uri.fsPath`s need to start with a slash. + // VSCode only adds it automatically for supported schemes, + // so we have to do it manually for the `zip` scheme. + // The path needs to start with a caret otherwise VSCode doesn't handle the protocol + // + // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 + // + // 2021-10-08: VSCode changed the format in 1.61. + // Before | ^zip:/c:/foo/bar.zip/package.json + // After | ^/zip//c:/foo/bar.zip/package.json + // + // 2022-04-06: VSCode changed the format in 1.66. + // Before | ^/zip//c:/foo/bar.zip/package.json + // After | ^/zip/c:/foo/bar.zip/package.json + // + // 2022-05-06: VSCode changed the format in 1.68 + // Before | ^/zip/c:/foo/bar.zip/package.json + // After | ^/zip//c:/foo/bar.zip/package.json + // + case `vscode <1.61`: { + str = `^zip:${str}`; + } break; + + case `vscode <1.66`: { + str = `^/zip/${str}`; + } break; + + case `vscode <1.68`: { + str = `^/zip${str}`; + } break; + + case `vscode`: { + str = `^/zip/${str}`; + } break; + + // To make "go to definition" work, + // We have to resolve the actual file system path from virtual path + // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) + case `coc-nvim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = resolve(`zipfile:${str}`); + } break; + + // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) + // We have to resolve the actual file system path from virtual path, + // everything else is up to neovim + case `neovim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = `zipfile://${str}`; + } break; + + default: { + str = `zip:${str}`; + } break; + } + } else { + str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`); + } + } + + return str; + } + + function fromEditorPath(str) { + switch (hostInfo) { + case `coc-nvim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for coc-nvim is in format of //zipfile://.yarn/... + // So in order to convert it back, we use .* to match all the thing + // before `zipfile:` + return process.platform === `win32` + ? str.replace(/^.*zipfile:\//, ``) + : str.replace(/^.*zipfile:/, ``); + } break; + + case `neovim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for neovim is in format of zipfile:////.yarn/... + return str.replace(/^zipfile:\/\//, ``); + } break; + + case `vscode`: + default: { + return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) + } break; + } + } + + // Force enable 'allowLocalPluginLoads' + // TypeScript tries to resolve plugins using a path relative to itself + // which doesn't work when using the global cache + // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 + // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but + // TypeScript already does local loads and if this code is running the user trusts the workspace + // https://github.com/microsoft/vscode/issues/45856 + const ConfiguredProject = tsserver.server.ConfiguredProject; + const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; + ConfiguredProject.prototype.enablePluginsWithOptions = function() { + this.projectService.allowLocalPluginLoads = true; + return originalEnablePluginsWithOptions.apply(this, arguments); + }; + + // And here is the point where we hijack the VSCode <-> TS communications + // by adding ourselves in the middle. We locate everything that looks + // like an absolute path of ours and normalize it. + + const Session = tsserver.server.Session; + const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; + let hostInfo = `unknown`; + + Object.assign(Session.prototype, { + onMessage(/** @type {string | object} */ message) { + const isStringMessage = typeof message === 'string'; + const parsedMessage = isStringMessage ? JSON.parse(message) : message; + + if ( + parsedMessage != null && + typeof parsedMessage === `object` && + parsedMessage.arguments && + typeof parsedMessage.arguments.hostInfo === `string` + ) { + hostInfo = parsedMessage.arguments.hostInfo; + if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { + const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( + // The RegExp from https://semver.org/ but without the caret at the start + /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ + ) ?? []).map(Number) + + if (major === 1) { + if (minor < 61) { + hostInfo += ` <1.61`; + } else if (minor < 66) { + hostInfo += ` <1.66`; + } else if (minor < 68) { + hostInfo += ` <1.68`; + } + } + } + } + + const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { + return typeof value === 'string' ? fromEditorPath(value) : value; + }); + + return originalOnMessage.call( + this, + isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) + ); + }, + + send(/** @type {any} */ msg) { + return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { + return typeof value === `string` ? toEditorPath(value) : value; + }))); + } + }); + + return tsserver; +}; + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/lib/tsserverlibrary.js + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/lib/tsserverlibrary.js your application uses +module.exports = moduleWrapper(absRequire(`typescript/lib/tsserverlibrary.js`)); diff --git a/test-data/yarn-pnp/.yarn/sdks/typescript/lib/typescript.js b/test-data/yarn-pnp/.yarn/sdks/typescript/lib/typescript.js new file mode 100644 index 00000000..b5f4db25 --- /dev/null +++ b/test-data/yarn-pnp/.yarn/sdks/typescript/lib/typescript.js @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript your application uses +module.exports = absRequire(`typescript`); diff --git a/test-data/yarn-pnp/.yarn/sdks/typescript/package.json b/test-data/yarn-pnp/.yarn/sdks/typescript/package.json new file mode 100644 index 00000000..eb7dd745 --- /dev/null +++ b/test-data/yarn-pnp/.yarn/sdks/typescript/package.json @@ -0,0 +1,10 @@ +{ + "name": "typescript", + "version": "5.3.3-sdk", + "main": "./lib/typescript.js", + "type": "commonjs", + "bin": { + "tsc": "./bin/tsc", + "tsserver": "./bin/tsserver" + } +} diff --git a/test-data/yarn-pnp/README.md b/test-data/yarn-pnp/README.md new file mode 100644 index 00000000..1da79178 --- /dev/null +++ b/test-data/yarn-pnp/README.md @@ -0,0 +1 @@ +# yarn2 diff --git a/test-data/yarn-pnp/package.json b/test-data/yarn-pnp/package.json new file mode 100644 index 00000000..3588b15f --- /dev/null +++ b/test-data/yarn-pnp/package.json @@ -0,0 +1,8 @@ +{ + "name": "yarn2", + "packageManager": "yarn@4.1.0", + "dependencies": { + "axios": "1.6.7", + "typescript": "^5.3.3" + } +} diff --git a/test-data/yarn-pnp/testfile.ts b/test-data/yarn-pnp/testfile.ts new file mode 100644 index 00000000..f51c9a65 --- /dev/null +++ b/test-data/yarn-pnp/testfile.ts @@ -0,0 +1 @@ +import type { AxiosHeaderValue } from 'axios'; diff --git a/test-data/yarn-pnp/tsconfig.json b/test-data/yarn-pnp/tsconfig.json new file mode 100644 index 00000000..55eb65d8 --- /dev/null +++ b/test-data/yarn-pnp/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "module": "ES2022", + "moduleResolution": "Node16" + }, +} diff --git a/test-data/yarn-pnp/yarn.lock b/test-data/yarn-pnp/yarn.lock new file mode 100644 index 00000000..25f5791c --- /dev/null +++ b/test-data/yarn-pnp/yarn.lock @@ -0,0 +1,113 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d + languageName: node + linkType: hard + +"axios@npm:1.6.7": + version: 1.6.7 + resolution: "axios@npm:1.6.7" + dependencies: + follow-redirects: "npm:^1.15.4" + form-data: "npm:^4.0.0" + proxy-from-env: "npm:^1.1.0" + checksum: 10c0/131bf8e62eee48ca4bd84e6101f211961bf6a21a33b95e5dfb3983d5a2fe50d9fffde0b57668d7ce6f65063d3dc10f2212cbcb554f75cfca99da1c73b210358d + languageName: node + linkType: hard + +"combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: "npm:~1.0.0" + checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 + languageName: node + linkType: hard + +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 + languageName: node + linkType: hard + +"follow-redirects@npm:^1.15.4": + version: 1.15.5 + resolution: "follow-redirects@npm:1.15.5" + peerDependenciesMeta: + debug: + optional: true + checksum: 10c0/418d71688ceaf109dfd6f85f747a0c75de30afe43a294caa211def77f02ef19865b547dfb73fde82b751e1cc507c06c754120b848fe5a7400b0a669766df7615 + languageName: node + linkType: hard + +"form-data@npm:^4.0.0": + version: 4.0.0 + resolution: "form-data@npm:4.0.0" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + mime-types: "npm:^2.1.12" + checksum: 10c0/cb6f3ac49180be03ff07ba3ff125f9eba2ff0b277fb33c7fc47569fc5e616882c5b1c69b9904c4c4187e97dd0419dd03b134174756f296dec62041e6527e2c6e + languageName: node + linkType: hard + +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa + languageName: node + linkType: hard + +"mime-types@npm:^2.1.12": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 + languageName: node + linkType: hard + +"proxy-from-env@npm:^1.1.0": + version: 1.1.0 + resolution: "proxy-from-env@npm:1.1.0" + checksum: 10c0/fe7dd8b1bdbbbea18d1459107729c3e4a2243ca870d26d34c2c1bcd3e4425b7bcc5112362df2d93cc7fb9746f6142b5e272fd1cc5c86ddf8580175186f6ad42b + languageName: node + linkType: hard + +"typescript@npm:^5.3.3": + version: 5.3.3 + resolution: "typescript@npm:5.3.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/e33cef99d82573624fc0f854a2980322714986bc35b9cb4d1ce736ed182aeab78e2cb32b385efa493b2a976ef52c53e20d6c6918312353a91850e2b76f1ea44f + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.3.3#optional!builtin": + version: 5.3.3 + resolution: "typescript@patch:typescript@npm%3A5.3.3#optional!builtin::version=5.3.3&hash=e012d7" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/1d0a5f4ce496c42caa9a30e659c467c5686eae15d54b027ee7866744952547f1be1262f2d40de911618c242b510029d51d43ff605dba8fb740ec85ca2d3f9500 + languageName: node + linkType: hard + +"yarn2@workspace:.": + version: 0.0.0-use.local + resolution: "yarn2@workspace:." + dependencies: + axios: "npm:1.6.7" + typescript: "npm:^5.3.3" + languageName: unknown + linkType: soft