diff --git a/source/served/extension.d b/source/served/extension.d index 6c0fa257..cecda6a2 100644 --- a/source/served/extension.d +++ b/source/served/extension.d @@ -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": @@ -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; } @@ -1125,6 +1135,13 @@ void onDidSaveDocument(DidSaveTextDocumentParams params) { import served.linters.dub; + lint(document); + } + }, { + if (config.d.enableCcdbLinting) + { + import served.linters.ccdb; + lint(document); } }); diff --git a/source/served/linters/ccdb.d b/source/served/linters/ccdb.d new file mode 100644 index 00000000..151eb210 --- /dev/null +++ b/source/served/linters/ccdb.d @@ -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(); +} \ No newline at end of file diff --git a/source/served/linters/diagnosticmanager.d b/source/served/linters/diagnosticmanager.d index 8f2dd317..9e4edb7f 100644 --- a/source/served/linters/diagnosticmanager.d +++ b/source/served/linters/diagnosticmanager.d @@ -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;