Merge branch 'main' into ai-codefixes

This commit is contained in:
Nathan Shively-Sanders
2023-09-07 10:46:52 -07:00
948 changed files with 24325 additions and 18018 deletions

View File

@@ -16,4 +16,13 @@ export class BrowserServiceConfigurationProvider extends BaseServiceConfiguratio
protected readLocalTsdk(_configuration: vscode.WorkspaceConfiguration): string | null {
return null;
}
// On browsers, we don't run TSServer on Node
protected readLocalNodePath(_configuration: vscode.WorkspaceConfiguration): string | null {
return null;
}
protected override readGlobalNodePath(_configuration: vscode.WorkspaceConfiguration): string | null {
return null;
}
}

View File

@@ -6,7 +6,10 @@
import * as os from 'os';
import * as path from 'path';
import * as vscode from 'vscode';
import * as child_process from 'child_process';
import * as fs from 'fs';
import { BaseServiceConfigurationProvider } from './configuration';
import { RelativeWorkspacePathResolver } from '../utils/relativePathResolver';
export class ElectronServiceConfigurationProvider extends BaseServiceConfigurationProvider {
@@ -35,4 +38,65 @@ export class ElectronServiceConfigurationProvider extends BaseServiceConfigurati
}
return null;
}
protected readLocalNodePath(configuration: vscode.WorkspaceConfiguration): string | null {
return this.validatePath(this.readLocalNodePathWorker(configuration));
}
private readLocalNodePathWorker(configuration: vscode.WorkspaceConfiguration): string | null {
const inspect = configuration.inspect('typescript.tsserver.nodePath');
if (inspect?.workspaceValue && typeof inspect.workspaceValue === 'string') {
if (inspect.workspaceValue === 'node') {
return this.findNodePath();
}
const fixedPath = this.fixPathPrefixes(inspect.workspaceValue);
if (!path.isAbsolute(fixedPath)) {
const workspacePath = RelativeWorkspacePathResolver.asAbsoluteWorkspacePath(fixedPath);
return workspacePath || null;
}
return fixedPath;
}
return null;
}
protected readGlobalNodePath(configuration: vscode.WorkspaceConfiguration): string | null {
return this.validatePath(this.readGlobalNodePathWorker(configuration));
}
private readGlobalNodePathWorker(configuration: vscode.WorkspaceConfiguration): string | null {
const inspect = configuration.inspect('typescript.tsserver.nodePath');
if (inspect?.globalValue && typeof inspect.globalValue === 'string') {
if (inspect.globalValue === 'node') {
return this.findNodePath();
}
const fixedPath = this.fixPathPrefixes(inspect.globalValue);
if (path.isAbsolute(fixedPath)) {
return fixedPath;
}
}
return null;
}
private findNodePath(): string | null {
try {
const out = child_process.execFileSync('node', ['-e', 'console.log(process.execPath)'], {
windowsHide: true,
timeout: 2000,
cwd: vscode.workspace.workspaceFolders?.[0].uri.fsPath,
encoding: 'utf-8',
});
return out.trim();
} catch (error) {
vscode.window.showWarningMessage(vscode.l10n.t("Could not detect a Node installation to run TS Server."));
return null;
}
}
private validatePath(nodePath: string | null): string | null {
if (nodePath && (!fs.existsSync(nodePath) || fs.lstatSync(nodePath).isDirectory())) {
vscode.window.showWarningMessage(vscode.l10n.t("The path {0} doesn\'t point to a valid Node installation to run TS Server. Falling back to bundled Node.", nodePath));
return null;
}
return nodePath;
}
}

View File

