Skip to content

Commit

Permalink
Prototype ATA for TS web
Browse files Browse the repository at this point in the history
  • Loading branch information
mjbvz committed Jan 15, 2022
1 parent a3e350e commit b969f85
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ module.exports = withBrowserDefaults({
entry: {
extension: './src/extension.browser.ts',
},
module: {
noParse: [require.resolve('typescript/lib/typescript.js')],

},
plugins: [
...browserPlugins, // add plugins, don't replace inherited

Expand Down
1 change: 1 addition & 0 deletions extensions/typescript-language-features/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"Programming Languages"
],
"dependencies": {
"@typescript/ata": "^0.9.3",
"jsonc-parser": "^2.2.1",
"semver": "5.5.1",
"vscode-extension-telemetry": "0.4.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { noopRequestCancellerFactory } from './tsServer/cancellation';
import { noopLogDirectoryProvider } from './tsServer/logDirectoryProvider';
import { WorkerServerProcess } from './tsServer/serverProcess.browser';
import { ITypeScriptVersionProvider, TypeScriptVersion, TypeScriptVersionSource } from './tsServer/versionProvider';
import { BrowserAtaManager } from './typingsInstaller.browser';
import { ActiveJsTsEditorTracker } from './utils/activeJsTsEditorTracker';
import API from './utils/api';
import { TypeScriptServiceConfiguration } from './utils/configuration';
Expand Down Expand Up @@ -81,6 +82,8 @@ export function activate(
context.subscriptions.push(module.register());
});

context.subscriptions.push(new BrowserAtaManager(lazyClientHost));

context.subscriptions.push(lazilyActivateClient(lazyClientHost, pluginManager, activeJsTsEditorTracker));

return getExtensionApi(onCompletionAccepted.event, pluginManager);
Expand Down
20 changes: 10 additions & 10 deletions extensions/typescript-language-features/src/tsServer/spawner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,23 +191,23 @@ export class TypeScriptServerSpawner {
let tsServerLogFile: string | undefined;
let tsServerTraceDirectory: string | undefined;

if (kind === TsServerProcessKind.Syntax) {
if (apiVersion.gte(API.v401)) {
args.push('--serverMode', 'partialSemantic');
} else {
args.push('--syntaxOnly');
}
}
// if (kind === TsServerProcessKind.Syntax) {
// if (apiVersion.gte(API.v401)) {
// args.push('--serverMode', 'partialSemantic');
// } else {
// args.push('--syntaxOnly');
// }
// }

if (apiVersion.gte(API.v250)) {
args.push('--useInferredProjectPerProjectRoot');
} else {
args.push('--useSingleInferredProject');
}

if (configuration.disableAutomaticTypeAcquisition || kind === TsServerProcessKind.Syntax || kind === TsServerProcessKind.Diagnostics) {
args.push('--disableAutomaticTypingAcquisition');
}
// if (configuration.disableAutomaticTypeAcquisition || kind === TsServerProcessKind.Syntax || kind === TsServerProcessKind.Diagnostics) {
// args.push('--disableAutomaticTypingAcquisition');
// }

if (kind === TsServerProcessKind.Semantic || kind === TsServerProcessKind.Main) {
args.push('--enableTelemetry');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,9 @@ export default class TypeScriptServiceClient extends Disposable implements IType
private readonly _onSurveyReady = this._register(new vscode.EventEmitter<Proto.SurveyReadyEventBody>());
public readonly onSurveyReady = this._onSurveyReady.event;

private readonly _onInstallTypings = this._register(new vscode.EventEmitter<{ unresolvedImports: string[] }>());
public readonly onInstallTypings = this._onInstallTypings.event;

public get apiVersion(): API {
if (this.serverState.type === ServerState.Type.Running) {
return this.serverState.apiVersion;
Expand Down Expand Up @@ -747,23 +750,28 @@ export default class TypeScriptServiceClient extends Disposable implements IType
return undefined;
}

// Try to find the best workspace for a resource
switch (resource.scheme) {
case fileSchemes.file:
case fileSchemes.untitled:
case fileSchemes.vscodeNotebookCell:
case fileSchemes.memFs:
case fileSchemes.vscodeVfs:
case fileSchemes.officeScript:
for (const root of roots.sort((a, b) => a.uri.fsPath.length - b.uri.fsPath.length)) {
if (resource.fsPath.startsWith(root.uri.fsPath + path.sep)) {
return root.uri.fsPath;
}
}
return roots[0].uri.fsPath;
break;
}

default:
return undefined;
const vsCodePreferredWorkspace = vscode.workspace.getWorkspaceFolder(resource);
if (vsCodePreferredWorkspace) {
return vsCodePreferredWorkspace.uri.fsPath;
}

// Otherwise default to the first workspace we find
return roots[0].uri.fsPath;
}

public execute(command: keyof TypeScriptRequests, args: any, token: vscode.CancellationToken, config?: ExecConfig): Promise<ServerResponse.Response<Proto.Response>> {
Expand Down Expand Up @@ -932,6 +940,16 @@ export default class TypeScriptServiceClient extends Disposable implements IType
case EventName.projectLoadingFinish:
this.loadingIndicator.finishedLoadingProject((event as Proto.ProjectLoadingFinishEvent).body.projectName);
break;

case 'installTypings': {
this._onInstallTypings.fire({
unresolvedImports: event.body.unresolvedImports,
});
break;
}
default:
console.log('xxx', event);
break;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as tsAta from '@typescript/ata';
import * as path from 'path';
import * as ts from 'typescript/lib/typescript';
import * as vscode from 'vscode';
import TypeScriptServiceClientHost from './typeScriptServiceClientHost';
import { Disposable } from './utils/dispose';
import { Lazy } from './utils/lazy';

declare const TextEncoder: typeof import('util').TextEncoder;

class File implements vscode.FileStat {

readonly type = vscode.FileType.File;
ctime: number;
mtime: number;
size: number;

readonly name: string;
data?: Uint8Array;

constructor(name: string) {
this.ctime = Date.now();
this.mtime = Date.now();
this.size = 0;
this.name = name;
}
}

class Directory implements vscode.FileStat {

readonly type = vscode.FileType.Directory;
ctime: number;
mtime: number;
size: number;

readonly name: string;
readonly entries: Map<string, File | Directory>;

constructor(name: string) {
this.ctime = Date.now();
this.mtime = Date.now();
this.size = 0;
this.name = name;
this.entries = new Map();
}
}

export type Entry = File | Directory;

export class BrowserAtaManager extends Disposable implements vscode.FileSystemProvider {

private readonly scheme = 'ts-typings';

private readonly root = new Directory('');

private readonly ata: (initialSourceFile: string) => void;

constructor(
lazyClientHost: Lazy<TypeScriptServiceClientHost>,
) {
super();

this._register(vscode.workspace.registerFileSystemProvider(this.scheme, this, { isReadonly: true }));

this._register(lazyClientHost.value.serviceClient.onInstallTypings((e) => this.addTypingsFile(e.unresolvedImports)));

this.ata = tsAta.setupTypeAcquisition({
projectName: 'My ATA Project',
typescript: ts,
logger: console,
delegate: {
receivedFile: (code: string, path: string) => {
const ataResource = vscode.Uri.parse(this.scheme + '://' + path);

const data = new TextEncoder().encode(code);
this.writeFile(ataResource, data, { create: true, overwrite: true });

vscode.workspace.openTextDocument(ataResource);
},
started: () => {
console.log('ATA start');
},
progress: (downloaded: number, total: number) => {
console.log(`Got ${downloaded} out of ${total}`);
},
finished: _vfs => {

},
},
});
}

public addTypingsFile(unresolvedImports: string[]): void {
this.ata?.(unresolvedImports.map(imp => `import '${imp}';`).join('\n'));
}

public readonly _onDidChangeFile = this._register(new vscode.EventEmitter<vscode.FileChangeEvent[]>());
public readonly onDidChangeFile = this._onDidChangeFile.event;

stat(uri: vscode.Uri): vscode.FileStat {
const entry = this.lookup(uri,);
if (!entry) {
throw vscode.FileSystemError.FileNotFound(uri);
}

return entry;
}

readFile(uri: vscode.Uri): Uint8Array {
const file = this.lookupAsFile(uri);
if (!file || !file.data) {
throw vscode.FileSystemError.FileNotFound(uri);
}
return file.data;
}

readDirectory(uri: vscode.Uri): [string, vscode.FileType][] {
const entry = this.lookupAsDirectory(uri);
if (!entry) {
throw vscode.FileSystemError.FileNotFound(uri);
}
const result: [string, vscode.FileType][] = [];
for (const [name, child] of entry.entries) {
result.push([name, child.type]);
}
return result;
}

//#region FS write methods
watch(_uri: vscode.Uri, _options: { recursive: boolean; excludes: string[]; }): vscode.Disposable {
throw new Error('Method not implemented.');
}

createDirectory(uri: vscode.Uri): void | Thenable<void> {
const basename = path.posix.basename(uri.path);
const dirname = uri.with({ path: path.posix.dirname(uri.path) });
const parent = this.lookupAsDirectory(dirname);
if (!parent) {
throw vscode.FileSystemError.FileNotFound(uri);
}

const entry = new Directory(basename);
parent.entries.set(entry.name, entry);
parent.mtime = Date.now();
parent.size += 1;
this._onDidChangeFile.fire([{ type: vscode.FileChangeType.Changed, uri: dirname }, { type: vscode.FileChangeType.Created, uri }]);
}

writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean, overwrite: boolean }): void {
// Ensure parent folders
const basename = path.posix.basename(uri.path);
const dirname = path.posix.dirname(uri.path);

const parts = dirname.split('/');
let parent: Entry = this.root;
for (const part of parts.slice(1)) {
if (parent.type !== vscode.FileType.Directory) {
throw vscode.FileSystemError.FileIsADirectory(uri);
}
let newEntry = parent.entries.get(part);
if (!newEntry) {
newEntry = new Directory(part);
parent.entries.set(newEntry.name, newEntry);
parent.mtime = Date.now();
parent.size += 1;
}
parent = newEntry;
}

if (!parent || parent.type !== vscode.FileType.Directory) {
return;
}

let entry = parent.entries.get(dirname);
if (entry instanceof Directory) {
throw vscode.FileSystemError.FileIsADirectory(uri);
}

if (!entry && !options.create) {
throw vscode.FileSystemError.FileNotFound(uri);
}

if (entry && options.create && !options.overwrite) {
throw vscode.FileSystemError.FileExists(uri);
}

if (!entry) {
entry = new File(basename);
parent.entries.set(basename, entry);
this._onDidChangeFile.fire([{ type: vscode.FileChangeType.Created, uri }]);
}

entry.mtime = Date.now();
entry.size = content.byteLength;
entry.data = content;

this._onDidChangeFile.fire([{ type: vscode.FileChangeType.Changed, uri }]);
}

delete(_uri: vscode.Uri, _options: { recursive: boolean; }): void | Thenable<void> {
throw new Error('Method not implemented.');
}

rename(_oldUri: vscode.Uri, _newUri: vscode.Uri, _options: { overwrite: boolean; }): void | Thenable<void> {
throw new Error('Method not implemented.');
}
//#endregion

private lookupAsDirectory(uri: vscode.Uri): Directory | undefined {
const entry = this.lookup(uri);
if (!entry) {
return undefined;
}
if (entry instanceof Directory) {
return entry;
}
throw vscode.FileSystemError.FileNotADirectory(uri);
}

private lookupAsFile(uri: vscode.Uri): File | undefined {
const entry = this.lookup(uri);
if (!entry) {
return undefined;
}
if (entry instanceof File) {
return entry;
}
throw vscode.FileSystemError.FileIsADirectory(uri);
}

private lookup(uri: vscode.Uri): Entry | undefined {
const parts = uri.path.split('/');
let entry: Entry = this.root;
for (const part of parts) {
if (!part) {
continue;
}

let child: Entry | undefined;
if (entry instanceof Directory) {
child = entry.entries.get(part);
}
if (!child) {
return undefined;
}
entry = child;
}
return entry;
}
}
5 changes: 5 additions & 0 deletions extensions/typescript-language-features/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==

"@typescript/ata@^0.9.3":
version "0.9.3"
resolved "https://registry.yarnpkg.com/@typescript/ata/-/ata-0.9.3.tgz#3ee8f4a43d071a156f7d0a819442db64025cfce5"
integrity sha512-/mETZakA5iOT8j8N8/ZUPLEsvSgMJz+cWt9Le1Q9dEwRXwDzekKXw3tpgrq77gaSxgE/Mn/nvtx8uzS41CmZKQ==

jsonc-parser@^2.2.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.3.1.tgz#59549150b133f2efacca48fe9ce1ec0659af2342"
Expand Down

0 comments on commit b969f85

Please sign in to comment.