Skip to content

Commit

Permalink
feat: .npmrc auto complete (#12)
Browse files Browse the repository at this point in the history
Co-authored-by: 余腾靖 <[email protected]>
  • Loading branch information
hyoban and tjx666 authored Jul 14, 2024
1 parent c1b9e81 commit 13a1700
Show file tree
Hide file tree
Showing 11 changed files with 1,262 additions and 16 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@
"prepare": "simple-git-hooks"
},
"dependencies": {
"@npmcli/config": "^8.3.4",
"@pnpm/config": "^21.6.1",
"axios": "^1.7.2",
"detect-package-manager": "^3.0.2",
"escape-string-regexp": "^5.0.0",
Expand Down Expand Up @@ -357,6 +359,7 @@
"@yutengjing/prettier-config": "^1.3.0",
"@yutengjing/release": "^0.3.1",
"all-node-versions": "^13.0.0",
"cheerio": "1.0.0-rc.12",
"esbuild": "^0.23.0",
"esbuild-visualizer": "^0.6.0",
"eslint": "^8.57.0",
Expand Down
762 changes: 762 additions & 0 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions scripts/fetch-npmrc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import fs from 'fs';
import { resolve } from 'path';
import process from 'process';

import * as cheerio from 'cheerio';

const npmNpmrcWebsite = 'https://docs.npmjs.com/cli/v10/using-npm/config';
const pnpmNpmrcWebsite = 'https://pnpm.io/npmrc';

async function fetchNpmrc() {
const options = new Set<string>();

async function handleNpmNpmrc() {
const response = await fetch(npmNpmrcWebsite);
const text = await response.text();
const $ = cheerio.load(text);
// get all links
const links = $('a');
links.each((_, link) => {
const href = $(link).attr('href');
const text = $(link).text();
if (href?.startsWith('#') && !text.includes(' ')) {
options.add(href.slice(1));
}
});
}

async function handlePnpmNpmrc() {
const response = await fetch(pnpmNpmrcWebsite);
const text = await response.text();
const $ = cheerio.load(text);
// get all links like:
// <a class="table-of-contents__link toc-highlight" href="/npmrc#dependency-hoisting-settings">Dependency Hoisting Settings</a>
const links = $('a.table-of-contents__link.toc-highlight');
links.each((_, link) => {
const href = $(link).attr('href');
const text = $(link).text();
if (href?.startsWith('/npmrc#') && !text.includes(' ')) {
options.add(href.slice('/npmrc#'.length));
}
});
}

await Promise.all([handleNpmNpmrc(), handlePnpmNpmrc()]);
fs.writeFileSync(
resolve(process.cwd(), 'src/completion/npmrc/options.ts'),
`export const options = new Set([\n${[...options].map((option) => ` '${option}',`).join('\n')}\n]);`,
);
return options;
}

fetchNpmrc();
129 changes: 129 additions & 0 deletions src/completion/npmrc/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { types } from 'util';

import type { CompletionItemProvider } from 'vscode';
import vscode from 'vscode';

import { options } from './options';
import { registryList } from './registryList';

export class NpmrcCompletionItemProvider implements CompletionItemProvider {
private async getAvailableValues(key: string): Promise<vscode.CompletionList | undefined> {
const [definitions, types] = await Promise.all([
import('@npmcli/config/lib/definitions').then((mod) => mod.definitions),
import('@pnpm/config').then((mod) => mod.types),
]);

if (key === 'registry') {
return {
items: registryList.map((item) => {
return new vscode.CompletionItem(
item.registry,
vscode.CompletionItemKind.Value,
);
}),
};
}

const availableValues = types[key as keyof typeof types] ?? definitions[key]?.type;
if (availableValues) {
const isBoolean =
typeof availableValues === 'function' && availableValues.name === 'Boolean';

if (isBoolean) {
return {
items: [
new vscode.CompletionItem('true', vscode.CompletionItemKind.Value),
new vscode.CompletionItem('false', vscode.CompletionItemKind.Value),
],
};
}

const values = Array.isArray(availableValues) ? availableValues : [availableValues];
const items = values
.filter((value) => typeof value === 'string')
.map((value) => {
const item = new vscode.CompletionItem(value, vscode.CompletionItemKind.Value);
item.insertText = value;
return item;
});
return { items };
}

return;
}

async provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position,
_token: vscode.CancellationToken,
_context: vscode.CompletionContext,
): Promise<vscode.CompletionList | undefined> {
const char = position.character;
const lineBefore = document.lineAt(position).text.slice(0, char);

if (lineBefore.endsWith('=')) {
const key = lineBefore.slice(0, -1);
if (!options.has(key)) {
return;
}

return this.getAvailableValues(key);
}

return {
items: Array.from(options).map(
(key) => new vscode.CompletionItem(key, vscode.CompletionItemKind.Property),
),
};
}

async resolveCompletionItem?(
item: vscode.CompletionItem,
_token: vscode.CancellationToken,
): Promise<vscode.CompletionItem> {
if (item.kind !== vscode.CompletionItemKind.Property) {
return item;
}

const key = item.label as string;
const definitions = await import('@npmcli/config/lib/definitions').then(
(mod) => mod.definitions,
);
const type = types[key as keyof typeof types] ?? definitions[key]?.type;
const availableValueTypes = type
? (Array.isArray(type) ? type : [type])
.map((value) =>
typeof value === 'string'
? value
: typeof value === 'function'
? value.name
: String(value),
)
.filter((value) => value !== '[object Object]')
: [];
const availableValueTypesString =
availableValueTypes.length > 0 ? `\n\nType: ${availableValueTypes.join(' | ')}` : '';
const description = definitions[key]?.description;
if (description) {
item.documentation = new vscode.MarkdownString(
description
.replaceAll('\\`', '`')
.split('\n')
.map((line: string) => line.trim())
.join('\n')
.concat(
`\n\n[npm .npmrc documentation](https://docs.npmjs.com/cli/v10/using-npm/config#${key})`,
)
.concat(availableValueTypesString),
);
} else {
item.documentation = new vscode.MarkdownString(
`[pnpm .npmrc documentation](https://pnpm.io/npmrc#${key})`.concat(
availableValueTypesString,
),
);
}
item.insertText = `${key}=`;
return item;
}
}
Loading

0 comments on commit 13a1700

Please sign in to comment.