@@ -112,12 +112,16 @@ export interface TypeScriptServiceConfiguration {
readonly useSyntaxServer: SyntaxServerConfiguration;
readonly webProjectWideIntellisenseEnabled: boolean;
readonly webProjectWideIntellisenseSuppressSemanticErrors: boolean;
readonly webExperimentalTypeAcquisition: boolean;
readonly enableDiagnosticsTelemetry: boolean;
readonly enableProjectDiagnostics: boolean;
readonly maxTsServerMemory: number;
readonly enablePromptUseWorkspaceTsdk: boolean;
readonly watchOptions: Proto.WatchOptions | undefined;
readonly includePackageJsonAutoImports: 'auto' | 'on' | 'off' | undefined;
readonly enableTsServerTracing: boolean;
readonly localNodePath: string | null;
readonly globalNodePath: string | null;
}
export function areServiceConfigurationsEqual(a: TypeScriptServiceConfiguration, b: TypeScriptServiceConfiguration): boolean {
@@ -144,17 +148,23 @@ 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),
enablePromptUseWorkspaceTsdk: this.readEnablePromptUseWorkspaceTsdk(configuration),
watchOptions: this.readWatchOptions(configuration),
includePackageJsonAutoImports: this.readIncludePackageJsonAutoImports(configuration),
enableTsServerTracing: this.readEnableTsServerTracing(configuration),
localNodePath: this.readLocalNodePath(configuration),
globalNodePath: this.readGlobalNodePath(configuration),
};
}
protected abstract readGlobalTsdk(configuration: vscode.WorkspaceConfiguration): string | null;
protected abstract readLocalTsdk(configuration: vscode.WorkspaceConfiguration): string | null;
protected abstract readLocalNodePath(configuration: vscode.WorkspaceConfiguration): string | null;
protected abstract readGlobalNodePath(configuration: vscode.WorkspaceConfiguration): string | null;
protected readTsServerLogLevel(configuration: vscode.WorkspaceConfiguration): TsServerLogLevel {
const setting = configuration.get<string>('typescript.tsserver.log', 'off');
@@ -173,6 +183,10 @@ export abstract class BaseServiceConfigurationProvider implements ServiceConfigu
return configuration.get<boolean>('typescript.disableAutomaticTypeAcquisition', false);
}
protected readWebExperimentalTypeAcquisition(configuration: vscode.WorkspaceConfiguration): boolean {
return configuration.get<boolean>('typescript.experimental.tsserver.web.typeAcquisition.enabled', false);
}
protected readLocale(configuration: vscode.WorkspaceConfiguration): string | null {
const value = configuration.get<string>('typescript.locale', 'auto');
return !value || value === 'auto' ? null : value;
@@ -197,6 +211,11 @@ export abstract class BaseServiceConfigurationProvider implements ServiceConfigu
return SyntaxServerConfiguration.Never;
}
protected readEnableDiagnosticsTelemetry(configuration: vscode.WorkspaceConfiguration): boolean {
// This setting does not appear in the settings view, as it is not to be enabled by users outside the team
return configuration.get<boolean>('typescript.enableDiagnosticsTelemetry', false);
}
protected readEnableProjectDiagnostics(configuration: vscode.WorkspaceConfiguration): boolean {
return configuration.get<boolean>('typescript.tsserver.experimental.enableProjectDiagnostics', false);
}

View File

@@ -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<Api> {
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);
}

View File

@@ -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<string, Set<string>>();
private readonly watcher: vscode.FileSystemWatcher;
private readonly _emitter = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
readonly onDidChangeFile: vscode.Event<vscode.FileChangeEvent[]> = 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<vscode.FileStat> {
// 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<Uint8Array> {
// 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<void> {
// 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);
}
}

View File

@@ -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<vscode.FileChangeEvent[]>();
readonly onDidChangeFile: vscode.Event<vscode.FileChangeEvent[]> = this._emitter.event;
private readonly watchers = new Map<string, Set<Symbol>>;
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<string, FsEntry>,
public ctime: number,
public mtime: number,
) { }
get data() {
if (this.type === vscode.FileType.Directory) {
throw vscode.FileSystemError.FileIsADirectory;
}
return <Uint8Array>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 <Map<string, FsEntry>>this._data;
}
}

View File

