diff --git a/.eslintignore b/.eslintignore index fea65b495873f..7bbd3778e9002 100644 --- a/.eslintignore +++ b/.eslintignore @@ -15,6 +15,7 @@ **/extensions/typescript-language-features/test-workspace/** **/extensions/typescript-language-features/extension.webpack.config.js **/extensions/typescript-language-features/extension-browser.webpack.config.js +**/extensions/typescript-language-features/package-manager/node-maintainer/** **/extensions/vscode-api-tests/testWorkspace/** **/extensions/vscode-api-tests/testWorkspace2/** **/fixtures/** diff --git a/build/filters.js b/build/filters.js index 5a8cf36ef84c2..0e0c5fcabb8ca 100644 --- a/build/filters.js +++ b/build/filters.js @@ -37,7 +37,7 @@ module.exports.unicodeFilter = [ '!LICENSES.chromium.html', '!**/LICENSE', - '!**/*.{dll,exe,png,bmp,jpg,scpt,cur,ttf,woff,eot,template,ico,icns,opus}', + '!**/*.{dll,exe,png,bmp,jpg,scpt,cur,ttf,woff,eot,template,ico,icns,opus,wasm}', '!**/test/**', '!**/*.test.ts', '!**/*.{d.ts,json,md}', @@ -88,6 +88,7 @@ module.exports.indentationFilter = [ '!test/smoke/out/**', '!extensions/typescript-language-features/test-workspace/**', '!extensions/typescript-language-features/resources/walkthroughs/**', + '!extensions/typescript-language-features/package-manager/node-maintainer/**', '!extensions/markdown-math/notebook-out/**', '!extensions/ipynb/notebook-out/**', '!extensions/vscode-api-tests/testWorkspace/**', @@ -115,7 +116,7 @@ module.exports.indentationFilter = [ '!src/vs/*/**/*.d.ts', '!src/typings/**/*.d.ts', '!extensions/**/*.d.ts', - '!**/*.{svg,exe,png,bmp,jpg,scpt,bat,cmd,cur,ttf,woff,eot,md,ps1,template,yaml,yml,d.ts.recipe,ico,icns,plist,opus,admx,adml}', + '!**/*.{svg,exe,png,bmp,jpg,scpt,bat,cmd,cur,ttf,woff,eot,md,ps1,template,yaml,yml,d.ts.recipe,ico,icns,plist,opus,admx,adml,wasm}', '!build/{lib,download,linux,darwin}/**/*.js', '!build/**/*.sh', '!build/azure-pipelines/**/*.js', @@ -157,6 +158,7 @@ module.exports.copyrightFilter = [ '!**/*.disabled', '!**/*.code-workspace', '!**/*.js.map', + '!**/*.wasm', '!build/**/*.init', '!build/linux/libcxx-fetcher.*', '!resources/linux/snap/snapcraft.yaml', @@ -166,6 +168,7 @@ module.exports.copyrightFilter = [ '!extensions/markdown-language-features/media/highlight.css', '!extensions/markdown-math/notebook-out/**', '!extensions/ipynb/notebook-out/**', + '!extensions/typescript-language-features/node-maintainer/**', '!extensions/html-language-features/server/src/modes/typescript/*', '!extensions/*/server/bin/*', '!src/vs/editor/test/node/classification/typescript-test.ts', diff --git a/extensions/shared.webpack.config.js b/extensions/shared.webpack.config.js index cd9aef4967718..19e76a9d35f61 100644 --- a/extensions/shared.webpack.config.js +++ b/extensions/shared.webpack.config.js @@ -139,6 +139,9 @@ function withBrowserDefaults(/**@type WebpackConfig & { context: string }*/extCo }, }, ] + }, { + test: /\.wasm$/, + type: 'asset/inline' }] }, externals: { diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index e3c9a7f870ddf..3873011888350 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -34,12 +34,13 @@ ], "dependencies": { "@vscode/extension-telemetry": "^0.8.4", - "jsonc-parser": "^3.2.0", - "semver": "7.5.2", - "vscode-tas-client": "^0.1.63", "@vscode/sync-api-client": "^0.7.2", "@vscode/sync-api-common": "^0.7.2", "@vscode/sync-api-service": "^0.7.3", + "@vscode/ts-package-manager": "^0.0.2", + "jsonc-parser": "^3.2.0", + "semver": "7.5.2", + "vscode-tas-client": "^0.1.63", "vscode-uri": "^3.0.3" }, "devDependencies": { @@ -1250,6 +1251,13 @@ "description": "%configuration.tsserver.web.projectWideIntellisense.suppressSemanticErrors%", "scope": "window" }, + "typescript.experimental.tsserver.web.typeAcquisition.enabled": { + "type": "boolean", + "default": false, + "description": "%configuration.experimental.tsserver.web.typeAcquisition.enabled%", + "scope": "window", + "tags": ["experimental"] + }, "typescript.preferGoToSourceDefinition": { "type": "boolean", "default": false, diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 81260c405e103..641a4092870de 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -212,5 +212,20 @@ "configuration.suggest.classMemberSnippets.enabled": "Enable/disable snippet completions for class members.", "configuration.suggest.objectLiteralMethodSnippets.enabled": "Enable/disable snippet completions for methods in object literals. Requires using TypeScript 4.7+ in the workspace.", "configuration.tsserver.web.projectWideIntellisense.enabled": "Enable/disable project-wide IntelliSense on web. Requires that VS Code is running in a trusted context.", - "configuration.tsserver.web.projectWideIntellisense.suppressSemanticErrors": "Suppresses semantic errors. This is needed when using external packages as these can't be included analyzed on web." + "configuration.tsserver.web.projectWideIntellisense.suppressSemanticErrors": "Suppresses semantic errors. This is needed when using external packages as these can't be included analyzed on web.", + "configuration.experimental.tsserver.web.typeAcquisition.enabled": "Enable/disable package acquisition on the web.", + "walkthroughs.nodejsWelcome.title": "Get started with JavaScript and Node.js", + "walkthroughs.nodejsWelcome.description": "Make the most of Visual Studio Code's first-class JavaScript experience.", + "walkthroughs.nodejsWelcome.downloadNode.forMacOrWindows.title": "Install Node.js", + "walkthroughs.nodejsWelcome.downloadNode.forMacOrWindows.description": "Node.js is an easy way to run JavaScript code. You can use it to quickly build command-line apps and servers. It also comes with npm, a package manager which makes reusing and sharing JavaScript code easy.\n[Install Node.js](https://nodejs.org/en/download/)", + "walkthroughs.nodejsWelcome.downloadNode.forLinux.title": "Install Node.js", + "walkthroughs.nodejsWelcome.downloadNode.forLinux.description": "Node.js is an easy way to run JavaScript code. You can use it to quickly build command-line apps and servers. It also comes with npm, a package manager which makes reusing and sharing JavaScript code easy.\n[Install Node.js](https://nodejs.org/en/download/package-manager/)", + "walkthroughs.nodejsWelcome.makeJsFile.title": "Create a JavaScript File", + "walkthroughs.nodejsWelcome.makeJsFile.description": "Let's write our first JavaScript file. We'll have to create a new file and save it with the ``.js`` extension at the end of the file name.\n[Create a JavaScript File](command:javascript-walkthrough.commands.createJsFile)", + "walkthroughs.nodejsWelcome.debugJsFile.title": "Run and Debug your JavaScript", + "walkthroughs.nodejsWelcome.debugJsFile.description": "Once you've installed Node.js, you can run JavaScript programs at a terminal by entering ``node your-file-name.js``\nAnother easy way to run Node.js programs is by using VS Code's debugger which lets you run your code, pause at different points, and help you understand what's going on step-by-step.\n[Start Debugging](command:javascript-walkthrough.commands.debugJsFile)", + "walkthroughs.nodejsWelcome.debugJsFile.altText": "Debug and run your JavaScript code in Node.js with Visual Studio Code.", + "walkthroughs.nodejsWelcome.learnMoreAboutJs.title": "Explore More", + "walkthroughs.nodejsWelcome.learnMoreAboutJs.description": "Want to get more comfortable with JavaScript, Node.js, and VS Code? Be sure to check out our docs!\nWe've got lots of resources for learning [JavaScript](https://code.visualstudio.com/docs/nodejs/working-with-javascript) and [Node.js](https://code.visualstudio.com/docs/nodejs/nodejs-tutorial).\n\n[Learn More](https://code.visualstudio.com/docs/nodejs/nodejs-tutorial)", + "walkthroughs.nodejsWelcome.learnMoreAboutJs.altText": "Learn more about JavaScript and Node.js in Visual Studio Code." } diff --git a/extensions/typescript-language-features/src/configuration/configuration.ts b/extensions/typescript-language-features/src/configuration/configuration.ts index d338792c58c41..f3a5817146b6a 100644 --- a/extensions/typescript-language-features/src/configuration/configuration.ts +++ b/extensions/typescript-language-features/src/configuration/configuration.ts @@ -112,6 +112,7 @@ export interface TypeScriptServiceConfiguration { readonly useSyntaxServer: SyntaxServerConfiguration; readonly webProjectWideIntellisenseEnabled: boolean; readonly webProjectWideIntellisenseSuppressSemanticErrors: boolean; + readonly webExperimentalTypeAcquisition: boolean; readonly enableDiagnosticsTelemetry: boolean; readonly enableProjectDiagnostics: boolean; readonly maxTsServerMemory: number; @@ -145,6 +146,7 @@ export abstract class BaseServiceConfigurationProvider implements ServiceConfigu useSyntaxServer: this.readUseSyntaxServer(configuration), webProjectWideIntellisenseEnabled: this.readWebProjectWideIntellisenseEnable(configuration), webProjectWideIntellisenseSuppressSemanticErrors: this.readWebProjectWideIntellisenseSuppressSemanticErrors(configuration), + webExperimentalTypeAcquisition: this.readWebExperimentalTypeAcquisition(configuration), enableDiagnosticsTelemetry: this.readEnableDiagnosticsTelemetry(configuration), enableProjectDiagnostics: this.readEnableProjectDiagnostics(configuration), maxTsServerMemory: this.readMaxTsServerMemory(configuration), @@ -175,6 +177,10 @@ export abstract class BaseServiceConfigurationProvider implements ServiceConfigu return configuration.get('typescript.disableAutomaticTypeAcquisition', false); } + protected readWebExperimentalTypeAcquisition(configuration: vscode.WorkspaceConfiguration): boolean { + return configuration.get('typescript.experimental.tsserver.web.typeAcquisition.enabled', false); + } + protected readLocale(configuration: vscode.WorkspaceConfiguration): string | null { const value = configuration.get('typescript.locale', 'auto'); return !value || value === 'auto' ? null : value; diff --git a/extensions/typescript-language-features/src/extension.browser.ts b/extensions/typescript-language-features/src/extension.browser.ts index 392a81f792200..65e9d57fc3815 100644 --- a/extensions/typescript-language-features/src/extension.browser.ts +++ b/extensions/typescript-language-features/src/extension.browser.ts @@ -8,22 +8,24 @@ import * as vscode from 'vscode'; import { Api, getExtensionApi } from './api'; import { CommandManager } from './commands/commandManager'; import { registerBaseCommands } from './commands/index'; +import { TypeScriptServiceConfiguration } from './configuration/configuration'; +import { BrowserServiceConfigurationProvider } from './configuration/configuration.browser'; import { ExperimentationTelemetryReporter, IExperimentationTelemetryReporter } from './experimentTelemetryReporter'; +import { AutoInstallerFs } from './filesystems/autoInstallerFs'; +import { MemFs } from './filesystems/memFs'; import { createLazyClientHost, lazilyActivateClient } from './lazyClientHost'; +import { Logger } from './logging/logger'; import RemoteRepositories from './remoteRepositories.browser'; import { API } from './tsServer/api'; import { noopRequestCancellerFactory } from './tsServer/cancellation'; import { noopLogDirectoryProvider } from './tsServer/logDirectoryProvider'; +import { PluginManager } from './tsServer/plugins'; import { WorkerServerProcessFactory } from './tsServer/serverProcess.browser'; import { ITypeScriptVersionProvider, TypeScriptVersion, TypeScriptVersionSource } from './tsServer/versionProvider'; import { ActiveJsTsEditorTracker } from './ui/activeJsTsEditorTracker'; -import { TypeScriptServiceConfiguration } from './configuration/configuration'; -import { BrowserServiceConfigurationProvider } from './configuration/configuration.browser'; -import { Logger } from './logging/logger'; +import { Disposable } from './utils/dispose'; import { getPackageInfo } from './utils/packageInfo'; import { isWebAndHasSharedArrayBuffers } from './utils/platform'; -import { PluginManager } from './tsServer/plugins'; -import { Disposable } from './utils/dispose'; class StaticVersionProvider implements ITypeScriptVersionProvider { @@ -99,6 +101,14 @@ export async function activate(context: vscode.ExtensionContext): Promise { context.subscriptions.push(lazilyActivateClient(lazyClientHost, pluginManager, activeJsTsEditorTracker, async () => { await startPreloadWorkspaceContentsIfNeeded(context, logger); })); + context.subscriptions.push(vscode.workspace.registerFileSystemProvider('vscode-global-typings', new MemFs(), { + isCaseSensitive: true, + isReadonly: false + })); + context.subscriptions.push(vscode.workspace.registerFileSystemProvider('vscode-node-modules', new AutoInstallerFs(), { + isCaseSensitive: true, + isReadonly: false + })); return getExtensionApi(onCompletionAccepted.event, pluginManager); } diff --git a/extensions/typescript-language-features/src/filesystems/autoInstallerFs.ts b/extensions/typescript-language-features/src/filesystems/autoInstallerFs.ts new file mode 100644 index 0000000000000..4e69fce8cdaac --- /dev/null +++ b/extensions/typescript-language-features/src/filesystems/autoInstallerFs.ts @@ -0,0 +1,252 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { MemFs } from './memFs'; +import { URI } from 'vscode-uri'; +import { PackageManager, FileSystem, packagePath } from '@vscode/ts-package-manager'; +import { join, basename, dirname } from 'path'; + +const TEXT_DECODER = new TextDecoder('utf-8'); +const TEXT_ENCODER = new TextEncoder(); + +export class AutoInstallerFs implements vscode.FileSystemProvider { + + private readonly memfs = new MemFs(); + private readonly fs: FileSystem; + private readonly projectCache = new Map>(); + private readonly watcher: vscode.FileSystemWatcher; + private readonly _emitter = new vscode.EventEmitter(); + + readonly onDidChangeFile: vscode.Event = this._emitter.event; + + constructor() { + this.watcher = vscode.workspace.createFileSystemWatcher('**/{package.json,package-lock.json,package-lock.kdl}'); + const handler = (uri: URI) => { + const root = dirname(uri.path); + if (this.projectCache.delete(root)) { + (async () => { + const pm = new PackageManager(this.fs); + const opts = await this.getInstallOpts(uri, root); + const proj = await pm.resolveProject(root, opts); + proj.pruneExtraneous(); + // TODO: should this fire on vscode-node-modules instead? + // NB(kmarchan): This should tell TSServer that there's + // been changes inside node_modules and it needs to + // re-evaluate things. + this._emitter.fire([{ + type: vscode.FileChangeType.Changed, + uri: uri.with({ path: join(root, 'node_modules') }) + }]); + })(); + } + }; + this.watcher.onDidChange(handler); + this.watcher.onDidCreate(handler); + this.watcher.onDidDelete(handler); + const memfs = this.memfs; + memfs.onDidChangeFile((e) => { + this._emitter.fire(e.map(ev => ({ + type: ev.type, + // TODO: we're gonna need a MappedUri dance... + uri: ev.uri.with({ scheme: 'memfs' }) + }))); + }); + this.fs = { + readDirectory(path: string, _extensions?: readonly string[], _exclude?: readonly string[], _include?: readonly string[], _depth?: number): string[] { + return memfs.readDirectory(URI.file(path)).map(([name, _]) => name); + }, + + deleteFile(path: string): void { + memfs.delete(URI.file(path)); + }, + + createDirectory(path: string): void { + memfs.createDirectory(URI.file(path)); + }, + + writeFile(path: string, data: string, _writeByteOrderMark?: boolean): void { + memfs.writeFile(URI.file(path), TEXT_ENCODER.encode(data), { overwrite: true, create: true }); + }, + + directoryExists(path: string): boolean { + try { + const stat = memfs.stat(URI.file(path)); + return stat.type === vscode.FileType.Directory; + } catch (e) { + return false; + } + }, + + readFile(path: string, _encoding?: string): string | undefined { + try { + return TEXT_DECODER.decode(memfs.readFile(URI.file(path))); + } catch (e) { + return undefined; + } + } + }; + } + + watch(resource: vscode.Uri): vscode.Disposable { + const mapped = URI.file(new MappedUri(resource).path); + console.log('watching', mapped); + return this.memfs.watch(mapped); + } + + async stat(uri: vscode.Uri): Promise { + // console.log('stat', uri.toString()); + const mapped = new MappedUri(uri); + + // TODO: case sensitivity configuration + + // We pretend every single node_modules or @types directory ever actually + // exists. + if (basename(mapped.path) === 'node_modules' || basename(mapped.path) === '@types') { + return { + mtime: 0, + ctime: 0, + type: vscode.FileType.Directory, + size: 0 + }; + } + + await this.ensurePackageContents(mapped); + + return this.memfs.stat(URI.file(mapped.path)); + } + + async readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> { + // console.log('readDirectory', uri.toString()); + const mapped = new MappedUri(uri); + await this.ensurePackageContents(mapped); + + return this.memfs.readDirectory(URI.file(mapped.path)); + } + + async readFile(uri: vscode.Uri): Promise { + // console.log('readFile', uri.toString()); + const mapped = new MappedUri(uri); + await this.ensurePackageContents(mapped); + + return this.memfs.readFile(URI.file(mapped.path)); + } + + writeFile(_uri: vscode.Uri, _content: Uint8Array, _options: { create: boolean; overwrite: boolean }): void { + throw new Error('not implemented'); + } + + rename(_oldUri: vscode.Uri, _newUri: vscode.Uri, _options: { overwrite: boolean }): void { + throw new Error('not implemented'); + } + + delete(_uri: vscode.Uri): void { + throw new Error('not implemented'); + } + + createDirectory(_uri: vscode.Uri): void { + throw new Error('not implemented'); + } + + private async ensurePackageContents(incomingUri: MappedUri): Promise { + // console.log('ensurePackageContents', incomingUri.path); + + // If we're not looking for something inside node_modules, bail early. + if (!incomingUri.path.includes('node_modules')) { + throw vscode.FileSystemError.FileNotFound(); + } + + // standard lib files aren't handled through here + if (incomingUri.path.includes('node_modules/@typescript') || incomingUri.path.includes('node_modules/@types/typescript__')) { + throw vscode.FileSystemError.FileNotFound(); + } + + const root = this.getProjectRoot(incomingUri.path); + + const pkgPath = packagePath(incomingUri.path); + if (!root || this.projectCache.get(root)?.has(pkgPath)) { + return; + } + + const proj = await (new PackageManager(this.fs)).resolveProject(root, await this.getInstallOpts(incomingUri.original, root)); + + const restore = proj.restorePackageAt(incomingUri.path); + try { + await restore; + } catch (e) { + console.error(`failed to restore package at ${incomingUri.path}: `, e); + throw e; + } + if (!this.projectCache.has(root)) { + this.projectCache.set(root, new Set()); + } + this.projectCache.get(root)!.add(pkgPath); + } + + private async getInstallOpts(originalUri: URI, root: string) { + const vsfs = vscode.workspace.fs; + let pkgJson; + try { + pkgJson = TEXT_DECODER.decode(await vsfs.readFile(originalUri.with({ path: join(root, 'package.json') }))); + } catch (e) { } + + let kdlLock; + try { + kdlLock = TEXT_DECODER.decode(await vsfs.readFile(originalUri.with({ path: join(root, 'package-lock.kdl') }))); + } catch (e) { } + + let npmLock; + try { + npmLock = TEXT_DECODER.decode(await vsfs.readFile(originalUri.with({ path: join(root, 'package-lock.json') }))); + } catch (e) { } + + return { + pkgJson, + kdlLock, + npmLock + }; + } + + private getProjectRoot(path: string): string | undefined { + const pkgPath = path.match(/(^.*)\/node_modules/); + return pkgPath?.[1]; + } + + // --- manage file events + +} + +class MappedUri { + readonly raw: vscode.Uri; + readonly original: vscode.Uri; + readonly mapped: vscode.Uri; + constructor(uri: vscode.Uri) { + this.raw = uri; + + const parts = uri.path.match(/^\/([^\/]+)\/([^\/]*)(?:\/(.+))?$/); + if (!parts) { + throw new Error(`Invalid path: ${uri.path}`); + } + + const scheme = parts[1]; + const authority = parts[2] === 'ts-nul-authority' ? '' : parts[2]; + const path = parts[3]; + this.original = URI.from({ scheme, authority, path: (path ? '/' + path : path) }); + this.mapped = this.original.with({ scheme: this.raw.scheme, authority: this.raw.authority }); + } + + get path() { + return this.mapped.path; + } + get scheme() { + return this.mapped.scheme; + } + get authority() { + return this.mapped.authority; + } + get flatPath() { + return join('/', this.scheme, this.authority, this.path); + } +} diff --git a/extensions/typescript-language-features/src/filesystems/memFs.ts b/extensions/typescript-language-features/src/filesystems/memFs.ts new file mode 100644 index 0000000000000..02476ec180426 --- /dev/null +++ b/extensions/typescript-language-features/src/filesystems/memFs.ts @@ -0,0 +1,198 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { basename, dirname } from 'path'; + +export class MemFs implements vscode.FileSystemProvider { + + private readonly root = new FsEntry( + new Map(), + 0, + 0, + ); + + stat(uri: vscode.Uri): vscode.FileStat { + // console.log('stat', uri.toString()); + const entry = this.getEntry(uri); + if (!entry) { + throw vscode.FileSystemError.FileNotFound(); + } + + return entry; + } + + readDirectory(uri: vscode.Uri): [string, vscode.FileType][] { + // console.log('readDirectory', uri.toString()); + + const entry = this.getEntry(uri); + if (!entry) { + throw vscode.FileSystemError.FileNotFound(); + } + + return [...entry.contents.entries()].map(([name, entry]) => [name, entry.type]); + } + + readFile(uri: vscode.Uri): Uint8Array { + // console.log('readFile', uri.toString()); + + const entry = this.getEntry(uri); + if (!entry) { + throw vscode.FileSystemError.FileNotFound(); + } + + return entry.data; + } + + writeFile(uri: vscode.Uri, content: Uint8Array, { create, overwrite }: { create: boolean; overwrite: boolean }): void { + // console.log('writeFile', uri.toString()); + + const dir = this.getParent(uri); + + const fileName = basename(uri.path); + const dirContents = dir.contents; + + const time = Date.now() / 1000; + const entry = dirContents.get(basename(uri.path)); + if (!entry) { + if (create) { + dirContents.set(fileName, new FsEntry(content, time, time)); + this._emitter.fire([{ type: vscode.FileChangeType.Created, uri }]); + } else { + throw vscode.FileSystemError.FileNotFound(); + } + } else { + if (overwrite) { + entry.mtime = time; + entry.data = content; + this._emitter.fire([{ type: vscode.FileChangeType.Changed, uri }]); + } else { + throw vscode.FileSystemError.NoPermissions('overwrite option was not passed in'); + } + } + } + + rename(_oldUri: vscode.Uri, _newUri: vscode.Uri, _options: { overwrite: boolean }): void { + throw new Error('not implemented'); + } + + delete(uri: vscode.Uri): void { + try { + const dir = this.getParent(uri); + dir.contents.delete(basename(uri.path)); + this._emitter.fire([{ type: vscode.FileChangeType.Deleted, uri }]); + } catch (e) { } + } + + createDirectory(uri: vscode.Uri): void { + // console.log('createDirectory', uri.toString()); + const dir = this.getParent(uri); + const now = Date.now() / 1000; + dir.contents.set(basename(uri.path), new FsEntry(new Map(), now, now)); + } + + private getEntry(uri: vscode.Uri): FsEntry | void { + // TODO: have this throw FileNotFound itself? + // TODO: support configuring case sensitivity + let node: FsEntry = this.root; + for (const component of uri.path.split('/')) { + if (!component) { + // Skip empty components (root, stuff between double slashes, + // trailing slashes) + continue; + } + + if (node.type !== vscode.FileType.Directory) { + // We're looking at a File or such, so bail. + return; + } + + const next = node.contents.get(component); + + if (!next) { + // not found! + return; + } + + node = next; + } + return node; + } + + private getParent(uri: vscode.Uri) { + const dir = this.getEntry(uri.with({ path: dirname(uri.path) })); + if (!dir) { + throw vscode.FileSystemError.FileNotFound(); + } + return dir; + } + + // --- manage file events + + private readonly _emitter = new vscode.EventEmitter(); + + readonly onDidChangeFile: vscode.Event = this._emitter.event; + private readonly watchers = new Map>; + + watch(resource: vscode.Uri): vscode.Disposable { + if (!this.watchers.has(resource.path)) { + this.watchers.set(resource.path, new Set()); + } + const sy = Symbol(resource.path); + return new vscode.Disposable(() => { + const watcher = this.watchers.get(resource.path); + if (watcher) { + watcher.delete(sy); + if (!watcher.size) { + this.watchers.delete(resource.path); + } + } + }); + } +} + +class FsEntry { + get type(): vscode.FileType { + if (this._data instanceof Uint8Array) { + return vscode.FileType.File; + } else { + return vscode.FileType.Directory; + } + } + + get size(): number { + if (this.type === vscode.FileType.Directory) { + return [...this.contents.values()].reduce((acc: number, entry: FsEntry) => acc + entry.size, 0); + } else { + return this.data.length; + } + } + + constructor( + private _data: Uint8Array | Map, + public ctime: number, + public mtime: number, + ) { } + + get data() { + if (this.type === vscode.FileType.Directory) { + throw vscode.FileSystemError.FileIsADirectory; + } + return this._data; + } + set data(val: Uint8Array) { + if (this.type === vscode.FileType.Directory) { + throw vscode.FileSystemError.FileIsADirectory; + } + this._data = val; + } + + get contents() { + if (this.type !== vscode.FileType.Directory) { + throw vscode.FileSystemError.FileNotADirectory; + } + return >this._data; + } +} diff --git a/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts b/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts index ab916a1f0e933..c57e6d352c953 100644 --- a/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts +++ b/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts @@ -43,13 +43,15 @@ export class WorkerServerProcessFactory implements TsServerProcessFactory { tsServerLog: TsServerLog | undefined, ) { const tsServerPath = version.tsServerPath; - return new WorkerServerProcess(kind, tsServerPath, this._extensionUri, [ + const launchArgs = [ ...args, - - // Explicitly give TS Server its path so it can - // load local resources + // Explicitly give TS Server its path so it can load local resources '--executingFilePath', tsServerPath, - ], tsServerLog, this._logger); + ]; + if (_configuration.webExperimentalTypeAcquisition) { + launchArgs.push('--experimentalTypeAcquisition'); + } + return new WorkerServerProcess(kind, tsServerPath, this._extensionUri, launchArgs, tsServerLog, this._logger); } } diff --git a/extensions/typescript-language-features/tsconfig.json b/extensions/typescript-language-features/tsconfig.json index dbed493cc43bb..59169c18bfdcb 100644 --- a/extensions/typescript-language-features/tsconfig.json +++ b/extensions/typescript-language-features/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", + "esModuleInterop": true, "experimentalDecorators": true, "types": [ "node" diff --git a/extensions/typescript-language-features/web/README.md b/extensions/typescript-language-features/web/README.md index a9c19b5d72a68..eda9e9cb00b54 100644 --- a/extensions/typescript-language-features/web/README.md +++ b/extensions/typescript-language-features/web/README.md @@ -1,156 +1,45 @@ # vscode-wasm-typescript -Language server host for typescript using vscode's sync-api in the browser +Language server host for typescript using vscode's sync-api in the browser. -## TODOs +## Getting up and running -### Prototype +To test this out, you'll need three shells: -- [x] get semantic diagnostics rendering squigglies - - typescriptserviceclient.ts has some functions that look at `scheme` to determine some features (hasCapabilityForResource) (also getWorkspaceRootForResource) - - known schemes are in utils/fileSchemes.ts, but don't include vscode-test-web - - adding vscode-test-web in a couple places didn't help, maybe I need to be hackier - - nope, another predicate is `isWeb`, so I had to change place(s) it's used too -- [x] cancellation +1. `yarn watch` for vscode itself +2. `yarn watch-web` for the web side +3. `node /scripts/code-web.js --coi` -### Cleanup +The last command will open a browser window. You'll want to add `?vscode-coi=` +to the end. This is for enabling shared array buffers. So, for example: +`http://localhost:8080/?vscode-coi=`. -- [x] point webpack hack to node_modules; link those files to locally built ones -- [x] create one or more MessageChannels for various communication -- [x] shut down normal listener - - starting the server currently crashes because ts.sys isn't defined -- I think it's a race condition. - In any case it'll need to get shut down before then, which may not be possible without changing Typescript. - - LATER: Turns out you can skip the existing server by depending on tsserverlibrary instead of tsserver. -- [x] figure out a webpack-native way to generate tsserver.web.js if possible -- [x] path rewriting is pretty loosey-goosey; likely to be incorrect some of the time - - invert the logic from TypeScriptServiceClient.normalizedPath for requests - - invert the function from webServer.ts for responses (maybe) - - something with getWorkspaceRootForResource (or anything else that checks `resouce.scheme`) -- [x] put files one level down from virtual root -- [x] fill in missing environment files like lib.dom.d.ts - - toResource's isWeb branch *probably* knows where to find this, just need to put it in the virtual FS - - I guess during setup in serverProcess.browser.ts. - - Not sure whether it needs to have the data or just a fs entry. - - Wait, I don't know how files get added to the FS normally. -- [x] cancellation should only retain one cancellation checker - - the one that matches the current request id - - but that means tracking (or retrieving from tsserver) the request id (aka seq?) - - and correctly setting/resetting it on the cancellation token too. - - I looked at the tsserver code. I think the web case is close to the single-pipe node case, - so I just require that requestId is set in order to call the *current* cancellation checker. - - Any incoming message with a cancellation checker will overwrite the current one. -- [x] Cancellation code in vscode is suspiciously prototypey. - - Specifically, it adds the vscode-wasm cancellation to original cancellation code, but should actually switch to the former for web only. - - looks like `isWeb()` is a way to check for being on the web -- [x] create multiple watchers - - on-demand instead of watching everything and checking on watch firing -- [x] get file watching to work - - it could *already* work, I just don't know how to test it - - look at extensions/markdown-language-features/src/client/fileWatchingManager.ts to see if I can use that - - later: it is OK. its main difference is that you can watch files in not-yet-created directories, and it maintains - a web of directory watches that then check whether the file is eventually created. - - even later: well, it works even though it is similar to my code. - I'm not sure what is different. -- [x] copy fileWatchingManager.ts to web/ ; there's no sharing code between extensions -- [x] Find out scheme the web actually uses instead of vscode-test-web (or switch over entirely to isWeb) -- [x] Need to parse and pass args through so that the syntax server isn't hard-coded to actually be another semantic server -- [x] think about implementing all the other ServerHost methods - - [x] copy importPlugin from previous version of webServer.ts - - [x] also copy details from - - previous implementation (although it's syntax-only so only covers part) - - node implementation in typescript proper -- [x] make realpath support symlinks similarly to node's realpath. - - Johannes says that the filesystem automatically follows symlinks, - so I don't think this is needed. -- [x] organise webServer.ts into multiple files - - OR at least re-arrange it so the diff with the previous version is smaller - - split it into multiple files after the initial PR -- [x] clear out TODOs -- [x] add semicolons everywhere; vscode's lint doesn't seem to complain, but the code clearly uses them -- [x] Further questions about host methods based on existing implementations - - `require` -- is this needed? In TS, it's only used in project system - - `trace` -- is this needed? In TS, it's only used in project system - - `useCaseSensitiveFileNames` -- old version says 'false' is the - safest option, but the virtual fs is case sensitive. Is the old - version still better? - - `writeOutputIsTTY` -- I'm using apiClient.vscode.terminal.write -- is it a tty? - - `getWidthOfTerminal` -- I don't know where to find this on apiClient.vscode.terminal either - - `clearScreen` -- node version writes \x1BC to the terminal. Would - this work for vscode? - - `readFile/writeFile` -- TS handles utf8, utf16le and manually - converts big-endian to utf16 little-endian. How does the in-memory - filesystem handle this? There's no place to specify encoding. (And - `writeFile` currently ignores the flag to write a BOM.) - - `resolvePath` -- node version uses path.resolve. Is it OK to use - that? Or should I re-implement it? Just use identity like the old - web code? - - `getDirectories`/`readDirectory` - - the node code manually skips '.' and '..' in the array returned by - readDirectory. Is this needed? - - `createSHA256Hash` -- the browser version is async, so I skipped it - - `realpath` -- still skips symlinks, I need to figure out what node does +### Working on type acquisition -### Bugs +In order to work with web's new type acquisition, you'll need to enable +`TypeScript > Experimental > Tsserver > Web: Enable Project Wide Intellisense` +in your VS Code options (`Ctrl-,`), you may need to reload the page. -- [x] Response `seq` is always 0. -- [ ] current method of encoding /scheme/authority means that (node) module resolution looks for /scheme/node_modules and /node_modules - - even though they can't possibly exist - - probably not a problem though -- [x] problems pane doesn't clear problems issued on tsconfig. - - This is a known problem in normal usage as well. -- [x] renaming a file throws a No Project error to the console. -- [x] gotodef in another file throws and the editor has a special UI for it. - - definitionProviderBase.getSymbolLocations calls toOpenedFilePath which eventually calls the new / code - - then it calls client.execute which appears to actually request/response to the tsserver - - then the response body is mapped over location.file >> client.toResource >> fromTextSpan - - toResource has isWeb support, as well as (now unused) inMemoryResourcePrefix support - - so I can just redo whatever that did and it'll be fine +This happens when working in a regular `.js` file on a dependency without +declared types. You should be able to open `file.js` and write something like +`import lodash from 'lodash';` at the top of the file and, after a moment, get +types and other intellisense features (like Go To Def/Source Def) working as +expected. This scenario works off Tsserver's own Automatic Type Acquisition +capabilities, and simulates a "global" types cache stored at +`/vscode-global-typings/ts-nul-authority/project`, which is backed by an +in-memory `MemFs` `FileSystemProvider`. -### Done +### Simulated `node_modules` -- [x] need to update 0.2 -> 0.7.* API (once it's working properly) -- [x] including reshuffling the webpack hack if needed -- [x] need to use the settings recommended by Sheetal -- [x] ProjectService always requests a typesMap.json at the cwd -- [x] sync-api-client says fs is rooted at memfs:/sample-folder; the protocol 'memfs:' is confusing our file parsing I think -- [x] nothing ever seems to find tsconfig.json -- [x] messages aren't actually coming through, just the message from the first request - - fixed by simplifying the listener setup for now -- [x] once messages work, you can probably log by postMessage({ type: 'log', body: "some logging text" }) -- [x] implement realpath, modifiedtime, resolvepath, then turn semantic mode on -- [x] file watching implemented with saved map of filename to callback, and forwarding +For regular `.ts` files, instead of going through Tsserver's type acquisition, +a separate `AutoInstallerFs` is used to create a "virtual" `node_modules` that +extracts desired packages on demand, to an underlying `MemFs`. This will +happen any time a filesystem operation is done inside a `node_modules` folder +across any project in the workspace, and will use the "real" `package.json` +(and, if present, `package-lock.json`) to resolve the dependency tree. -### Also - -- [ ] ATA will eventually need a host interface, or an improvement of the existing one (?) - -## Notes - -messages received by extension AND host use paths like ^/memfs/ts-nul-authority/sample-folder/file.ts - -- problem: pretty sure the extension doesn't know what to do with that: it's not putting down error spans in file.ts -- question: why is the extension requesting quickinfo in that URI format? And it works! (probably because the result is a tooltip, not an in-file span) -- problem: weird concatenations with memfs:/ in the middle -- problem: weird concatenations with ^/memfs/ts-nul-authority in the middle - -question: where is the population of sample-folder with a bunch of files happening? - -question: Is that location writable while it's running? - -but readFile is getting called with things like memfs:/sample-folder/memfs:/typesMap.json - directoryExists with /sample-folder/node_modules/@types and /node_modules/@types - same for watchDirectory - watchDirectory with /sample-folder/^ and directoryExists with /sample-folder/^/memfs/ts-nul-authority/sample-folder/workspaces/ - watchFile with /sample-folder/memfs:/sample-folder/memfs:/lib.es2020.full.d.ts - -### LATER - -OK, so the paths that tsserver has look like this: ^/scheme/mount/whatever.ts -but the paths the filesystem has look like this: scheme:/whatever.ts (not sure about 'mount', that's only when cloning from the fs) -so you have to shave off the scheme that the host combined with the path and put on the scheme that the vfs is using. - -### LATER 2 - -Some commands ask for getExecutingFilePath or getCurrentDirectory and cons up a path themselves. -This works, because URI.from({ scheme, path }) matches what the fs has in it -Problem: In *some* messages (all?), vscode then refers to /x.ts and ^/vscode-test-web/mount/x.ts (or ^/memfs/ts-nul-authority/x.ts) +A fallback is then set up such that when a URI like +`memfs:/path/to/node_modules/lodash/lodash.d.ts` is accessed, that gets +redirected to +`vscode-node-modules:/ts-nul-authority/memfs/ts-nul-authority/path/to/node_modules/lodash/lodash.d.ts`, +which will be sent to the `AutoInstallerFs`. diff --git a/extensions/typescript-language-features/web/jsTyping.ts b/extensions/typescript-language-features/web/jsTyping.ts new file mode 100644 index 0000000000000..bd940e88c1c37 --- /dev/null +++ b/extensions/typescript-language-features/web/jsTyping.ts @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +/// Utilities copied from ts.JsTyping internals + +export const enum NameValidationResult { + Ok, + EmptyName, + NameTooLong, + NameStartsWithDot, + NameStartsWithUnderscore, + NameContainsNonURISafeCharacters +} + +type PackageNameValidationResult = NameValidationResult | ScopedPackageNameValidationResult; + +interface ScopedPackageNameValidationResult { + readonly name: string; + readonly isScopeName: boolean; + readonly result: NameValidationResult; +} + +enum CharacterCodes { + _ = 0x5F, + dot = 0x2E, +} + +const maxPackageNameLength = 214; + +// Validates package name using rules defined at https://docs.npmjs.com/files/package.json +// Copied from typescript/jsTypings.ts +export function validatePackageNameWorker(packageName: string, supportScopedPackage: true): ScopedPackageNameValidationResult; +export function validatePackageNameWorker(packageName: string, supportScopedPackage: false): NameValidationResult; +export function validatePackageNameWorker(packageName: string, supportScopedPackage: boolean): PackageNameValidationResult { + if (!packageName) { + return NameValidationResult.EmptyName; + } + if (packageName.length > maxPackageNameLength) { + return NameValidationResult.NameTooLong; + } + if (packageName.charCodeAt(0) === CharacterCodes.dot) { + return NameValidationResult.NameStartsWithDot; + } + if (packageName.charCodeAt(0) === CharacterCodes._) { + return NameValidationResult.NameStartsWithUnderscore; + } + + // check if name is scope package like: starts with @ and has one '/' in the middle + // scoped packages are not currently supported + if (supportScopedPackage) { + const matches = /^@([^/]+)\/([^/]+)$/.exec(packageName); + if (matches) { + const scopeResult = validatePackageNameWorker(matches[1], /*supportScopedPackage*/ false); + if (scopeResult !== NameValidationResult.Ok) { + return { name: matches[1], isScopeName: true, result: scopeResult }; + } + const packageResult = validatePackageNameWorker(matches[2], /*supportScopedPackage*/ false); + if (packageResult !== NameValidationResult.Ok) { + return { name: matches[2], isScopeName: false, result: packageResult }; + } + return NameValidationResult.Ok; + } + } + + if (encodeURIComponent(packageName) !== packageName) { + return NameValidationResult.NameContainsNonURISafeCharacters; + } + + return NameValidationResult.Ok; +} + +export interface TypingResolutionHost { + directoryExists(path: string): boolean; + fileExists(fileName: string): boolean; + readFile(path: string, encoding?: string): string | undefined; + readDirectory(rootDir: string, extensions: readonly string[], excludes: readonly string[] | undefined, includes: readonly string[] | undefined, depth?: number): string[]; +} diff --git a/extensions/typescript-language-features/web/tsconfig.json b/extensions/typescript-language-features/web/tsconfig.json index 9944d5b63d8f9..531d57bddcbff 100644 --- a/extensions/typescript-language-features/web/tsconfig.json +++ b/extensions/typescript-language-features/web/tsconfig.json @@ -2,8 +2,7 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "../../out", - "module": "nodenext", - "moduleDetection": "legacy", + "esModuleInterop": true, "experimentalDecorators": true, "types": [ "node" diff --git a/extensions/typescript-language-features/web/typingsInstaller.ts b/extensions/typescript-language-features/web/typingsInstaller.ts new file mode 100644 index 0000000000000..7b9b164c40c52 --- /dev/null +++ b/extensions/typescript-language-features/web/typingsInstaller.ts @@ -0,0 +1,213 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/* + * This file implements the global typings installer API for web clients. It + * uses [nassun](https://docs.rs/nassun) and + * [node-maintainer](https://docs.rs/node-maintainer) to install typings + * in-memory (and maybe eventually cache them in IndexedDB?). + * + * Implementing a typings installer involves implementing two parts: + * + * -> ITypingsInstaller: the "top level" interface that tsserver uses to + * request typings. Implementers of this interface are what actually get + * passed to tsserver. + * + * -> TypingsInstaller: an abstract class that implements a good chunk of + * the "generic" functionality for what ITypingsInstaller needs to do. For + * implementation detail reasons, it does this in a "server/client" model of + * sorts. In our case, we don't need a separate process, or even _quite_ a + * pure "server/client" model, so we play along a bit for the sake of reusing + * the stuff the abstract class is already doing for us. + */ + +import { PackageManager, PackageType } from '@vscode/ts-package-manager'; +import { join } from 'path'; +import * as ts from 'typescript/lib/tsserverlibrary'; +import { NameValidationResult, validatePackageNameWorker } from './jsTyping'; + +type InstallerResponse = ts.server.PackageInstalledResponse | ts.server.SetTypings | ts.server.InvalidateCachedTypings | ts.server.BeginInstallTypes | ts.server.EndInstallTypes | ts.server.WatchTypingLocations; + +/** + * The "server" part of the "server/client" model. This is the part that + * actually gets instantiated and passed to tsserver. + */ +export default class WebTypingsInstallerClient implements ts.server.ITypingsInstaller { + + private projectService: ts.server.ProjectService | undefined; + + private requestedRegistry = false; + + private typesRegistryCache: Map> = new Map(); + + private readonly server: Promise; + + constructor( + private readonly fs: ts.server.ServerHost, + readonly globalTypingsCacheLocation: string, + ) { + this.server = WebTypingsInstallerServer.initialize( + (response: InstallerResponse) => this.handleResponse(response), + this.fs, + globalTypingsCacheLocation + ); + } + + /** + * TypingsInstaller expects a "server/client" model, and as such, some of + * its methods are implemented in terms of sending responses back to a + * client. This method is a catch-all for those responses generated by + * TypingsInstaller internals. + */ + private async handleResponse(response: InstallerResponse): Promise { + switch (response.kind) { + case 'action::packageInstalled': + case 'action::invalidate': + case 'action::set': + this.projectService!.updateTypingsForProject(response); + break; + case 'event::beginInstallTypes': + case 'event::endInstallTypes': + // Don't care. + break; + default: + throw new Error(`unexpected response: ${response}`); + } + } + + // NB(kmarchan): this is a code action that expects an actual NPM-specific + // installation. We shouldn't mess with this ourselves. + async installPackage(_options: ts.server.InstallPackageOptionsWithProject): Promise { + throw new Error('not implemented'); + } + + // NB(kmarchan): As far as I can tell, this is only ever used for + // completions? + isKnownTypesPackageName(packageName: string): boolean { + console.log('isKnownTypesPackageName', packageName); + const looksLikeValidName = validatePackageNameWorker(packageName, true); + if (looksLikeValidName.result !== NameValidationResult.Ok) { + return false; + } + + if (this.requestedRegistry) { + return !!this.typesRegistryCache && this.typesRegistryCache.has(packageName); + } + + this.requestedRegistry = true; + this.server.then(s => this.typesRegistryCache = s.typesRegistry); + return false; + } + + enqueueInstallTypingsRequest(p: ts.server.Project, typeAcquisition: ts.TypeAcquisition, unresolvedImports: ts.SortedReadonlyArray): void { + console.log('enqueueInstallTypingsRequest', typeAcquisition, unresolvedImports); + const req = ts.server.createInstallTypingsRequest(p, typeAcquisition, unresolvedImports); + this.server.then(s => s.install(req)); + } + + attach(projectService: ts.server.ProjectService): void { + this.projectService = projectService; + } + + onProjectClosed(_projectService: ts.server.Project): void { + // noop + } +} + +/** + * Internal implementation of the "server" part of the "server/client" model. + * This takes advantage of the existing TypingsInstaller to reuse a lot of + * already-implemented logic around package installation, but with + * installation details handled by Nassun/Node Maintainer. + */ +class WebTypingsInstallerServer extends ts.server.typingsInstaller.TypingsInstaller { + + private static readonly typesRegistryPackageName = 'types-registry'; + + private constructor( + override typesRegistry: Map>, + private readonly handleResponse: (response: InstallerResponse) => void, + fs: ts.server.ServerHost, + private readonly packageManager: PackageManager, + globalTypingsCachePath: string, + ) { + super(fs, globalTypingsCachePath, join(globalTypingsCachePath, 'fakeSafeList') as ts.Path, join(globalTypingsCachePath, 'fakeTypesMapLocation') as ts.Path, Infinity); + } + + /** + * Because loading the typesRegistry is an async operation for us, we need + * to have a separate "constructor" that will be used by + * WebTypingsInstallerClient. + * + * @returns a promise that resolves to a WebTypingsInstallerServer + */ + static async initialize( + handleResponse: (response: InstallerResponse) => void, + fs: ts.server.ServerHost, + globalTypingsCachePath: string, + ): Promise { + const pm = new PackageManager(fs); + const pkgJson = join(globalTypingsCachePath, 'package.json'); + if (!fs.fileExists(pkgJson)) { + fs.writeFile(pkgJson, '{"private":true}'); + } + const resolved = await pm.resolveProject(globalTypingsCachePath, { + addPackages: [this.typesRegistryPackageName] + }); + await resolved.restore(); + + const registry = new Map>(); + const indexPath = join(globalTypingsCachePath, 'node_modules/types-registry/index.json'); + const index = WebTypingsInstallerServer.readJson(fs, indexPath); + for (const [packageName, entry] of Object.entries(index.entries)) { + registry.set(packageName, entry as ts.MapLike); + } + console.log('ATA registry loaded'); + return new WebTypingsInstallerServer(registry, handleResponse, fs, pm, globalTypingsCachePath); + } + + /** + * Implements the actual logic of installing a set of given packages. It + * does this by looking up the latest versions of those packages using + * Nassun, then handing Node Maintainer the updated package.json to run a + * full install (modulo existing lockfiles, which can make this faster). + */ + protected override installWorker(requestId: number, packageNames: string[], cwd: string, onRequestCompleted: ts.server.typingsInstaller.RequestCompletedAction): void { + console.log('installWorker', requestId, cwd); + (async () => { + try { + const resolved = await this.packageManager.resolveProject(cwd, { + addPackages: packageNames, + packageType: PackageType.DevDependency + }); + await resolved.restore(); + onRequestCompleted(true); + } catch (e) { + onRequestCompleted(false); + } + })(); + } + + /** + * This is a thing that TypingsInstaller uses internally to send + * responses, and we'll need to handle this in the Client later. + */ + protected override sendResponse(response: InstallerResponse): void { + this.handleResponse(response); + } + + /** + * What it says on the tin. Reads a JSON file from the given path. Throws + * if the file doesn't exist (as opposed to returning `undefined`, like + * fs.readFile does). + */ + private static readJson(fs: ts.server.ServerHost, path: string): any { + const data = fs.readFile(path); + if (!data) { + throw new Error('Failed to read file: ' + path); + } + return JSON.parse(data.trim()); + } +} diff --git a/extensions/typescript-language-features/web/webServer.ts b/extensions/typescript-language-features/web/webServer.ts index 1688a93fcc023..191c2d03f6321 100644 --- a/extensions/typescript-language-features/web/webServer.ts +++ b/extensions/typescript-language-features/web/webServer.ts @@ -5,10 +5,12 @@ /// /// -import * as ts from 'typescript/lib/tsserverlibrary'; -import { ApiClient, FileType, Requests } from '@vscode/sync-api-client'; +import { ApiClient, FileStat, FileSystem, FileType, Requests } from '@vscode/sync-api-client'; import { ClientConnection } from '@vscode/sync-api-common/browser'; +import { basename } from 'path'; +import * as ts from 'typescript/lib/tsserverlibrary'; import { URI } from 'vscode-uri'; +import WebTypingsInstaller from './typingsInstaller'; // GLOBALS const watchFiles: Map = new Map(); @@ -87,7 +89,7 @@ class AccessOutsideOfRootError extends Error { type ServerHostWithImport = ts.server.ServerHost & { importPlugin(root: string, moduleName: string): Promise }; -function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient: ApiClient | undefined, args: string[], fsWatcher: MessagePort): ServerHostWithImport { +function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient: ApiClient | undefined, args: string[], fsWatcher: MessagePort, enabledExperimentalTypeAcquisition: boolean): ServerHostWithImport { const currentDirectory = '/'; const fs = apiClient?.vscode.workspace.fileSystem; let watchId = 0; @@ -99,7 +101,6 @@ function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient const directorySeparator: string = (ts as any).directorySeparator; const executingFilePath = findArgument(args, '--executingFilePath') || location + ''; const getExecutingDirectoryPath = memoize(() => memoize(() => ensureTrailingDirectorySeparator(getDirectoryPath(executingFilePath)))); - // Later we could map ^memfs:/ to do something special if we want to enable more functionality like module resolution or something like that const getWebPath = (path: string) => path.startsWith(directorySeparator) ? path.replace(directorySeparator, getExecutingDirectoryPath()) : undefined; const textDecoder = new TextDecoder(); @@ -121,6 +122,8 @@ function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient return noopWatcher; } + console.log('watching file:', path); + logVerbose('fs.watchFile', { path }); let uri: URI; @@ -132,14 +135,20 @@ function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient } watchFiles.set(path, { path, callback, pollingInterval, options }); - watchId++; - fsWatcher.postMessage({ type: 'watchFile', uri, id: watchId }); + const watchIds = [++watchId]; + fsWatcher.postMessage({ type: 'watchFile', uri: uri, id: watchIds[0] }); + if (enabledExperimentalTypeAcquisition && looksLikeNodeModules(path)) { + watchIds.push(++watchId); + fsWatcher.postMessage({ type: 'watchFile', uri: mapUri(uri, 'vscode-node-modules'), id: watchIds[1] }); + } return { close() { logVerbose('fs.watchFile.close', { path }); watchFiles.delete(path); - fsWatcher.postMessage({ type: 'dispose', id: watchId }); + for (const id of watchIds) { + fsWatcher.postMessage({ type: 'dispose', id }); + } } }; }, @@ -155,14 +164,16 @@ function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient } watchDirectories.set(path, { path, callback, recursive, options }); - watchId++; + const watchIds = [++watchId]; fsWatcher.postMessage({ type: 'watchDirectory', recursive, uri, id: watchId }); return { close() { logVerbose('fs.watchDirectory.close', { path }); watchDirectories.delete(path); - fsWatcher.postMessage({ type: 'dispose', id: watchId }); + for (const id of watchIds) { + fsWatcher.postMessage({ type: 'dispose', id }); + } } }; }, @@ -226,14 +237,28 @@ function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient } } + let uri; + try { + uri = toResource(path); + } catch (e) { + return undefined; + } + + let contents: Uint8Array | undefined; try { // We need to slice the bytes since we can't pass a shared array to text decoder - const contents = fs.readFile(toResource(path)).slice(); - return textDecoder.decode(contents); + contents = fs.readFile(uri); } catch (error) { - logNormal('Error fs.readFile', { path, error: error + '' }); - return undefined; + if (!enabledExperimentalTypeAcquisition) { + return undefined; + } + try { + contents = fs.readFile(mapUri(uri, 'vscode-node-modules')); + } catch (e) { + return undefined; + } } + return textDecoder.decode(contents.slice()); }, getFileSize(path) { logVerbose('fs.getFileSize', { path }); @@ -242,12 +267,19 @@ function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient throw new Error('not supported'); } + const uri = toResource(path); + let ret = 0; try { - return fs.stat(toResource(path)).size; - } catch (error) { - logNormal('Error fs.getFileSize', { path, error: error + '' }); - return 0; + ret = fs.stat(uri).size; + } catch (_error) { + if (enabledExperimentalTypeAcquisition) { + try { + ret = fs.stat(mapUri(uri, 'vscode-node-modules')).size; + } catch (_error) { + } + } } + return ret; }, writeFile(path, data, writeByteOrderMark) { logVerbose('fs.writeFile', { path }); @@ -260,10 +292,21 @@ function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient data = byteOrderMarkIndicator + data; } + let uri; try { - fs.writeFile(toResource(path), textEncoder.encode(data)); + uri = toResource(path); + } catch (e) { + return; + } + const encoded = textEncoder.encode(data); + try { + fs.writeFile(uri, encoded); + const name = basename(uri.path); + if (uri.scheme !== 'vscode-global-typings' && (name === 'package.json' || name === 'package-lock.json' || name === 'package-lock.kdl')) { + fs.writeFile(mapUri(uri, 'vscode-node-modules'), encoded); + } } catch (error) { - logNormal('Error fs.writeFile', { path, error: error + '' }); + console.error('fs.writeFile', { path, error }); } }, resolvePath(path: string): string { @@ -284,12 +327,24 @@ function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient return request.status === 200; } + let uri; try { - return fs.stat(toResource(path)).type === FileType.File; - } catch (error) { - logNormal('Error fs.fileExists', { path, error: error + '' }); + uri = toResource(path); + } catch (e) { return false; } + let ret = false; + try { + ret = fs.stat(uri).type === FileType.File; + } catch (_error) { + if (enabledExperimentalTypeAcquisition) { + try { + ret = fs.stat(mapUri(uri, 'vscode-node-modules')).type === FileType.File; + } catch (_error) { + } + } + } + return ret; }, directoryExists(path: string): boolean { logVerbose('fs.directoryExists', { path }); @@ -298,10 +353,32 @@ function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient return false; } + let uri; try { - return fs.stat(toResource(path)).type === FileType.Directory; - } catch (error) { - logNormal('Error fs.directoryExists', { path, error: error + '' }); + uri = toResource(path); + } catch (_error) { + return false; + } + + let stat: FileStat | undefined = undefined; + try { + stat = fs.stat(uri); + } catch (_error) { + if (enabledExperimentalTypeAcquisition) { + try { + stat = fs.stat(mapUri(uri, 'vscode-node-modules')); + } catch (_error) { + } + } + } + if (stat) { + if (path.startsWith('/https') && !path.endsWith('.d.ts')) { + // TODO: Hack, https "file system" can't actually tell what is a file vs directory + return stat.type === FileType.File || stat.type === FileType.Directory; + } + + return stat.type === FileType.Directory; + } else { return false; } }, @@ -341,12 +418,19 @@ function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient throw new Error('not supported'); } + const uri = toResource(path); + let s: FileStat | undefined = undefined; try { - return new Date(fs.stat(toResource(path)).mtime); - } catch (error) { - logNormal('Error fs.getModifiedTime', { path, error: error + '' }); - return undefined; + s = fs.stat(uri); + } catch (_e) { + if (enabledExperimentalTypeAcquisition) { + try { + s = fs.stat(mapUri(uri, 'vscode-node-modules')); + } catch (_e) { + } + } } + return s && new Date(s.mtime); }, deleteFile(path: string): void { logVerbose('fs.deleteFile', { path }); @@ -373,13 +457,19 @@ function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient base64encode: input => Buffer.from(input).toString('base64'), }; - /** For module resolution only; symlinks aren't supported yet. */ + // For module resolution only. `node_modules` is also automatically mapped + // as if all node_modules-like paths are symlinked. function realpath(path: string): string { - // skip paths without .. or ./ or /. - if (!path.match(/\.\.|\/\.|\.\//)) { + const isNm = looksLikeNodeModules(path) && !path.startsWith('/vscode-global-typings/'); + // skip paths without .. or ./ or /. And things that look like node_modules + if (!isNm && !path.match(/\.\.|\/\.|\.\//)) { return path; } - const uri = toResource(path); + + let uri = toResource(path); + if (isNm) { + uri = mapUri(uri, 'vscode-node-modules'); + } const out = [uri.scheme]; if (uri.authority) { out.push(uri.authority); } for (const part of uri.path.split('/')) { @@ -403,31 +493,35 @@ function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient throw new Error('not supported'); } + const uri = toResource(path || '.'); + let entries: [string, FileType][] = []; + const files: string[] = []; + const directories: string[] = []; try { - const uri = toResource(path || '.'); - const entries = fs.readDirectory(uri); - const files: string[] = []; - const directories: string[] = []; - for (const [entry, type] of entries) { - // This is necessary because on some file system node fails to exclude - // '.' and '..'. See https://github.com/nodejs/node/issues/4002 - if (entry === '.' || entry === '..') { - continue; - } + entries = fs.readDirectory(uri); + } catch (_e) { + try { + entries = fs.readDirectory(mapUri(uri, 'vscode-node-modules')); + } catch (_e) { + } + } + for (const [entry, type] of entries) { + // This is necessary because on some file system node fails to exclude + // '.' and '..'. See https://github.com/nodejs/node/issues/4002 + if (entry === '.' || entry === '..') { + continue; + } - if (type === FileType.File) { - files.push(entry); - } - else if (type === FileType.Directory) { - directories.push(entry); - } + if (type === FileType.File) { + files.push(entry); + } + else if (type === FileType.Directory) { + directories.push(entry); } - files.sort(); - directories.sort(); - return { files, directories }; - } catch (e) { - return { files: [], directories: [] }; } + files.sort(); + directories.sort(); + return { files, directories }; } /** @@ -475,6 +569,10 @@ function looksLikeLibDtsPath(filepath: string) { return filepath.startsWith('/lib.') && filepath.endsWith('.d.ts'); } +function looksLikeNodeModules(filepath: string) { + return filepath.includes('/node_modules'); +} + function filePathToResourceUri(filepath: string): URI | undefined { const parts = filepath.match(/^\/([^\/]+)\/([^\/]*)(?:\/(.+))?$/); if (!parts) { @@ -509,14 +607,15 @@ class WasmCancellationToken implements ts.server.ServerCancellationToken { } interface StartSessionOptions { - globalPlugins: ts.server.SessionOptions['globalPlugins']; - pluginProbeLocations: ts.server.SessionOptions['pluginProbeLocations']; - allowLocalPluginLoads: ts.server.SessionOptions['allowLocalPluginLoads']; - useSingleInferredProject: ts.server.SessionOptions['useSingleInferredProject']; - useInferredProjectPerProjectRoot: ts.server.SessionOptions['useInferredProjectPerProjectRoot']; - suppressDiagnosticEvents: ts.server.SessionOptions['suppressDiagnosticEvents']; - noGetErrOnBackgroundUpdate: ts.server.SessionOptions['noGetErrOnBackgroundUpdate']; - serverMode: ts.server.SessionOptions['serverMode']; + readonly globalPlugins: ts.server.SessionOptions['globalPlugins']; + readonly pluginProbeLocations: ts.server.SessionOptions['pluginProbeLocations']; + readonly allowLocalPluginLoads: ts.server.SessionOptions['allowLocalPluginLoads']; + readonly useSingleInferredProject: ts.server.SessionOptions['useSingleInferredProject']; + readonly useInferredProjectPerProjectRoot: ts.server.SessionOptions['useInferredProjectPerProjectRoot']; + readonly suppressDiagnosticEvents: ts.server.SessionOptions['suppressDiagnosticEvents']; + readonly noGetErrOnBackgroundUpdate: ts.server.SessionOptions['noGetErrOnBackgroundUpdate']; + readonly serverMode: ts.server.SessionOptions['serverMode']; + readonly disableAutomaticTypingAcquisition: boolean; } class WorkerSession extends ts.server.Session<{}> { @@ -526,17 +625,20 @@ class WorkerSession extends ts.server.Session<{}> { constructor( host: ts.server.ServerHost, + fs: FileSystem | undefined, options: StartSessionOptions, - public readonly port: MessagePort, + private readonly port: MessagePort, logger: ts.server.Logger, hrtime: ts.server.SessionOptions['hrtime'] ) { const cancellationToken = new WasmCancellationToken(); + const typingsInstaller = options.disableAutomaticTypingAcquisition || !fs ? ts.server.nullTypingsInstaller : new WebTypingsInstaller(host, '/vscode-global-typings/ts-nul-authority/projects'); + super({ host, cancellationToken, ...options, - typingsInstaller: ts.server.nullTypingsInstaller, // TODO: Someday! + typingsInstaller, byteLength: () => { throw new Error('Not implemented'); }, // Formats the message text in send of Session which is overridden in this class so not needed hrtime, logger, @@ -671,7 +773,7 @@ async function initializeSession(args: string[], extensionUri: URI, ports: { tss logger.info(`Version: 0.0.0`); logger.info(`Arguments: ${args.join(' ')}`); logger.info(`ServerMode: ${serverMode} unknownServerMode: ${unknownServerMode}`); - const options = { + const options: StartSessionOptions = { globalPlugins: findArgumentStringArray(args, '--globalPlugins'), pluginProbeLocations: findArgumentStringArray(args, '--pluginProbeLocations'), allowLocalPluginLoads: hasArgument(args, '--allowLocalPluginLoads'), @@ -679,22 +781,27 @@ async function initializeSession(args: string[], extensionUri: URI, ports: { tss useInferredProjectPerProjectRoot: hasArgument(args, '--useInferredProjectPerProjectRoot'), suppressDiagnosticEvents: hasArgument(args, '--suppressDiagnosticEvents'), noGetErrOnBackgroundUpdate: hasArgument(args, '--noGetErrOnBackgroundUpdate'), - serverMode + serverMode, + disableAutomaticTypingAcquisition: hasArgument(args, '--disableAutomaticTypingAcquisition'), }; + let sys: ServerHostWithImport; + let fs: FileSystem | undefined; if (hasArgument(args, '--enableProjectWideIntelliSenseOnWeb')) { + const enabledExperimentalTypeAcquisition = hasArgument(args, '--experimentalTypeAcquisition'); const connection = new ClientConnection(ports.sync); await connection.serviceReady(); - sys = createServerHost(extensionUri, logger, new ApiClient(connection), args, ports.watcher); + const apiClient = new ApiClient(connection); + fs = apiClient.vscode.workspace.fileSystem; + sys = createServerHost(extensionUri, logger, apiClient, args, ports.watcher, enabledExperimentalTypeAcquisition); } else { - sys = createServerHost(extensionUri, logger, undefined, args, ports.watcher); - + sys = createServerHost(extensionUri, logger, undefined, args, ports.watcher, false); } setSys(sys); - session = new WorkerSession(sys, options, ports.tsserver, logger, hrtime); + session = new WorkerSession(sys, fs, options, ports.tsserver, logger, hrtime); session.listen(); } @@ -743,3 +850,15 @@ const listener = async (e: any) => { console.error(`unexpected message on main channel: ${JSON.stringify(e)}`); }; addEventListener('message', listener); + +function mapUri(uri: URI, mappedScheme: string): URI { + if (uri.scheme === 'vscode-global-typings') { + throw new Error('can\'t map vscode-global-typings'); + } + if (!uri.authority) { + uri = uri.with({ authority: 'ts-nul-authority' }); + } + uri = uri.with({ scheme: mappedScheme, path: `/${uri.scheme}/${uri.authority || 'ts-nul-authority'}${uri.path}` }); + + return uri; +} diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index eedf2d9c58027..710e6c5413267 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -9,15 +9,7 @@ dependencies: tslib "^2.2.0" -"@azure/core-auth@^1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.4.0.tgz#6fa9661c1705857820dbc216df5ba5665ac36a9e" - integrity sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ== - dependencies: - "@azure/abort-controller" "^1.0.0" - tslib "^2.2.0" - -"@azure/core-auth@^1.5.0": +"@azure/core-auth@^1.4.0", "@azure/core-auth@^1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.5.0.tgz#a41848c5c31cb3b7c84c409885267d55a2c92e44" integrity sha512-udzoBuYG1VBoHVohDTrvKjyzel34zt77Bhp7dQntVGGD0ehVq48owENbBG8fIgkHRNUBQH5k1r0hpoMu5L8+kw== @@ -57,15 +49,7 @@ "@azure/abort-controller" "^1.0.0" tslib "^2.2.0" -"@azure/core-util@^1.0.0": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.1.1.tgz#8f87b3dd468795df0f0849d9f096c3e7b29452c1" - integrity sha512-A4TBYVQCtHOigFb2ETiiKFDocBoI1Zk2Ui1KpI42aJSIDexF7DHQFpnjonltXAIU/ceH+1fsZAWWgvX6/AKzog== - dependencies: - "@azure/abort-controller" "^1.0.0" - tslib "^2.2.0" - -"@azure/core-util@^1.1.0": +"@azure/core-util@^1.0.0", "@azure/core-util@^1.1.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.4.0.tgz#c120a56b3e48a9e4d20619a0b00268ae9de891c7" integrity sha512-eGAyJpm3skVQoLiRqm/xPa+SXi/NPDdSHMxbRAz2lSprd+Zs+qrpQGQQ2VQ3Nttu+nSZR4XoYQC71LbEI7jsig== @@ -74,9 +58,9 @@ tslib "^2.2.0" "@azure/logger@^1.0.0": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.0.3.tgz#6e36704aa51be7d4a1bae24731ea580836293c96" - integrity sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g== + version "1.0.4" + resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.0.4.tgz#28bc6d0e5b3c38ef29296b32d35da4e483593fa1" + integrity sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg== dependencies: tslib "^2.2.0" @@ -180,12 +164,7 @@ resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web-snippet/-/applicationinsights-web-snippet-1.0.1.tgz#6bb788b2902e48bf5d460c38c6bb7fedd686ddd7" integrity sha512-2IHAOaLauc8qaAitvWS+U931T+ze+7MNWrDHY47IENP5y2UA0vqJDu67kWZDdpCN1fFC77sfgfB+HV7SrKshnQ== -"@microsoft/dynamicproto-js@^1.1.7": - version "1.1.7" - resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.7.tgz#ede48dd3f85af14ee369c805e5ed5b84222b9fe2" - integrity sha512-SK3D3aVt+5vOOccKPnGaJWB5gQ8FuKfjboUJHedMP7gu54HqSCXX5iFXhktGD8nfJb0Go30eDvs/UDoTnR2kOA== - -"@microsoft/dynamicproto-js@^1.1.9": +"@microsoft/dynamicproto-js@^1.1.7", "@microsoft/dynamicproto-js@^1.1.9": version "1.1.9" resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.9.tgz#7437db7aa061162ee94e4131b69a62b8dad5dea6" integrity sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ== @@ -260,9 +239,9 @@ integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== "@types/node@18.x": - version "18.15.13" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" - integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== + version "18.17.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.17.11.tgz#c04054659d88bfeba94095f41ef99a8ddf4e1813" + integrity sha512-r3hjHPBu+3LzbGBa8DHnr/KAeTEEOrahkcL+cZc4MaBMTM+mk8LtXR+zw+nqfjuDZZzYTYgTcpHuP+BEQk069g== "@types/semver@^5.5.0": version "5.5.0" @@ -305,6 +284,11 @@ "@vscode/sync-api-common" "0.7.2" vscode-uri "3.0.3" +"@vscode/ts-package-manager@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@vscode/ts-package-manager/-/ts-package-manager-0.0.2.tgz#d1cade5ff0d01da8c5b5b00bf79d80e7156771cf" + integrity sha512-cXPxGbPVTkEQI8mUiWYUwB6j3ga6M9i7yubUOCrjgZ01GeZPMSnaWRprfJ09uuy81wJjY2gfHgLsOgwrGvUBTw== + acorn-import-assertions@^1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" @@ -429,9 +413,9 @@ emitter-listener@^1.0.1, emitter-listener@^1.1.1: shimmer "^1.2.0" follow-redirects@^1.14.8: - version "1.15.1" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" - integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== + version "1.15.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== form-data@^4.0.0: version "4.0.0" @@ -587,9 +571,9 @@ tas-client@0.1.58: axios "^0.26.1" tslib@^2.2.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" - integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== uuid@^8.3.0: version "8.3.2" @@ -603,11 +587,16 @@ vscode-tas-client@^0.1.63: dependencies: tas-client "0.1.58" -vscode-uri@3.0.3, vscode-uri@^3.0.3: +vscode-uri@3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.3.tgz#a95c1ce2e6f41b7549f86279d19f47951e4f4d84" integrity sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA== +vscode-uri@^3.0.3: + version "3.0.7" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.7.tgz#6d19fef387ee6b46c479e5fb00870e15e58c1eb8" + integrity sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"