Skip to content

Commit

Permalink
feat(lsp-cli): make it better and more docs
Browse files Browse the repository at this point in the history
  • Loading branch information
izaakschroeder committed Mar 20, 2024
1 parent 1c1ce1a commit 4f1aab6
Show file tree
Hide file tree
Showing 11 changed files with 97 additions and 37 deletions.
11 changes: 1 addition & 10 deletions README.md
@@ -1,12 +1,3 @@
# @izaakschroeder/lsp-tools

Tools to work with your LSP.

```sh
lsp \
--connect 'stdio://biome/?arg=lsp-proxy' \
fix \
--ignore '**/node_modules/**' \
--rule 'quickfix.suppressRule.biome.*' \
'./src/**/*{.ts,.tsx,.js,.jsx,.mjs,.cjs}'
```
Tools and libraries to fulfill your LSP needs.
8 changes: 4 additions & 4 deletions packages/lsp-cli/README.md
Expand Up @@ -7,8 +7,8 @@
Use the LSP `textDocument/codeAction` to perform specific actions on files.

```sh
lsp fix --connect 'stdio://biome/?arg=lsp-proxy' \
lsp fix --connect "stdio://$(yarn bin biome)/?arg=lsp-proxy" \
--ignore '**/node_modules/**' \
--action-kind 'quickfix.suppressRule.biome.*' \
'./src/**/*{.ts,.tsx,.js,.jsx,.mjs,.cjs}'
```
--action-kind 'quickfix.suppressRule.biome.**' \
'**/*{.ts,.tsx,.js,.jsx,.mjs,.cjs}'
```
21 changes: 10 additions & 11 deletions packages/lsp-cli/package.json
Expand Up @@ -2,33 +2,32 @@
"name": "@izaakschroeder/lsp-cli",
"version": "0.0.1",
"type": "module",
"main": "./src/index.ts",
"bin": {
"lsp": "./dist/cli.js"
},
"files": ["./dist/**", "README.md", "package.json"],
"scripts": {
"build": "rollup -c",
"test": "biome check ./"
},
"dependencies": {
"@izaakschroeder/lsp-client": "workspace:^",
"chalk": "5.3.0",
"cli-progress": "3.12.0",
"clipanion": "4.0.0-rc.3",
"ku-progress-bar": "0.6.0",
"picomatch": "4.0.1",
"throat": "6.0.2",
"typanion": "3.14.0"
},
"devDependencies": {
"@biomejs/biome": "1.6.1",
"@izaakschroeder/lsp-client": "workspace:^",
"@rollup/plugin-commonjs": "25.0.7",
"@rollup/plugin-node-resolve": "15.2.3",
"@types/node": "20.11.29",
"@types/picomatch": "2.3.3",
"chalk": "5.3.0",
"cli-progress": "3.12.0",
"clipanion": "4.0.0-rc.3",
"esbuild": "0.20.2",
"fast-deep-equal": "3.1.3",
"ku-progress-bar": "0.6.0",
"picomatch": "4.0.1",
"rollup": "4.13.0",
"rollup-plugin-esbuild": "6.1.1",
"throat": "6.0.2",
"typanion": "3.14.0",
"typescript": "5.4.2"
}
}
4 changes: 3 additions & 1 deletion packages/lsp-cli/rollup.config.js
Expand Up @@ -10,7 +10,9 @@ export default [
exportConditions: ['node'],
}),
commonjs(),
esbuild(),
esbuild({
target: 'es2022',
}),
],
output: [
{
Expand Down
27 changes: 21 additions & 6 deletions packages/lsp-cli/src/FixCommand.ts
Expand Up @@ -10,6 +10,7 @@ import * as t from 'typanion';

import { BaseLspCommand } from './BaseLspCommand';
import { createActionFilter } from './createActionFilter';
import { createActionRewrite } from './createActionRewrite';
import { fixFile } from './fixFile';
import { glob } from './glob';

Expand Down Expand Up @@ -49,7 +50,19 @@ export class FixCommand extends BaseLspCommand {
Can be specified more than once to include multiple patterns.
`,
});
actionTransform = Option.String('--action-transform');
actionTextReplace = Option.Array('--action-text-replace', {
description: `
Pass a \`needle=haystack\` search/replace string to modify the
result after an action has been performed but before it has been
applied to your files.
`,
});
actionIgnoreDuplicates = Option.Boolean('--action-ignore-duplicates', {
description: `
If multiple actions are presented that do the same thing, ignore
all but one of them.
`,
});