@@ -8,6 +8,9 @@ import { DiagnosticLanguage } from '../configuration/languageDescription';
import * as arrays from '../utils/arrays';
import { Disposable } from '../utils/dispose';
import { ResourceMap } from '../utils/resourceMap';
import { TelemetryReporter } from '../logging/telemetry';
import { TypeScriptServiceConfiguration } from '../configuration/configuration';
import { equals } from '../utils/objects';
function diagnosticsEquals(a: vscode.Diagnostic, b: vscode.Diagnostic): boolean {
if (a === b) {
@@ -148,6 +151,82 @@ class DiagnosticSettings {
}
}
class DiagnosticsTelemetryManager extends Disposable {
private readonly _diagnosticCodesMap = new Map<number, number>();
private readonly _diagnosticSnapshotsMap = new ResourceMap<readonly vscode.Diagnostic[]>(uri => uri.toString(), { onCaseInsensitiveFileSystem: false });
private _timeout: NodeJS.Timeout | undefined;
private _telemetryEmitter: NodeJS.Timer | undefined;
constructor(
private readonly _telemetryReporter: TelemetryReporter,
private readonly _diagnosticsCollection: vscode.DiagnosticCollection,
) {
super();
this._register(vscode.workspace.onDidChangeTextDocument(e => {
if (e.document.languageId === 'typescript' || e.document.languageId === 'typescriptreact') {
this._updateAllDiagnosticCodesAfterTimeout();
}
}));
this._updateAllDiagnosticCodesAfterTimeout();
this._registerTelemetryEventEmitter();
}
private _updateAllDiagnosticCodesAfterTimeout() {
clearTimeout(this._timeout);
this._timeout = setTimeout(() => this._updateDiagnosticCodes(), 5000);
}
private _increaseDiagnosticCodeCount(code: string | number | undefined) {
if (code === undefined) {
return;
}
this._diagnosticCodesMap.set(Number(code), (this._diagnosticCodesMap.get(Number(code)) || 0) + 1);
}
private _updateDiagnosticCodes() {
this._diagnosticsCollection.forEach((uri, diagnostics) => {
const previousDiagnostics = this._diagnosticSnapshotsMap.get(uri);
this._diagnosticSnapshotsMap.set(uri, diagnostics);
const diagnosticsDiff = diagnostics.filter((diagnostic) => !previousDiagnostics?.some((previousDiagnostic) => equals(diagnostic, previousDiagnostic)));
diagnosticsDiff.forEach((diagnostic) => {
const code = diagnostic.code;
this._increaseDiagnosticCodeCount(typeof code === 'string' || typeof code === 'number' ? code : code?.value);
});
});
}
private _registerTelemetryEventEmitter() {
this._telemetryEmitter = setInterval(() => {
if (this._diagnosticCodesMap.size > 0) {
let diagnosticCodes = '';
this._diagnosticCodesMap.forEach((value, key) => {
diagnosticCodes += `${key}:${value},`;
});
this._diagnosticCodesMap.clear();
/* __GDPR__
"typescript.diagnostics" : {
"owner": "@aiday-mar",
"diagnosticCodes" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
"${include}": [
"${TypeScriptCommonProperties}"
]
}
*/
this._telemetryReporter.logTelemetry('typescript.diagnostics', {
diagnoticCodes: diagnosticCodes
});
}
}, 5 * 60 * 1000); // 5 minutes
}
override dispose() {
super.dispose();
clearTimeout(this._timeout);
clearInterval(this._telemetryEmitter);
}
}
export class DiagnosticsManager extends Disposable {
private readonly _diagnostics: ResourceMap<FileDiagnostics>;
private readonly _settings = new DiagnosticSettings();
@@ -158,6 +237,8 @@ export class DiagnosticsManager extends Disposable {
constructor(
owner: string,
configuration: TypeScriptServiceConfiguration,
telemetryReporter: TelemetryReporter,
onCaseInsensitiveFileSystem: boolean
) {
super();
@@ -165,6 +246,10 @@ export class DiagnosticsManager extends Disposable {
this._pendingUpdates = new ResourceMap<any>(undefined, { onCaseInsensitiveFileSystem });
this._currentDiagnostics = this._register(vscode.languages.createDiagnosticCollection(owner));
// Here we are selecting only 1 user out of 1000 to send telemetry diagnostics
if (Math.random() * 1000 <= 1 || configuration.enableDiagnosticsTelemetry) {
this._register(new DiagnosticsTelemetryManager(telemetryReporter, this._currentDiagnostics));
}
}
public override dispose() {

View File

@@ -6,6 +6,7 @@
import * as vscode from 'vscode';
import { DocumentSelector } from '../configuration/documentSelector';
import { LanguageDescription } from '../configuration/languageDescription';
import { TelemetryReporter } from '../logging/telemetry';
import { API } from '../tsServer/api';
import type * as Proto from '../tsServer/protocol/protocol';
import { Location, Position } from '../typeConverters';
@@ -29,13 +30,16 @@ class TypeScriptInlayHintsProvider extends Disposable implements vscode.InlayHin
public static readonly minVersion = API.v440;
private readonly _onDidChangeInlayHints = new vscode.EventEmitter<void>();
private readonly _onDidChangeInlayHints = this._register(new vscode.EventEmitter<void>());
public readonly onDidChangeInlayHints = this._onDidChangeInlayHints.event;
private hasReportedTelemetry = false;
constructor(
private readonly language: LanguageDescription,
private readonly client: ITypeScriptServiceClient,
private readonly fileConfigurationManager: FileConfigurationManager
private readonly fileConfigurationManager: FileConfigurationManager,
private readonly telemetryReporter: TelemetryReporter,
) {
super();
@@ -54,31 +58,47 @@ class TypeScriptInlayHintsProvider extends Disposable implements vscode.InlayHin
}));
}
async provideInlayHints(model: vscode.TextDocument, range: vscode.Range, token: vscode.CancellationToken): Promise<vscode.InlayHint[]> {
async provideInlayHints(model: vscode.TextDocument, range: vscode.Range, token: vscode.CancellationToken): Promise<vscode.InlayHint[] | undefined> {
const filepath = this.client.toOpenTsFilePath(model);
if (!filepath) {
return [];
return;
}
if (!areInlayHintsEnabledForFile(this.language, model)) {
return [];
return;
}
const start = model.offsetAt(range.start);
const length = model.offsetAt(range.end) - start;
await this.fileConfigurationManager.ensureConfigurationForDocument(model, token);
if (token.isCancellationRequested) {
return;
}
if (!this.hasReportedTelemetry) {
this.hasReportedTelemetry = true;
/* __GDPR__
"inlayHints.provide" : {
"owner": "mjbvz",
"${include}": [
"${TypeScriptCommonProperties}"
]
}
*/
this.telemetryReporter.logTelemetry('inlayHints.provide', {});
}
const response = await this.client.execute('provideInlayHints', { file: filepath, start, length }, token);
if (response.type !== 'response' || !response.success || !response.body) {
return [];
return;
}
return response.body.map(hint => {
const result = new vscode.InlayHint(
Position.fromLocation(hint.position),
this.convertInlayHintText(model.uri, hint),
hint.kind && fromProtocolInlayHintKind(hint.kind)
this.convertInlayHintText(hint),
fromProtocolInlayHintKind(hint.kind)
);
result.paddingLeft = hint.whitespaceBefore;
result.paddingRight = hint.whitespaceAfter;
@@ -86,19 +106,18 @@ class TypeScriptInlayHintsProvider extends Disposable implements vscode.InlayHin
});
}
private convertInlayHintText(resource: vscode.Uri, tsHint: Proto.InlayHintItem): string | vscode.InlayHintLabelPart[] {
private convertInlayHintText(tsHint: Proto.InlayHintItem): string | vscode.InlayHintLabelPart[] {
if (tsHint.displayParts) {
return tsHint.displayParts.map((part): vscode.InlayHintLabelPart => {
const out = new vscode.InlayHintLabelPart(part.text);
if (part.span) {
out.location = Location.fromTextSpan(resource, part.span);
out.location = Location.fromTextSpan(this.client.toResource(part.span.file), part.span);
}
return out;
});
}
return tsHint.text;
}
}
@@ -128,13 +147,14 @@ export function register(
selector: DocumentSelector,
language: LanguageDescription,
client: ITypeScriptServiceClient,
fileConfigurationManager: FileConfigurationManager
fileConfigurationManager: FileConfigurationManager,
telemetryReporter: TelemetryReporter,
) {
return conditionalRegistration([
requireMinVersion(client, TypeScriptInlayHintsProvider.minVersion),
requireSomeCapability(client, ClientCapability.Semantic),
], () => {
const provider = new TypeScriptInlayHintsProvider(language, client, fileConfigurationManager);
const provider = new TypeScriptInlayHintsProvider(language, client, fileConfigurationManager, telemetryReporter);
return vscode.languages.registerInlayHintsProvider(selector.semantic, provider);
});
}

View File

@@ -74,7 +74,7 @@ export default class LanguageProvider extends Disposable {
import('./languageFeatures/formatting').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager))),
import('./languageFeatures/hover').then(provider => this._register(provider.register(selector, this.client, this.fileConfigurationManager))),
import('./languageFeatures/implementations').then(provider => this._register(provider.register(selector, this.client))),
import('./languageFeatures/inlayHints').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager))),
import('./languageFeatures/inlayHints').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager, this.telemetryReporter))),
import('./languageFeatures/jsDocCompletions').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager))),
import('./languageFeatures/linkedEditing').then(provider => this._register(provider.register(selector, this.client))),
import('./languageFeatures/organizeImports').then(provider => this._register(provider.register(selector, this.client, this.commandManager, this.fileConfigurationManager, this.telemetryReporter))),

