From 43caad72a86751a441f644e9aa5881110f0e8c79 Mon Sep 17 00:00:00 2001 From: WebFreak001 Date: Sat, 8 Jul 2023 22:26:59 +0200 Subject: [PATCH] add CLI API to get autofixes at location --- README.md | 42 +++++++++++++++++ src/dscanner/analysis/run.d | 92 +++++++++++++++++++++++++++++++++++-- src/dscanner/main.d | 11 ++++- 3 files changed, 139 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 617a6cc..624ed2a 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,48 @@ dscanner -S -f github source/ dscanner -S -f '{filepath}({line}:{column})[{type}]: {message}' source/ ``` +To collect automatic issue fixes for a given location use + +```sh +# collecting automatic issue fixes +# --resolveMessage : +dscanner --resolveMessage 11:3 file.d +# --resolveMessage b +dscanner --resolveMessage b512 file.d +# may be omitted to read from stdin +``` + +outputs JSON: + +```json +// list of available auto-fixes at the given location +[ + { + "name": "Make function const", + // byte range `[start, end)` what code to replace + // this is sorted by range[0] + "replacements": [ + // replace: range[0] < range[1], newText != "" + {"range": [10, 14], "newText": "const "}, + // insert: range[0] == range[1], newText != "" + {"range": [20, 20], "newText": "auto"}, + // remove: range[0] < range[1], newText == "" + {"range": [30, 40], "newText": ""}, + ] + } +] +``` + +Algorithm to apply replacements: +```d +foreach_reverse (r; replacements) + codeBytes = codeBytes[0 .. r.range[0]] ~ r.newText ~ codeBytes[r.range[1] .. $]; +``` + +Replacements are non-overlapping, sorted by `range[0]` in ascending order. When +combining multiple different replacements, you first need to sort them by +`range[0]` to apply using the algorithm above. + ## Other features ### Token Count diff --git a/src/dscanner/analysis/run.d b/src/dscanner/analysis/run.d index a8f3a08..44477d8 100644 --- a/src/dscanner/analysis/run.d +++ b/src/dscanner/analysis/run.d @@ -480,6 +480,90 @@ bool autofix(string[] fileNames, const StaticAnalysisConfig config, string error return hasErrors; } +void listAutofixes( + StaticAnalysisConfig config, + string resolveMessage, + bool usingStdin, + string fileName, + StringCache* cache, + ref ModuleCache moduleCache +) +{ + import dparse.parser : parseModule; + import dscanner.analysis.base : Message; + import std.format : format; + import std.json : JSONValue; + + union RequestedLocation + { + struct + { + uint line, column; + } + ulong bytes; + } + + RequestedLocation req; + bool isBytes = resolveMessage[0] == 'b'; + if (isBytes) + req.bytes = resolveMessage[1 .. $].to!ulong; + else + { + auto parts = resolveMessage.findSplit(":"); + req.line = parts[0].to!uint; + req.column = parts[2].to!uint; + } + + bool matchesCursor(Message m) + { + return isBytes + ? req.bytes >= m.startIndex && req.bytes <= m.endIndex + : req.line >= m.startLine && req.line <= m.endLine + && (req.line > m.startLine || req.column >= m.startColumn) + && (req.line < m.endLine || req.column <= m.endColumn); + } + + RollbackAllocator rba; + LexerConfig lexerConfig; + lexerConfig.fileName = fileName; + lexerConfig.stringBehavior = StringBehavior.source; + auto tokens = getTokensForParser(usingStdin ? readStdin() + : readFile(fileName), lexerConfig, cache); + auto mod = parseModule(tokens, fileName, &rba, toDelegate(&doNothing)); + + auto messages = analyze(fileName, mod, config, moduleCache, tokens); + + with (stdout.lockingTextWriter) + { + put("["); + foreach (message; messages[].filter!matchesCursor) + { + resolveAutoFixes(message, fileName, moduleCache, tokens, mod, config); + + foreach (i, autofix; message.autofixes) + { + put(i == 0 ? "\n" : ",\n"); + put("\t{\n"); + put(format!"\t\t\"name\": %s,\n"(JSONValue(autofix.name))); + put("\t\t\"replacements\": ["); + foreach (j, replacement; autofix.expectReplacements) + { + put(j == 0 ? "\n" : ",\n"); + put(format!"\t\t\t{\"range\": [%d, %d], \"newText\": %s}"( + replacement.range[0], + replacement.range[1], + JSONValue(replacement.newText))); + } + put("\n"); + put("\t\t]\n"); + put("\t}"); + } + } + put("\n]"); + } + stdout.flush(); +} + private struct UserSelect { import std.string : strip; @@ -942,9 +1026,8 @@ private void resolveMessageFromCheck( } } -AutoFix.CodeReplacement[] resolveAutoFix(const Message message, - const AutoFix.ResolveContext resolve, string fileName, - ref ModuleCache moduleCache, scope const(char)[] rawCode, +void resolveAutoFixes(ref Message message, string fileName, + ref ModuleCache moduleCache, scope const(Token)[] tokens, const Module m, const StaticAnalysisConfig analysisConfig, const AutoFixFormatting overrideFormattingConfig = AutoFixFormatting.invalid) @@ -973,7 +1056,8 @@ AutoFix.CodeReplacement[] resolveAutoFix(const Message message, { if (check.getName() == message.checkName) { - return check.resolveAutoFix(m, tokens, message, resolve, formattingConfig); + resolveMessageFromCheck(message, check, m, tokens, formattingConfig); + return; } } diff --git a/src/dscanner/main.d b/src/dscanner/main.d index b571269..3e9bab9 100644 --- a/src/dscanner/main.d +++ b/src/dscanner/main.d @@ -64,6 +64,7 @@ else bool report; bool skipTests; bool applySingleFixes; + string resolveMessage; string reportFormat; string reportFile; string symbolName; @@ -98,6 +99,7 @@ else "report", &report, "reportFormat", &reportFormat, "reportFile", &reportFile, + "resolveMessage", &resolveMessage, "applySingle", &applySingleFixes, "I", &importPaths, "version", &printVersion, @@ -213,7 +215,7 @@ else immutable optionCount = count!"a"([sloc, highlight, ctags, tokenCount, syntaxCheck, ast, imports, outline, tokenDump, styleCheck, - defaultConfig, report, autofix, + defaultConfig, report, autofix, resolveMessage.length, symbolName !is null, etags, etagsAll, recursiveImports, ]); if (optionCount > 1) @@ -286,7 +288,7 @@ else { stdout.printEtags(etagsAll, expandArgs(args)); } - else if (styleCheck || autofix) + else if (styleCheck || autofix || resolveMessage.length) { StaticAnalysisConfig config = defaultStaticAnalysisConfig(); string s = configLocation is null ? getConfigurationLocation() : configLocation; @@ -299,6 +301,11 @@ else { return .autofix(expandArgs(args), config, errorFormat, cache, moduleCache, applySingleFixes) ? 1 : 0; } + else if (resolveMessage.length) + { + listAutofixes(config, resolveMessage, usingStdin, usingStdin ? "stdin" : args[1], &cache, moduleCache); + return 0; + } else if (report) { switch (reportFormat)