Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support deno lint #162

Merged
merged 10 commits into from
Sep 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ console.log("concat Array", M.concat([1, 2], [2, 3]));

- `deno.unstable` - If Deno's unstable mode is enabled. Default is `false`

- `deno.lint` - If inline `deno lint` diagnostics are enabled. Because this is experimental, `deno.unstable = true` is required. Default is `false`

We recommend that you do not set global configuration. It should be configured in `.vscode/settings.json` in the project directory:

```json5
Expand All @@ -164,6 +166,19 @@ This extension also provides Deno's formatting tools, settings are in `.vscode/s
}
```

This extension also provides inline `deno lint` diagnostics. You can enable this in `.vscode/settings.json`:

NOTE: Since `deno lint` is still an experimental feature, you need to set `deno.unstable = true` in your VS Code settings. This function may change in the future.

```json5
// .vscode/settings.json
{
"deno.enable": true,
"deno.unstable": true,
"deno.lint": true,
}
```

## Contribute

Follow these steps to contribute, the community needs your strength.
Expand Down
80 changes: 64 additions & 16 deletions client/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
TextDocument,
languages,
env,
Position,
} from "vscode";
import {
LanguageClient,
Expand Down Expand Up @@ -244,7 +245,10 @@ export class Extension {
}
const denoDiagnostics: Diagnostic[] = [];
for (const diagnostic of context.diagnostics) {
if (diagnostic.source === "Deno Language Server") {
if (
diagnostic.source === "Deno Language Server" ||
diagnostic.source === "deno_lint"
) {
denoDiagnostics.push(diagnostic);
}
}
Expand Down Expand Up @@ -356,29 +360,39 @@ Executable ${this.denoInfo.executablePath}`;
[command: string]: (
editor: TextEditor,
text: string,
range: Range
range: Range,
...args: unknown[]
) => void | Promise<void>;
}) {
for (const command in map) {
const handler = map[command];
this.registerCommand(command, async (uri: string, range: Range) => {
const textEditor = window.activeTextEditor;
this.registerCommand(
command,
async (uri: string, range: Range, ...args: unknown[]) => {
const textEditor = window.activeTextEditor;

if (!textEditor || textEditor.document.uri.toString() !== uri) {
return;
}
if (!textEditor || textEditor.document.uri.toString() !== uri) {
return;
}

range = new Range(
range.start.line,
range.start.character,
range.end.line,
range.end.character
);
range = new Range(
range.start.line,
range.start.character,
range.end.line,
range.end.character
);

const rangeText = textEditor.document.getText(range);
const rangeText = textEditor.document.getText(range);

return await handler.call(this, textEditor, rangeText, range);
});
return await handler.call(
this,
textEditor,
rangeText,
range,
...args
);
}
);
}
}
// update diagnostic for a Document
Expand Down Expand Up @@ -596,6 +610,40 @@ Executable ${this.denoInfo.executablePath}`;

this.updateDiagnostic(editor.document.uri);
},
_ignore_next_line_lint: async (editor, _, range, rule: unknown) => {
editor.edit((edit) => {
const currentLineText = editor.document.lineAt(range.start.line);
const previousLineText = editor.document.lineAt(range.start.line - 1);

const offset =
currentLineText.text.length - currentLineText.text.trim().length;

if (/^\s*\/\/\s+deno-lint-ignore\s*/.test(previousLineText.text)) {
edit.replace(
previousLineText.range,
previousLineText.text + " " + rule
);
} else {
edit.replace(
previousLineText.range,
previousLineText.text +
"\n" +
`${" ".repeat(offset)}// deno-lint-ignore ${rule}`
);
}
});
return;
},
_ignore_entry_file: async (editor) => {
editor.edit((edit) => {
const firstLineText = editor.document.lineAt(0);
edit.insert(
new Position(0, 0),
"// deno-lint-ignore-file" + (firstLineText.text ? "\n" : "")
);
});
return;
},
});

this.watchConfiguration(() => {
Expand Down
5 changes: 5 additions & 0 deletions core/configuration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ test("core / configuration / resolveFromVscode if it is a valid file", async ()
enable: true,
unstable: true,
import_map: "./import_map.json",
lint: false,
} as ConfigurationField);
});

Expand All @@ -29,6 +30,7 @@ test("core / configuration / resolveFromVscode if valid section", async () => {
enable: true,
unstable: false,
import_map: null,
lint: false,
} as ConfigurationField);
});

Expand All @@ -51,6 +53,7 @@ test("core / configuration / resolveFromVscode if config file is empty", async (
enable: false,
unstable: false,
import_map: null,
lint: false,
} as ConfigurationField);
});

Expand All @@ -65,6 +68,7 @@ test("core / configuration / resolveFromVscode if field is invalid", async () =>
enable: true,
unstable: true,
import_map: "1,2,3",
lint: false,
} as ConfigurationField);
});

Expand All @@ -87,5 +91,6 @@ test("core / configuration / update", async () => {
enable: true,
unstable: false,
import_map: null,
lint: false,
} as ConfigurationField);
});
4 changes: 4 additions & 0 deletions core/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ export const DenoPluginConfigurationField: (keyof ConfigurationField)[] = [
"enable",
"unstable",
"import_map",
"lint",
];

export type ConfigurationField = {
enable?: boolean;
unstable?: boolean;
import_map?: string | null;
lint?: boolean;
};

