first cut of import helper extension (#227203)

This commit is contained in:
Johannes Rieken
2024-08-30 14:37:52 +02:00
committed by GitHub
parent 7e5c07f327
commit 97ef3ff276
7 changed files with 304 additions and 0 deletions
@@ -0,0 +1,16 @@
{
"configurations": [
{
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--enable-proposed-api=ms-vscode.vscode-selfhost-import-aid"
],
"name": "Launch Extension",
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"request": "launch",
"type": "extensionHost"
}
]
}
@@ -0,0 +1,7 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "vscode.typescript-language-features",
"editor.codeActionsOnSave": {
"source.organizeImports": "always"
}
}
@@ -0,0 +1,29 @@
{
"name": "vscode-selfhost-import-aid",
"displayName": "VS Code Selfhost Import Aid",
"description": "Util to improve dealing with imports",
"engines": {
"vscode": "^1.88.0"
},
"version": "0.0.1",
"publisher": "ms-vscode",
"categories": [
"Other"
],
"activationEvents": [
"onLanguage:typescript"
],
"main": "./out/extension.js",
"repository": {
"type": "git",
"url": "https://github.com/microsoft/vscode.git"
},
"license": "MIT",
"scripts": {
"compile": "gulp compile-extension:vscode-selfhost-import-aid",
"watch": "gulp watch-extension:vscode-selfhost-import-aid"
},
"dependencies": {
"typescript": "5.5.4"
}
}
@@ -0,0 +1,177 @@
/*---------------------------------------------------------------------------------------------
* 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 * as ts from 'typescript';
import * as path from 'path';
export async function activate(context: vscode.ExtensionContext) {
const fileIndex = new class {
private _currentRun?: Thenable<void>;
private _disposables: vscode.Disposable[] = [];
private readonly _index = new Map<string, vscode.Uri>();
constructor() {
const watcher = vscode.workspace.createFileSystemWatcher('**/*.ts', false, true, false);
this._disposables.push(watcher.onDidChange(e => { this._index.set(e.toString(), e); }));
this._disposables.push(watcher.onDidDelete(e => { this._index.delete(e.toString()); }));
this._disposables.push(watcher);
this._refresh(false);
}
dispose(): void {
for (const disposable of this._disposables) {
disposable.dispose();
}
this._disposables = [];
this._index.clear();
}
async all() {
await this._currentRun;
return this._index.values();
}
private _refresh(clear: boolean) {
// TODO@jrieken LATEST API! findFiles2New
this._currentRun = vscode.workspace.findFiles('src/vs/**/*.ts', '{**/node_modules/**,**/extensions/**}').then(all => {
if (clear) {
this._index.clear();
}
for (const item of all) {
this._index.set(item.toString(), item);
}
});
}
};
const selector: vscode.DocumentSelector = 'typescript';
function findNodeAtPosition(document: vscode.TextDocument, node: ts.Node, position: vscode.Position): ts.Node | undefined {
if (node.getStart() <= document.offsetAt(position) && node.getEnd() >= document.offsetAt(position)) {
return ts.forEachChild(node, child => findNodeAtPosition(document, child, position)) || node;
}
return undefined;
}
function findImportAt(document: vscode.TextDocument, position: vscode.Position): ts.ImportDeclaration | undefined {
const sourceFile = ts.createSourceFile(document.fileName, document.getText(), ts.ScriptTarget.Latest, true);
const node = findNodeAtPosition(document, sourceFile, position);
if (node && ts.isStringLiteral(node) && ts.isImportDeclaration(node.parent)) {
return node.parent;
}
return undefined;
}
const completionProvider = new class implements vscode.CompletionItemProvider {
async provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.CompletionList | undefined> {
const index = document.getText().lastIndexOf(' from \'');
if (index < 0 || document.positionAt(index).line < position.line) {
// line after last import is before position
// -> no completion, safe a parse call
return undefined;
}
const node = findImportAt(document, position);
if (!node) {
return undefined;
}
const range = new vscode.Range(document.positionAt(node.moduleSpecifier.pos), document.positionAt(node.moduleSpecifier.end));
const uris = await Promise.race([fileIndex.all(), new Promise<void>(resolve => token.onCancellationRequested(resolve))]);
if (!uris) {
console.log('NO uris');
return undefined;
}
const result = new vscode.CompletionList();
result.isIncomplete = true;
for (const item of uris) {
if (!item.path.endsWith('.ts')) {
continue;
}
let relativePath = path.relative(path.dirname(document.uri.path), item.path);
relativePath = relativePath.startsWith('.') ? relativePath : `./${relativePath}`;
const label = path.basename(item.path, path.extname(item.path));
const insertText = ` '${relativePath.replace(/\.ts$/, '.js')}'`;
const filterText = ` '${label}'`;
const completion = new vscode.CompletionItem({
label: label,
description: vscode.workspace.asRelativePath(item),
});
completion.kind = vscode.CompletionItemKind.File;
completion.insertText = insertText;
completion.filterText = filterText;
completion.range = range;
result.items.push(completion);
}
return result;
}
};
class ImportCodeActions implements vscode.CodeActionProvider {
static kind = vscode.CodeActionKind.QuickFix.append('import');
async provideCodeActions(document: vscode.TextDocument, range: vscode.Range | vscode.Selection, context: vscode.CodeActionContext, token: vscode.CancellationToken): Promise<vscode.CodeAction[] | undefined> {
const diag = context.diagnostics.find(d => d.code === 2307);
if (!diag) {
return undefined;
}
const node = findImportAt(document, range.start)?.moduleSpecifier;
if (!node || !ts.isStringLiteral(node)) {
return undefined;
}
const nodeRange = new vscode.Range(document.positionAt(node.pos), document.positionAt(node.end));
const name = path.basename(node.text, path.extname(node.text));
const uris = await Promise.race([fileIndex.all(), new Promise<void>(resolve => token.onCancellationRequested(resolve))]);
if (!uris) {
console.log('NO uris');
return [];
}
const result: vscode.CodeAction[] = [];
for (const item of uris) {
if (path.basename(item.path, path.extname(item.path)) === name) {
let relativePath = path.relative(path.dirname(document.uri.path), item.path).replace(/\.ts$/, '.js');
relativePath = relativePath.startsWith('.') ? relativePath : `./${relativePath}`;
const action = new vscode.CodeAction(`Fix to '${relativePath}'`, ImportCodeActions.kind);
action.edit = new vscode.WorkspaceEdit();
action.edit.replace(document.uri, nodeRange, ` '${relativePath}'`);
action.diagnostics = [diag];
result.push(action);
}
}
return result;
}
}
context.subscriptions.push(fileIndex);
context.subscriptions.push(vscode.languages.registerCompletionItemProvider(selector, completionProvider));
context.subscriptions.push(vscode.languages.registerCodeActionsProvider(selector, new ImportCodeActions(), { providedCodeActionKinds: [ImportCodeActions.kind] }));
}
@@ -0,0 +1,14 @@
{
"extends": "../../../extensions/tsconfig.base.json",
"compilerOptions": {
"outDir": "./out",
"types": [
"node",
"mocha",
]
},
"include": [
"src/**/*",
"../../../src/vscode-dts/vscode.d.ts"
]
}
+60
View File
@@ -0,0 +1,60 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@jridgewell/resolve-uri@^3.1.0":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
"@jridgewell/sourcemap-codec@^1.4.14":
version "1.4.15"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
"@jridgewell/trace-mapping@^0.3.25":
version "0.3.25"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0"
integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==
dependencies:
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@types/istanbul-lib-coverage@^2.0.6":
version "2.0.6"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7"
integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==
"@types/mocha@^10.0.6":
version "10.0.6"
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.6.tgz#818551d39113081048bdddbef96701b4e8bb9d1b"
integrity sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==
"@types/node@20.x":
version "20.12.11"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.11.tgz#c4ef00d3507000d17690643278a60dc55a9dc9be"
integrity sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==
dependencies:
undici-types "~5.26.4"
ansi-styles@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
cockatiel@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/cockatiel/-/cockatiel-3.1.3.tgz#bb1774a498a17e739dd994d56610dc6538b02858"
integrity sha512-xC759TpZ69d7HhfDp8m2WkRwEUiCkxY8Ee2OQH/3H6zmy2D/5Sm+zSTbPRa+V2QyjDtpMvjOIAOVjA2gp6N1kQ==
istanbul-to-vscode@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/istanbul-to-vscode/-/istanbul-to-vscode-2.0.1.tgz#84994d06e604b68cac7301840f338b1e74eb888b"
integrity sha512-V9Hhr7kX3UvkvkaT1lK3AmCRPkaIAIogQBrduTpNiLTkp1eVsybnJhWiDSVeCQap/3aGeZ2019oIivhX9MNsCQ==
dependencies:
"@types/istanbul-lib-coverage" "^2.0.6"
undici-types@~5.26.4:
version "5.26.5"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
+1
View File
@@ -68,6 +68,7 @@ const compilations = [
'extensions/vscode-test-resolver/tsconfig.json',
'.vscode/extensions/vscode-selfhost-test-provider/tsconfig.json',
'.vscode/extensions/vscode-selfhost-import-aid/tsconfig.json',
];
const getBaseUrl = out => `https://main.vscode-cdn.net/sourcemaps/${commit}/${out}`;