Skip to content

Commit

Permalink
CCDB linter
Browse files Browse the repository at this point in the history
  • Loading branch information
rtbo committed May 23, 2023
1 parent 7d1f57f commit ed4af0b
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 1 deletion.
17 changes: 17 additions & 0 deletions source/served/extension.d
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,11 @@ void changedConfig(ConfigWorkspace target, string[] paths, served.types.Configur
{
import served.linters.dscanner : clear1 = clear;
import served.linters.dub : clear2 = clear;
import served.linters.ccdb : clear3 = clear;

clear1();
clear2();
clear3();
}
break;
case "d.enableStaticLinting":
Expand All @@ -200,6 +202,14 @@ void changedConfig(ConfigWorkspace target, string[] paths, served.types.Configur
clear();
}
break;
case "d.enableCcdbLinting":
if (!config.d.enableCcdbLinting)
{
import served.linters.ccdb : clear;

clear();
}
break;
default:
break;
}
Expand Down Expand Up @@ -1125,6 +1135,13 @@ void onDidSaveDocument(DidSaveTextDocumentParams params)
{
import served.linters.dub;

lint(document);
}
}, {
if (config.d.enableCcdbLinting)
{
import served.linters.ccdb;

lint(document);
}
});
Expand Down
168 changes: 168 additions & 0 deletions source/served/linters/ccdb.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/// Linting module for ClangCompilationDatabase
///
/// This linter will simply run the command in the `compile_commands.json` file
/// and scan the output for errors.
module served.linters.ccdb;

import served.linters.diagnosticmanager;
import served.types;

import workspaced.api;
import workspaced.coms;

import std.algorithm;
import std.experimental.logger;
import std.file;
import std.process;
import std.range;

enum DiagnosticSlot = 3;
enum CcdbDiagnosticSource = "compile_commands.json";

private struct DocLinterStatus
{
bool running;
bool retryAtEnd;
}

DocLinterStatus[DocumentUri] linterStatus;

void lint(Document document)
{
void removeForDoc()
{
auto diag = diagnostics[DiagnosticSlot];
diag = diag.remove!(d => d.uri == document.uri);
diagnostics[DiagnosticSlot] = diag;
}
void noErrors()
{
removeForDoc();
updateDiagnostics();
}

auto instance = activeInstance = backend.getBestInstance!ClangCompilationDatabaseComponent(document.uri.uriToFile);
if (!instance)
return noErrors();

auto fileConfig = config(document.uri);
if (!fileConfig.d.enableLinting || !fileConfig.d.enableCcdbLinting)
return noErrors();

auto command = instance.get!ClangCompilationDatabaseComponent.getCompileCommand(uriToFile(document.uri));
if (!command)
{
auto dbPath = instance.get!ClangCompilationDatabaseComponent.getDbPath();
warningf("No command entry for %s in CCDB %s", uriToFile(document.uri), dbPath);
return noErrors();
}

auto statusp = document.uri in linterStatus;
if (!statusp) {
linterStatus[document.uri] = DocLinterStatus();
statusp = document.uri in linterStatus;
}
assert(statusp);

if (statusp.running) {
statusp.retryAtEnd = true;
return;
}

statusp.running = true;
scope(exit)
statusp.running = false;

do {
statusp.retryAtEnd = false;

tracef("running CCDB command for ", document.uri);
auto issues = command.run().getYield();
auto result = appender!(PublishDiagnosticsParams[]);

void pushError(Diagnostic error, string uri)
{
bool found;
foreach (ref elem; result.data)
if (elem.uri == uri)
{
found = true;
elem.diagnostics ~= error;
}
if (!found)
result ~= PublishDiagnosticsParams(uri, [error]);
}

while (!issues.empty)
{
import served.linters.dub : applyDubLintType;

auto issue = issues.front;
issues.popFront();
tracef("issue %s", issue);
int numSupplemental = cast(int) issues.length;
foreach (i, other; issues)
if (!other.cont)
{
numSupplemental = cast(int) i;
break;
}
auto supplemental = issues[0 .. numSupplemental];
if (numSupplemental > 0)
issues = issues[numSupplemental .. $];

auto uri = uriFromFile(command.getPath(issue.file));

Diagnostic error;
error.range = TextRange(issue.line - 1, issue.column - 1, issue.line - 1, uint.max);
applyDubLintType(error, issue.type);
error.source = CcdbDiagnosticSource;
error.message = issue.text;
if (supplemental.length)
error.relatedInformation = opt(supplemental.map!((other) {
DiagnosticRelatedInformation related;
string otherUri = other.file != issue.file ? command.getPath(other.file) : uri;
related.location = Location(otherUri, TextRange(other.line - 1,
other.column - 1, other.line - 1, uint.max));
related.message = other.text;
return related;
}).array);

//extendErrorRange(error.range, instance, uri, error);
pushError(error, uri);

foreach (i, suppl; supplemental)
{
if (suppl.text.startsWith("instantiated from here:"))
{
// add all "instantiated from here" errors in the project as diagnostics

auto supplUri = issue.file != suppl.file ? uriFromFile(command.getPath(suppl.file)) : uri;

if (workspaceIndex(supplUri) == size_t.max)
continue;

Diagnostic supplError;
supplError.range = TextRange(suppl.line - 1, suppl.column - 1, suppl.line - 1, uint.max);
applyDubLintType(supplError, issue.type);
supplError.source = CcdbDiagnosticSource;
supplError.message = issue.text ~ "\n" ~ suppl.text;
if (i + 1 < supplemental.length)
supplError.relatedInformation = opt(error.relatedInformation.deref[i + 1 .. $]);
pushError(supplError, supplUri);
}
}
}

removeForDoc();
diagnostics[DiagnosticSlot] ~= result.data;
updateDiagnostics();
}
while (statusp.retryAtEnd);
}

void clear()
{
diagnostics[DiagnosticSlot] = null;
updateDiagnostics();
}
2 changes: 1 addition & 1 deletion source/served/linters/diagnosticmanager.d
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import std.algorithm : map, sort;
import served.utils.memory;
import served.types;

enum NumDiagnosticProviders = 3;
enum NumDiagnosticProviders = 4;
alias DiagnosticCollection = PublishDiagnosticsParams[];
DiagnosticCollection[NumDiagnosticProviders] diagnostics;

Expand Down

0 comments on commit ed4af0b

Please sign in to comment.