mirror of
https://github.com/jlengrand/elm-language-client-vscode.git
synced 2026-03-10 08:11:17 +00:00
Port linter from vscode-elm
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
"vscode": "^1.25.0"
|
||||
},
|
||||
"activationEvents": [
|
||||
"onLanguage:plaintext"
|
||||
"onLanguage:elm"
|
||||
],
|
||||
"main": "./client/out/extension",
|
||||
"contributes": {
|
||||
|
||||
182
server/src/elmLinter.ts
Normal file
182
server/src/elmLinter.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
import * as cp from 'child_process';
|
||||
import * as readline from 'readline';
|
||||
import * as utils from './elmUtils';
|
||||
import * as vscode from 'vscode-languageserver/lib/main';
|
||||
import Uri from 'vscode-uri/lib/umd';
|
||||
import { Diagnostic } from 'vscode-languageserver/lib/main';
|
||||
|
||||
export interface IElmIssueRegion {
|
||||
start: { line: number; column: number };
|
||||
end: { line: number; column: number };
|
||||
}
|
||||
|
||||
export interface IElmIssue {
|
||||
tag: string;
|
||||
overview: string;
|
||||
subregion: string;
|
||||
details: string;
|
||||
region: IElmIssueRegion;
|
||||
type: string;
|
||||
file: string;
|
||||
}
|
||||
|
||||
function severityStringToDiagnosticSeverity(
|
||||
severity: string,
|
||||
): vscode.DiagnosticSeverity {
|
||||
switch (severity) {
|
||||
case 'error':
|
||||
return vscode.DiagnosticSeverity.Error;
|
||||
case 'warning':
|
||||
return vscode.DiagnosticSeverity.Warning;
|
||||
default:
|
||||
return vscode.DiagnosticSeverity.Error;
|
||||
}
|
||||
}
|
||||
|
||||
function elmMakeIssueToDiagnostic(issue: IElmIssue): vscode.Diagnostic {
|
||||
let lineRange: vscode.Range = vscode.Range.create(
|
||||
issue.region.start.line - 1,
|
||||
issue.region.start.column - 1,
|
||||
issue.region.end.line - 1,
|
||||
issue.region.end.column - 1,
|
||||
);
|
||||
return vscode.Diagnostic.create(
|
||||
lineRange,
|
||||
issue.overview + ' - ' + issue.details.replace(/\[\d+m/g, ''),
|
||||
severityStringToDiagnosticSeverity(issue.type),
|
||||
);
|
||||
}
|
||||
|
||||
function checkForErrors(connection: vscode.Connection, rootPath: string, filename: string): Promise<IElmIssue[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const makeCommand: string = 'elm-make';
|
||||
const cwd: string =
|
||||
utils.detectProjectRoot(filename) || rootPath;
|
||||
let make: cp.ChildProcess;
|
||||
if (utils.isWindows) {
|
||||
filename = "\"" + filename + "\""
|
||||
}
|
||||
const args = [filename, '--report', 'json', '--output', '/dev/null'];
|
||||
if (utils.isWindows) {
|
||||
make = cp.exec(makeCommand + ' ' + args.join(' '), { cwd: cwd });
|
||||
} else {
|
||||
make = cp.spawn(makeCommand, args, { cwd: cwd });
|
||||
}
|
||||
// output is actually optional
|
||||
// (fixed in https://github.com/Microsoft/vscode/commit/b4917afe9bdee0e9e67f4094e764f6a72a997c70,
|
||||
// but unreleased at this time)
|
||||
const stdoutlines: readline.ReadLine = readline.createInterface({
|
||||
input: make.stdout,
|
||||
output: undefined,
|
||||
});
|
||||
const lines: IElmIssue[] = [];
|
||||
stdoutlines.on('line', (line: string) => {
|
||||
// Ignore compiler success.
|
||||
if (line.startsWith('Successfully generated')) {
|
||||
return;
|
||||
}
|
||||
// Elm writes out JSON arrays of diagnostics, with one array per line.
|
||||
// Multiple lines may be received.
|
||||
lines.push(...(<IElmIssue[]>JSON.parse(line)));
|
||||
});
|
||||
const stderr: Buffer[] = [];
|
||||
make.stderr.on('data', (data: Buffer) => {
|
||||
if (data) {
|
||||
stderr.push(data);
|
||||
}
|
||||
});
|
||||
make.on('error', (err: Error) => {
|
||||
stdoutlines.close();
|
||||
if (err && (<any>err).code === 'ENOENT') {
|
||||
connection.console.log(
|
||||
"The 'elm-make' compiler is not available. Install Elm from http://elm-lang.org/.",
|
||||
);
|
||||
resolve([]);
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
make.on('close', (code: number, signal: string) => {
|
||||
stdoutlines.close();
|
||||
if (stderr.length) {
|
||||
let errorResult: IElmIssue = {
|
||||
tag: 'error',
|
||||
overview: '',
|
||||
subregion: '',
|
||||
details: stderr.join(''),
|
||||
region: {
|
||||
start: {
|
||||
line: 1,
|
||||
column: 1,
|
||||
},
|
||||
end: {
|
||||
line: 1,
|
||||
column: 1,
|
||||
},
|
||||
},
|
||||
type: 'error',
|
||||
file: filename,
|
||||
};
|
||||
resolve([errorResult]);
|
||||
} else {
|
||||
resolve(lines);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function runLinter(
|
||||
connection: vscode.Connection,
|
||||
rootPath: string,
|
||||
document: vscode.TextDocument,
|
||||
// elmAnalyse: ElmAnalyse,
|
||||
): void {
|
||||
let compileErrors: vscode.Diagnostic[] = [];
|
||||
let uri: Uri = Uri.parse(document.uri);
|
||||
|
||||
checkForErrors(connection, rootPath, uri.fsPath)
|
||||
.then((compilerErrors: IElmIssue[]) => {
|
||||
const cwd: string =
|
||||
utils.detectProjectRoot(uri.fsPath) || rootPath;
|
||||
let splitCompilerErrors: Map<string, IElmIssue[]> = new Map();
|
||||
|
||||
compilerErrors.forEach((issue: IElmIssue) => {
|
||||
// If provided path is relative, make it absolute
|
||||
if (issue.file.startsWith('.')) {
|
||||
issue.file = cwd + issue.file.slice(1);
|
||||
}
|
||||
if (splitCompilerErrors.has(issue.file)) {
|
||||
splitCompilerErrors.get(issue.file).push(issue);
|
||||
} else {
|
||||
splitCompilerErrors.set(issue.file, [issue]);
|
||||
}
|
||||
});
|
||||
// Turn split arrays into diagnostics and associate them with correct files in VS
|
||||
splitCompilerErrors.forEach((issue: IElmIssue[], path: string) => {
|
||||
connection.sendDiagnostics({
|
||||
uri: document.uri,
|
||||
diagnostics: issue.map(error => elmMakeIssueToDiagnostic(error)),
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
});
|
||||
|
||||
// if (elmAnalyse.elmAnalyseIssues.length > 0) {
|
||||
// let splitCompilerErrors: Map<string, IElmIssue[]> = new Map();
|
||||
// elmAnalyse.elmAnalyseIssues.forEach((issue: IElmIssue) => {
|
||||
// if (splitCompilerErrors.has(issue.file)) {
|
||||
// splitCompilerErrors.get(issue.file).push(issue);
|
||||
// } else {
|
||||
// splitCompilerErrors.set(issue.file, [issue]);
|
||||
// }
|
||||
// splitCompilerErrors.forEach(
|
||||
// (analyserIssue: IElmIssue[], path: string) => {
|
||||
// compileErrors.concat(
|
||||
// analyserIssue.map(error => elmMakeIssueToDiagnostic(error)),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// });
|
||||
// }
|
||||
}
|
||||
67
server/src/elmUtils.ts
Normal file
67
server/src/elmUtils.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import * as cp from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode-languageserver';
|
||||
|
||||
export const isWindows = process.platform === 'win32';
|
||||
|
||||
/** Options for execCmd */
|
||||
export interface ExecCmdOptions {
|
||||
/** The project root folder for this file is used as the cwd of the process */
|
||||
fileName?: string;
|
||||
/** Any arguments */
|
||||
cmdArguments?: string[];
|
||||
/** Shows a message if an error occurs (in particular the command not being */
|
||||
/* found), instead of rejecting. If this happens, the promise never resolves */
|
||||
showMessageOnError?: boolean;
|
||||
/** Called after the process successfully starts */
|
||||
onStart?: () => void;
|
||||
/** Called when data is sent to stdout */
|
||||
onStdout?: (data: string) => void;
|
||||
/** Called when data is sent to stderr */
|
||||
onStderr?: (data: string) => void;
|
||||
/** Called after the command (successfully or unsuccessfully) exits */
|
||||
onExit?: () => void;
|
||||
/** Text to add when command is not found (maybe helping how to install) */
|
||||
notFoundText?: string;
|
||||
}
|
||||
|
||||
|
||||
export function findProj(dir: string): string {
|
||||
if (fs.lstatSync(dir).isDirectory()) {
|
||||
const files = fs.readdirSync(dir);
|
||||
const file = files.find((v, i) => v === 'elm-package.json');
|
||||
if (file !== undefined) {
|
||||
return dir + path.sep + file;
|
||||
}
|
||||
let parent = '';
|
||||
if (dir.lastIndexOf(path.sep) > 0) {
|
||||
parent = dir.substr(0, dir.lastIndexOf(path.sep));
|
||||
}
|
||||
if (parent === '') {
|
||||
return '';
|
||||
} else {
|
||||
return findProj(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function detectProjectRoot(fileName: string): string {
|
||||
const proj = findProj(path.dirname(fileName));
|
||||
if (proj !== '') {
|
||||
return path.dirname(proj);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getIndicesOf(searchStr: string, str: string): number[] {
|
||||
let startIndex = 0,
|
||||
searchStrLen = searchStr.length;
|
||||
let index,
|
||||
indices = [];
|
||||
while ((index = str.indexOf(searchStr, startIndex)) > -1) {
|
||||
indices.push(index);
|
||||
startIndex = index + searchStrLen;
|
||||
}
|
||||
return indices;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
import * as cp from 'child_process';
|
||||
import Uri from 'vscode-uri'
|
||||
import Uri from 'vscode-uri/lib/umd'
|
||||
const Compiler = require('node-elm-compiler');
|
||||
import {
|
||||
createConnection,
|
||||
@@ -18,6 +18,8 @@ import {
|
||||
Position,
|
||||
TextEdit
|
||||
} from 'vscode-languageserver';
|
||||
import { runLinter, IElmIssue } from './elmLinter';
|
||||
// import { ElmAnalyse } from './elmAnalyse';
|
||||
|
||||
// Create a connection for the server. The connection uses Node's IPC as a transport.
|
||||
// Also include all preview / proposed LSP features.
|
||||
@@ -29,9 +31,11 @@ let documents: TextDocuments = new TextDocuments();
|
||||
|
||||
let hasConfigurationCapability: boolean = false;
|
||||
let hasWorkspaceFolderCapability: boolean = false;
|
||||
let rootPath: string = undefined;
|
||||
|
||||
connection.onInitialize((params: InitializeParams) => {
|
||||
let capabilities = params.capabilities;
|
||||
this.rootPath = params.rootPath;
|
||||
|
||||
// Does the client support the `workspace/configuration` request?
|
||||
// If not, we will fall back using global settings
|
||||
@@ -66,12 +70,20 @@ connection.onInitialized(() => {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
documents.onDidOpen(params => {
|
||||
validateTextDocument(params.document);
|
||||
const elmAnalyseIssues: IElmIssue[] = [];
|
||||
// const elmAnalyse = new ElmAnalyse(elmAnalyseIssues);
|
||||
runLinter(connection, this.rootPath, params.document);
|
||||
// validateTextDocument(params.document);
|
||||
});
|
||||
|
||||
documents.onDidSave(params => {
|
||||
validateTextDocument(params.document);
|
||||
// const elmAnalyseIssues: IElmIssue[] = [];
|
||||
// const elmAnalyse = new ElmAnalyse(elmAnalyseIssues);
|
||||
runLinter(connection, this.rootPath, params.document);
|
||||
|
||||
// validateTextDocument(params.document);
|
||||
});
|
||||
|
||||
async function validateTextDocument(textDocument: TextDocument): Promise<void> {
|
||||
@@ -81,7 +93,10 @@ async function validateTextDocument(textDocument: TextDocument): Promise<void> {
|
||||
let diagnostics: Diagnostic[] = []
|
||||
try {
|
||||
await Compiler.compileToString(uri.fsPath, { report: 'json' })
|
||||
|
||||
|
||||
var x = await Compiler.findAllDependencies(uri.fsPath);
|
||||
connection.console.log(x);
|
||||
|
||||
} catch (err) {
|
||||
const issues = JSON.parse(err.message.split('\n')[1]);
|
||||
const byFile = issues.reduce((acc: any, issue: any) => {
|
||||
|
||||
Reference in New Issue
Block a user