// TODO(@izaakschroeder): Move this to `BaseLspCommand`
// TODO(@izaakschroeder): Do feature detection for workspaces.
Expand Down Expand Up @@ -115,12 +128,14 @@ export class FixCommand extends BaseLspCommand {

const actionFilter = createActionFilter(this.actionKinds);
let actionMap = null;
if (this.actionTransform) {
const parentURL = pathToFileURL(process.cwd());
const resolved = import.meta.resolve(this.actionTransform, parentURL);
actionMap = await import(resolved);
if (this.actionTextReplace) {
actionMap = createActionRewrite(this.actionTextReplace);
}
const fixOptions = { actionFilter, actionMap };
const fixOptions = {
actionFilter,
actionMap,
ignoreDuplicateActions: this.actionIgnoreDuplicates,
};

const exec = createThroat(this.parallel, async (path) => {
return await fixFile(lsp, path, fixOptions);
Expand Down
27 changes: 27 additions & 0 deletions packages/lsp-cli/src/createActionRewrite.ts
@@ -0,0 +1,27 @@
import type { CodeActionItem } from '@izaakschroeder/lsp-client';

export const createActionRewrite = (patterns: string[]) => {
const rewrites = patterns.map((patterns) => {
const [find, replace] = patterns.split('=', 2);
if (!find || !replace) {
throw new Error();
}
return (action: CodeActionItem) => {
for (const key in action.edit.changes) {
const changes = action.edit.changes[key];
if (!changes) {
continue;
}
for (const change of changes) {
change.newText = change.newText.replaceAll(find, replace);
}
}
return action;
};
});
return (action: CodeActionItem) => {
return rewrites.reduce((prev, cur) => {
return cur(prev);
}, action);
};
};
12 changes: 11 additions & 1 deletion packages/lsp-cli/src/fixFile.ts
@@ -1,6 +1,7 @@
import * as fs from 'node:fs/promises';
import { extname } from 'node:path';
import { pathToFileURL } from 'node:url';
import isEqual from 'fast-deep-equal/es6';

import type {
CodeActionItem,
Expand All @@ -24,6 +25,7 @@ const languageMap = {
interface FixFileOptions {
actionFilter?: ((action: CodeActionItem) => boolean) | null | undefined;
actionMap?: ((action: CodeActionItem) => CodeActionItem) | null | undefined;
ignoreDuplicateActions?: boolean | null | undefined;
}

export const fixFile = async (
Expand Down Expand Up @@ -80,8 +82,16 @@ export const fixFile = async (
if (actions.length === 0) {
return false;
}
let desiredActions = actions.filter((action) => {
let desiredActions = actions.filter((action, i) => {
let ignoredBecauseDuplicate = false;
if (options.ignoreDuplicateActions) {
const otherIndex = actions.findIndex((other) => {
return isEqual(other, action);
});
ignoredBecauseDuplicate = otherIndex !== i;
}
return (
!ignoredBecauseDuplicate &&
(options.actionFilter ? options.actionFilter(action) : true) &&
action.edit.changes[uri] &&
Object.keys(action.edit.changes).length === 1
Expand Down
6 changes: 3 additions & 3 deletions packages/lsp-cli/src/glob.ts
Expand Up @@ -13,15 +13,15 @@ export const glob = async (
cb: (path: string) => Promise<void>,
) => {
const { ignore, include } = options;
const isIgnored = ignore ? picomatch(ignore) : () => false;
const isIncluded = include ? picomatch(include) : () => true;
const isIgnored = ignore ? picomatch(ignore, { dot: true }) : () => false;
const isIncluded = include ? picomatch(include, { dot: true }) : () => true;

const processDir = async (root: string, dir: string) => {
const entries = await fs.readdir(dir, { withFileTypes: true });
await Promise.all(
entries.map(async (entry) => {
const path = join(dir, entry.name);
const relativePath = path.substring(root.length);
const relativePath = path.substring(root.length + 1);
if (isIgnored(relativePath)) {
return;
}
Expand Down
3 changes: 3 additions & 0 deletions packages/lsp-client/src/LspClient.ts
Expand Up @@ -71,6 +71,9 @@ export class LspClient {
}

#send(req: JSONRPCPayload) {
if (!this.#connections.length) {
throw new Error('Not connected.');
}
for (const connection of this.#connections) {
connection.send(req);
}
Expand Down
7 changes: 6 additions & 1 deletion packages/lsp-client/src/createTransport.ts
@@ -1,10 +1,15 @@
import { fileURLToPath } from 'node:url';
import { LspStdioTransport } from './LspStdioTransport';

export const createTransport = (urlString: string) => {
const url = new URL(urlString);
switch (url.protocol) {
case 'stdio:': {
const bin = url.hostname;
if (url.hostname) {
// TODO: Consider resolving non-absolute paths
throw new TypeError('Path must be absolute');
}
const bin = fileURLToPath(`file://${url.pathname}`);
const args = url.searchParams.getAll('arg');
return new LspStdioTransport(bin, args);
}
Expand Down
8 changes: 8 additions & 0 deletions yarn.lock
Expand Up @@ -285,6 +285,7 @@ __metadata:
cli-progress: "npm:3.12.0"
clipanion: "npm:4.0.0-rc.3"
esbuild: "npm:0.20.2"
fast-deep-equal: "npm:3.1.3"
ku-progress-bar: "npm:0.6.0"
picomatch: "npm:4.0.1"
rollup: "npm:4.13.0"
Expand Down Expand Up @@ -880,6 +881,13 @@ __metadata:
languageName: node
linkType: hard

"fast-deep-equal@npm:3.1.3":
version: 3.1.3
resolution: "fast-deep-equal@npm:3.1.3"
checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0
languageName: node
linkType: hard

"foreground-child@npm:^3.1.0":
version: 3.1.1
resolution: "foreground-child@npm:3.1.1"
Expand Down

0 comments on commit 4f1aab6

Please sign in to comment.