interface ConfigurationInterface {
Expand All @@ -31,6 +33,7 @@ export class Configuration implements ConfigurationInterface {
enable: false,
unstable: false,
import_map: null,
lint: false,
};

private readonly _configUpdatedListeners = new Set<() => void>();
Expand Down Expand Up @@ -76,6 +79,7 @@ export class Configuration implements ConfigurationInterface {
// Make sure the type of each configuration item is correct
this._configuration.enable = !!this._configuration.enable;
this._configuration.unstable = !!this._configuration.unstable;
this._configuration.lint = !!this._configuration.lint;
this._configuration.import_map = this._configuration.import_map
? this._configuration.import_map + ""
: null;
Expand Down
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,16 @@
true,
false
]
},
"deno.lint": {
"type": "boolean",
"default": false,
"markdownDescription": "Controls if `deno lint` is enabled. It is currently experimental, so make sure `deno.unstable: true` is enabled. \n\n**Not recommended in global configuration**",
"scope": "resource",
"examples": [
true,
false
]
}
}
},
Expand Down
2 changes: 2 additions & 0 deletions server/src/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { Request } from "../../core/const";
type Configuration = {
enable: boolean;
import_map?: string;
unstable?: boolean;
lint?: boolean;
};

/**
Expand Down
110 changes: 93 additions & 17 deletions server/src/deno.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Readable } from "stream";
import execa from "execa";
import which from "which";
import * as semver from "semver";
import { Cache } from "../../core/cache";

type Version = {
deno: string;
Expand All @@ -15,6 +16,34 @@ type FormatOptions = {
cwd: string;
};

interface LintLocation {
line: number; // one base number
col: number; // zero base number
}

interface LintDiagnostic {
code: string;
filename: string;
message: string;
range: {
start: LintLocation;
end: LintLocation;
};
}

interface LintError {
file_path: string;
message: string;
}

interface LintOutput {
diagnostics: LintDiagnostic[];
errors: LintError[];
}

// caching Deno lint's rules for 120s or 100 referenced times
const denoLintRulesCache = Cache.create<string[]>(1000 * 120, 100);

class Deno {
public version!: Version | void;
public executablePath!: string | void;
Expand All @@ -33,25 +62,23 @@ class Deno {
return;
}

// If the currently used Deno is less than 0.33.0
// If the currently used Deno is less than 1.3.3
// We will give an warning to upgrade.
const minimumDenoVersion = "0.35.0";
const minimumDenoVersion = "1.3.3";
if (!semver.gte(this.version.deno, minimumDenoVersion)) {
throw new Error(`Please upgrade to Deno ${minimumDenoVersion} or above.`);
}
}
public async getTypes(unstable: boolean): Promise<Buffer> {
const { stdout } = await execa(this.executablePath as string, [
"types",
...(unstable && this.version && semver.gte(this.version.deno, "0.43.0")
? ["--unstable"]
: []),
...(unstable ? ["--unstable"] : []),
]);

return Buffer.from(stdout, "utf8");
}
// format code
// echo "console.log(123)" | deno fmt --stdin
// echo "console.log(123)" | deno fmt -
public async format(code: string, options: FormatOptions): Promise<string> {
const reader = Readable.from([code]);

Expand All @@ -72,22 +99,71 @@ class Deno {
resolve(stdout);
}
});
subprocess.on("error", (err: Error) => {
reject(err);
});
subprocess.stdout?.on("data", (data: Buffer) => {
stdout += data;
});

subprocess.stderr?.on("data", (data: Buffer) => {
stderr += data;
});

subprocess.on("error", (err: Error) => reject(err));
subprocess.stdout?.on("data", (data: Buffer) => (stdout += data));
subprocess.stderr?.on("data", (data: Buffer) => (stderr += data));
subprocess.stdin && reader.pipe(subprocess.stdin);
})) as string;

return formattedCode;
}

public async getLintRules(): Promise<string[]> {
const cachedRules = denoLintRulesCache.get();
if (cachedRules) {
return cachedRules;
}
const subprocess = execa(
this.executablePath as string,
["lint", "--unstable", "--rules"],
{
stdout: "pipe",
}
);

const output = await new Promise<string>((resolve, reject) => {
let stdout = "";
subprocess.on("exit", () => resolve(stdout));
subprocess.on("error", (err: Error) => reject(err));
subprocess.stdout?.on("data", (data: Buffer) => (stdout += data));
});

const rules = output
.split("\n")
.map((v) => v.trim())
.filter((v) => v.startsWith("-"))
.map((v) => v.replace(/^-\s+/, ""));

denoLintRulesCache.set(rules);

return rules;
}

// lint code
// echo "console.log(123)" | deno lint --unstable --json -
public async lint(code: string): Promise<LintOutput> {
const reader = Readable.from([code]);

const subprocess = execa(
this.executablePath as string,
["lint", "--unstable", "--json", "-"],
{
stdin: "pipe",
stderr: "pipe",
}
);

const output = await new Promise<string>((resolve, reject) => {
let stderr = "";
subprocess.on("exit", () => resolve(stderr));
subprocess.on("error", (err: Error) => reject(err));
subprocess.stderr?.on("data", (data: Buffer) => (stderr += data));
subprocess.stdin && reader.pipe(subprocess.stdin);
});

return JSON.parse(output) as LintOutput;
}

private async getExecutablePath(): Promise<string | undefined> {
const denoPath = await which("deno").catch(() =>
Promise.resolve(undefined)
Expand Down
Loading