View File

@@ -0,0 +1,149 @@
/*---------------------------------------------------------------------------------------------
* 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 { TypeScriptServiceConfiguration } from '../configuration/configuration';
import { setImmediate } from '../utils/async';
import { Disposable } from '../utils/dispose';
const useWorkspaceNodeStorageKey = 'typescript.useWorkspaceNode';
const lastKnownWorkspaceNodeStorageKey = 'typescript.lastKnownWorkspaceNode';
type UseWorkspaceNodeState = undefined | boolean;
type LastKnownWorkspaceNodeState = undefined | string;
export class NodeVersionManager extends Disposable {
private _currentVersion: string | undefined;
public constructor(
private configuration: TypeScriptServiceConfiguration,
private readonly workspaceState: vscode.Memento
) {
super();
this._currentVersion = this.configuration.globalNodePath || undefined;
if (vscode.workspace.isTrusted) {
const workspaceVersion = this.configuration.localNodePath;
if (workspaceVersion) {
const useWorkspaceNode = this.canUseWorkspaceNode(workspaceVersion);
if (useWorkspaceNode === undefined) {
setImmediate(() => {
this.promptAndSetWorkspaceNode();
});
}
else if (useWorkspaceNode) {
this._currentVersion = workspaceVersion;
}
}
}
else {
this._disposables.push(vscode.workspace.onDidGrantWorkspaceTrust(() => {
const workspaceVersion = this.configuration.localNodePath;
if (workspaceVersion) {
const useWorkspaceNode = this.canUseWorkspaceNode(workspaceVersion);
if (useWorkspaceNode === undefined) {
setImmediate(() => {
this.promptAndSetWorkspaceNode();
});
}
else if (useWorkspaceNode) {
this.updateActiveVersion(workspaceVersion);
}
}
}));
}
}
private readonly _onDidPickNewVersion = this._register(new vscode.EventEmitter<void>());
public readonly onDidPickNewVersion = this._onDidPickNewVersion.event;
public get currentVersion(): string | undefined {
return this._currentVersion;
}
public async updateConfiguration(nextConfiguration: TypeScriptServiceConfiguration) {
const oldConfiguration = this.configuration;
this.configuration = nextConfiguration;
if (oldConfiguration.globalNodePath !== nextConfiguration.globalNodePath
|| oldConfiguration.localNodePath !== nextConfiguration.localNodePath) {
await this.computeNewVersion();
}
}
private async computeNewVersion() {
let version = this.configuration.globalNodePath || undefined;
const workspaceVersion = this.configuration.localNodePath;
if (vscode.workspace.isTrusted && workspaceVersion) {
const useWorkspaceNode = this.canUseWorkspaceNode(workspaceVersion);
if (useWorkspaceNode === undefined) {
version = await this.promptUseWorkspaceNode() || version;
}
else if (useWorkspaceNode) {
version = workspaceVersion;
}
}
this.updateActiveVersion(version);
}
private async promptUseWorkspaceNode(): Promise<string | undefined> {
const workspaceVersion = this.configuration.localNodePath;
if (workspaceVersion === null) {
throw new Error('Could not prompt to use workspace Node installation because no workspace Node installation is specified');
}
const allow = vscode.l10n.t("Yes");
const disallow = vscode.l10n.t("No");
const dismiss = vscode.l10n.t("Not now");
const result = await vscode.window.showInformationMessage(vscode.l10n.t("This workspace wants to use the Node installation at '{0}' to run TS Server. Would you like to use it?", workspaceVersion),
allow,
disallow,
dismiss,
);
let version = undefined;
switch (result) {
case allow:
await this.setUseWorkspaceNodeState(true, workspaceVersion);
version = workspaceVersion;
break;
case disallow:
await this.setUseWorkspaceNodeState(false, workspaceVersion);
break;
case dismiss:
await this.setUseWorkspaceNodeState(undefined, workspaceVersion);
break;
}
return version;
}
private async promptAndSetWorkspaceNode(): Promise<void> {
const version = await this.promptUseWorkspaceNode();
if (version !== undefined) {
this.updateActiveVersion(version);
}
}
private updateActiveVersion(pickedVersion: string | undefined): void {
const oldVersion = this.currentVersion;
this._currentVersion = pickedVersion;
if (oldVersion !== pickedVersion) {
this._onDidPickNewVersion.fire();
}
}
private canUseWorkspaceNode(nodeVersion: string): boolean | undefined {
const lastKnownWorkspaceNode = this.workspaceState.get<LastKnownWorkspaceNodeState>(lastKnownWorkspaceNodeStorageKey);
if (lastKnownWorkspaceNode === nodeVersion) {
return this.workspaceState.get<UseWorkspaceNodeState>(useWorkspaceNodeStorageKey);
}
return undefined;
}
private async setUseWorkspaceNodeState(allow: boolean | undefined, nodeVersion: string) {
await this.workspaceState.update(lastKnownWorkspaceNodeStorageKey, nodeVersion);
await this.workspaceState.update(useWorkspaceNodeStorageKey, allow);
}
}

View File

@@ -19,6 +19,7 @@ import type * as Proto from './protocol/protocol';
import { EventName } from './protocol/protocol.const';
import { TypeScriptVersionManager } from './versionManager';
import { TypeScriptVersion } from './versionProvider';
import { NodeVersionManager } from './nodeManager';
export enum ExecutionTarget {
Semantic,
@@ -70,6 +71,7 @@ export interface TsServerProcessFactory {
kind: TsServerProcessKind,
configuration: TypeScriptServiceConfiguration,
versionManager: TypeScriptVersionManager,
nodeVersionManager: NodeVersionManager,
tsServerLog: TsServerLog | undefined,
): TsServerProcess;
}

View File

@@ -13,6 +13,7 @@ import type * as Proto from './protocol/protocol';
import { TsServerLog, TsServerProcess, TsServerProcessFactory, TsServerProcessKind } from './server';
import { TypeScriptVersionManager } from './versionManager';
import { TypeScriptVersion } from './versionProvider';
import { NodeVersionManager } from './nodeManager';
type BrowserWatchEvent = {
type: 'watchDirectory' | 'watchFile';
@@ -40,16 +41,19 @@ export class WorkerServerProcessFactory implements TsServerProcessFactory {
kind: TsServerProcessKind,
_configuration: TypeScriptServiceConfiguration,
_versionManager: TypeScriptVersionManager,
_nodeVersionManager: NodeVersionManager,
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);
}
}

View File

@@ -15,6 +15,7 @@ import type * as Proto from './protocol/protocol';
import { TsServerLog, TsServerProcess, TsServerProcessFactory, TsServerProcessKind } from './server';
import { TypeScriptVersionManager } from './versionManager';
import { TypeScriptVersion } from './versionProvider';
import { NodeVersionManager } from './nodeManager';
const defaultSize: number = 8192;
@@ -134,10 +135,12 @@ class Reader<T> extends Disposable {
}
}
function generatePatchedEnv(env: any, modulePath: string): any {
function generatePatchedEnv(env: any, modulePath: string, hasExecPath: boolean): any {
const newEnv = Object.assign({}, env);
newEnv['ELECTRON_RUN_AS_NODE'] = '1';
if (!hasExecPath) {
newEnv['ELECTRON_RUN_AS_NODE'] = '1';
}
newEnv['NODE_PATH'] = path.join(modulePath, '..', '..', '..');
// Ensure we always have a PATH set
@@ -253,6 +256,7 @@ export class ElectronServiceProcessFactory implements TsServerProcessFactory {
kind: TsServerProcessKind,
configuration: TypeScriptServiceConfiguration,
versionManager: TypeScriptVersionManager,
nodeVersionManager: NodeVersionManager,
_tsserverLog: TsServerLog | undefined,
): TsServerProcess {
let tsServerPath = version.tsServerPath;
@@ -263,20 +267,30 @@ export class ElectronServiceProcessFactory implements TsServerProcessFactory {
tsServerPath = versionManager.currentVersion.tsServerPath;
}
const useIpc = version.apiVersion?.gte(API.v460);
const execPath = nodeVersionManager.currentVersion;
const env = generatePatchedEnv(process.env, tsServerPath, !!execPath);
const runtimeArgs = [...args];
const execArgv = getExecArgv(kind, configuration);
const useIpc = !execPath && version.apiVersion?.gte(API.v460);
if (useIpc) {
runtimeArgs.push('--useNodeIpc');
}
const childProcess = child_process.fork(tsServerPath, runtimeArgs, {
silent: true,
cwd: undefined,
env: generatePatchedEnv(process.env, tsServerPath),
execArgv: getExecArgv(kind, configuration),
stdio: useIpc ? ['pipe', 'pipe', 'pipe', 'ipc'] : undefined,
});
const childProcess = execPath ?
child_process.spawn(execPath, [...execArgv, tsServerPath, ...runtimeArgs], {
shell: true,
windowsHide: true,
cwd: undefined,
env,
}) :
child_process.fork(tsServerPath, runtimeArgs, {
silent: true,
cwd: undefined,
env,
execArgv,
stdio: useIpc ? ['pipe', 'pipe', 'pipe', 'ipc'] : undefined,
});
return useIpc ? new IpcChildServerProcess(childProcess) : new StdioChildServerProcess(childProcess);
}

View File

@@ -19,6 +19,7 @@ import { PluginManager } from './plugins';
import { GetErrRoutingTsServer, ITypeScriptServer, SingleTsServer, SyntaxRoutingTsServer, TsServerDelegate, TsServerLog, TsServerProcessFactory, TsServerProcessKind } from './server';
import { TypeScriptVersionManager } from './versionManager';
import { ITypeScriptVersionProvider, TypeScriptVersion } from './versionProvider';
import { NodeVersionManager } from './nodeManager';
const enum CompositeServerType {
/** Run a single server that handles all commands */
@@ -44,6 +45,7 @@ export class TypeScriptServerSpawner {
public constructor(
private readonly _versionProvider: ITypeScriptVersionProvider,
private readonly _versionManager: TypeScriptVersionManager,
private readonly _nodeVersionManager: NodeVersionManager,
private readonly _logDirectoryProvider: ILogDirectoryProvider,
private readonly _pluginPathsProvider: TypeScriptPluginPathsProvider,
private readonly _logger: Logger,
@@ -160,7 +162,7 @@ export class TypeScriptServerSpawner {
}
this._logger.info(`<${kind}> Forking...`);
const process = this._factory.fork(version, args, kind, configuration, this._versionManager, tsServerLog);
const process = this._factory.fork(version, args, kind, configuration, this._versionManager, this._nodeVersionManager, tsServerLog);
this._logger.info(`<${kind}> Starting...`);
return new SingleTsServer(

View File

@@ -30,6 +30,7 @@ import { TelemetryProperties, TelemetryReporter, VSCodeTelemetryReporter } from
import Tracer from './logging/tracer';
import { ProjectType, inferredProjectCompilerOptions } from './tsconfig';
import { Schemes } from './configuration/schemes';
import { NodeVersionManager } from './tsServer/nodeManager';
export interface TsDiagnostics {
@@ -103,6 +104,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
private _configuration: TypeScriptServiceConfiguration;
private readonly pluginPathsProvider: TypeScriptPluginPathsProvider;
private readonly _versionManager: TypeScriptVersionManager;
private readonly _nodeVersionManager: NodeVersionManager;
private readonly logger: Logger;
private readonly tracer: Tracer;
@@ -173,10 +175,14 @@ export default class TypeScriptServiceClient extends Disposable implements IType
this.restartTsServer();
}));
this._nodeVersionManager = this._register(new NodeVersionManager(this._configuration, context.workspaceState));
this._register(this._nodeVersionManager.onDidPickNewVersion(() => {
this.restartTsServer();
}));
this.bufferSyncSupport = new BufferSyncSupport(this, allModeIds, onCaseInsenitiveFileSystem);
this.onReady(() => { this.bufferSyncSupport.listen(); });
this.diagnosticsManager = new DiagnosticsManager('typescript', onCaseInsenitiveFileSystem);
this.bufferSyncSupport.onDelete(resource => {
this.cancelInflightRequestsForResource(resource);
this.diagnosticsManager.deleteAllDiagnosticsInFile(resource);
@@ -193,6 +199,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
this.versionProvider.updateConfiguration(this._configuration);
this._versionManager.updateConfiguration(this._configuration);
this.pluginPathsProvider.updateConfiguration(this._configuration);
this._nodeVersionManager.updateConfiguration(this._configuration);
if (this.serverState.type === ServerState.Type.Running) {
if (!this._configuration.implicitProjectConfiguration.isEqualTo(oldConfiguration.implicitProjectConfiguration)) {
@@ -214,7 +221,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType
return this.apiVersion.fullVersionString;
});
this.typescriptServerSpawner = new TypeScriptServerSpawner(this.versionProvider, this._versionManager, this.logDirectoryProvider, this.pluginPathsProvider, this.logger, this.telemetryReporter, this.tracer, this.processFactory);
this.diagnosticsManager = new DiagnosticsManager('typescript', this._configuration, this.telemetryReporter, onCaseInsenitiveFileSystem);
this.typescriptServerSpawner = new TypeScriptServerSpawner(this.versionProvider, this._versionManager, this._nodeVersionManager, this.logDirectoryProvider, this.pluginPathsProvider, this.logger, this.telemetryReporter, this.tracer, this.processFactory);
this._register(this.pluginManager.onDidUpdateConfig(update => {
this.configurePlugin(update.pluginId, update.config);
@@ -388,6 +396,10 @@ export default class TypeScriptServiceClient extends Disposable implements IType
}
this.info(`Using tsserver from: ${version.path}`);
const nodePath = this._nodeVersionManager.currentVersion;
if (nodePath) {
this.info(`Using Node installation from ${nodePath} to run TS Server`);
}
const apiVersion = version.apiVersion || API.defaultVersion;
const mytoken = ++this.token;