clean up quickfix lint title system

This commit is contained in:
XeroOl 2025-06-25 00:45:30 -05:00
parent 623700e1a2
commit fc57612fea
4 changed files with 103 additions and 81 deletions

View File

@ -108,7 +108,7 @@ Every time the client sends a message, it gets handled by a function in the corr
symbol symbol
(analyzer.find-nearest-definition server this-file symbol byte) (analyzer.find-nearest-definition server this-file symbol byte)
{: referenced-by :file {:uri this-file.uri &as file} : binding} {: referenced-by :file {:uri this-file.uri &as file} : binding}
(let [result (icollect [_ {:symbol reference} (ipairs referenced-by)] (let [result (icollect [_ {:symbol reference} (ipairs referenced-by) &into (message.array)]
{:range (message.ast->range server file reference) {:range (message.ast->range server file reference)
:kind documentHighlightKind.Read})] :kind documentHighlightKind.Read})]
(table.insert result {:range (message.ast->range server file binding) (table.insert result {:range (message.ast->range server file binding)
@ -142,12 +142,12 @@ Every time the client sends a message, it gets handled by a function in the corr
(let [file (files.get-by-uri server uri) (let [file (files.get-by-uri server uri)
byte (utils.position->byte file.text position server.position-encoding)] byte (utils.position->byte file.text position server.position-encoding)]
(case-try (analyzer.find-nearest-call server file byte) (case-try (analyzer.find-nearest-call server file byte)
(symbol active-parameter) (call active-parameter)
(analyzer.find-definition server file symbol) (analyzer.find-definition server file call)
{:indeterminate nil &as definition} {:indeterminate nil &as definition}
(formatter.signature-help-format definition) (formatter.signature-help-format definition)
signature signature
(message.symbol->signature-help server file symbol (message.call->signature-help server file call
signature signature
active-parameter) active-parameter)
(catch _ nil)))) (catch _ nil))))
@ -207,9 +207,8 @@ Every time the client sends a message, it gets handled by a function in the corr
(λ requests.textDocument/codeAction [server _send {: range :textDocument {: uri}}] (λ requests.textDocument/codeAction [server _send {: range :textDocument {: uri}}]
(let [file (files.get-by-uri server uri)] (let [file (files.get-by-uri server uri)]
(icollect [_ diagnostic (ipairs file.diagnostics)] (icollect [_ diagnostic (ipairs file.diagnostics) &into (message.array)]
(if (and (overlap? diagnostic.range range) (if (overlap? diagnostic.range range)
diagnostic.quickfix)
(message.diagnostic->code-action server file diagnostic :quickfix))))) (message.diagnostic->code-action server file diagnostic :quickfix)))))
(λ notifications.textDocument/didChange [server send {: contentChanges :textDocument {: uri}}] (λ notifications.textDocument/didChange [server send {: contentChanges :textDocument {: uri}}]

View File

@ -13,8 +13,10 @@ the `file.diagnostics` field, filling it with diagnostics."
(local diagnostic-mt {:__tojson (fn [{: self} state] (dkjson.encode self state)) (local diagnostic-mt {:__tojson (fn [{: self} state] (dkjson.encode self state))
:__index #(. $1 :self $2)}) :__index #(. $1 :self $2)})
(fn diagnostic [self quickfix] (fn diagnostic [self]
(setmetatable {: self : quickfix} diagnostic-mt)) (let [fix self.fix]
(set self.fix nil)
(setmetatable {: self : fix} diagnostic-mt)))
(fn could-be-rewritten-as-sym? [str] (fn could-be-rewritten-as-sym? [str]
(and (= :string (type str)) (not (str:find "^%d")) (and (= :string (type str)) (not (str:find "^%d"))
@ -33,9 +35,10 @@ the `file.diagnostics` field, filling it with diagnostics."
{:range (message.ast->range server file symbol) {:range (message.ast->range server file symbol)
:message (.. "unused definition: " (tostring symbol)) :message (.. "unused definition: " (tostring symbol))
:severity message.severity.WARN :severity message.severity.WARN
:code :unused-definition} :code :unused-definition
#[{:range (message.ast->range server file symbol) :fix #{:title (.. "Replace " (tostring symbol) " with _" (tostring symbol))
:newText (.. "_" (tostring symbol))}]))) :changes [{:range (message.ast->range server file symbol)
:newText (.. "_" (tostring symbol))}]}})))
;; this is way too specific; it's also safe to do this inside an `if` or `case` ;; this is way too specific; it's also safe to do this inside an `if` or `case`
(fn in-or? [calls symbol] (fn in-or? [calls symbol]
@ -107,9 +110,10 @@ the `file.diagnostics` field, filling it with diagnostics."
(diagnostic {:range (message.ast->range server file call) (diagnostic {:range (message.ast->range server file call)
:message (.. "unnecessary " (tostring head)) :message (.. "unnecessary " (tostring head))
:severity message.severity.WARN :severity message.severity.WARN
:code :unnecessary-tset} :code :unnecessary-tset
#[{:range (message.ast->range server file call) :fix #{:title "Replace tset with set"
:newText (make-new-text call)}]))) :changes [{:range (message.ast->range server file call)
:newText (make-new-text call)}]}})))
(λ unnecessary-do-values [server file head call] (λ unnecessary-do-values [server file head call]
(if (and (or (sym? head :do) (sym? head :values)) (if (and (or (sym? head :do) (sym? head :values))
@ -117,26 +121,31 @@ the `file.diagnostics` field, filling it with diagnostics."
(diagnostic {:range (message.ast->range server file call) (diagnostic {:range (message.ast->range server file call)
:message (.. "unnecessary " (tostring head)) :message (.. "unnecessary " (tostring head))
:severity message.severity.WARN :severity message.severity.WARN
:code :unnecessary-do-values} :code :unnecessary-do-values
#[{:range (message.ast->range server file call) :fix #{:title "Unwrap the expression"
:newText (view (. call 2))}]))) :changes [{:range (message.ast->range server file call)
:newText (view (. call 2))}]}})))
(local implicit-do-forms (collect [form {: body-form?} (pairs (fennel.syntax))] (local implicit-do-forms (collect [form {: body-form?} (pairs (fennel.syntax))]
(values form body-form?))) (values form body-form?)))
(λ redundant-do [server file head call] (λ redundant-do [server file head call]
(let [last-body (. call (length call))] (let [last-body (. call (length call))]
(if (and (. implicit-do-forms (tostring head)) (. file.lexical call) (if (and (. implicit-do-forms (tostring head))
(list? last-body) (sym? (. last-body 1) :do)) (. file.lexical call)
(list? last-body)
(sym? (. last-body 1) :do)
(not (unnecessary-do-values server file head call))) ;; we don't want two lints to trigger for same call
(diagnostic {:range (message.ast->range server file last-body) (diagnostic {:range (message.ast->range server file last-body)
:message "redundant do" :message "redundant do"
:severity message.severity.WARN :severity message.severity.WARN
:code :redundant-do} :code :redundant-do
#[{:range (message.ast->range server file last-body) :fix #{:title "Unwrap the expression"
:newText (table.concat :changes [{:range (message.ast->range server file last-body)
(fcollect [i 2 (length last-body)] :newText (table.concat
(view (. last-body i))) (fcollect [i 2 (length last-body)]
" ")}])))) (view (. last-body i)))
" ")}]}}))))
(λ bad-unpack [server file op call] (λ bad-unpack [server file op call]
"an unpack call leading into an operator" "an unpack call leading into an operator"
@ -160,12 +169,12 @@ the `file.diagnostics` field, filling it with diagnostics."
(.. " Use a loop when you have a dynamic number of " (.. " Use a loop when you have a dynamic number of "
"arguments to (" (tostring op) ")"))) "arguments to (" (tostring op) ")")))
:severity message.severity.WARN :severity message.severity.WARN
:code :bad-unpack} :code :bad-unpack
(if (and (= (length call) 2) :fix (if (and (= (length last-item) 2)
(= (length (. call 2)) 2) (sym? op ".."))
(sym? op "..")) #{:title "Replace with a call to table.concat"
#[{:range (message.ast->range server file call) :changes [{:range (message.ast->range server file (if (= 2 (length call)) call last-item))
:newText (.. "(table.concat " (view (. call 2 2)) ")")}]))))) :newText (.. "(table.concat " (view (. last-item 2)) ")")}]})}))))
(λ var-never-set [server file symbol definition] (λ var-never-set [server file symbol definition]
(if (and definition.var? (not definition.var-set) (. file.lexical symbol)) (if (and definition.var? (not definition.var-set) (. file.lexical symbol))
@ -189,9 +198,10 @@ the `file.diagnostics` field, filling it with diagnostics."
{:range (message.ast->range server file call) {:range (message.ast->range server file call)
:message (.. "write " (view identity) " instead of (" (tostring op) ")") :message (.. "write " (view identity) " instead of (" (tostring op) ")")
:severity message.severity.WARN :severity message.severity.WARN
:code :op-with-no-arguments} :code :op-with-no-arguments
#[{:range (message.ast->range server file call) :fix #{:title (.. "Replace (" (tostring op) ") with " (view identity))
:newText (view identity)}])))) :changes [{:range (message.ast->range server file call)
:newText (view identity)}]}}))))
(λ no-decreasing-comparison [server file op call] (λ no-decreasing-comparison [server file op call]
(if (or (sym? op :>) (sym? op :>=)) (if (or (sym? op :>) (sym? op :>=))
@ -199,13 +209,14 @@ the `file.diagnostics` field, filling it with diagnostics."
{:range (message.ast->range server file call) {:range (message.ast->range server file call)
:message "Use increasing operator instead of decreasing" :message "Use increasing operator instead of decreasing"
:severity message.severity.WARN :severity message.severity.WARN
:code :no-decreasing-comparison} :code :no-decreasing-comparison
#[{:range (message.ast->range server file call) :fix #{:title "Reverse the comparison"
:newText (let [new (if (sym? op :>=) (fennel.sym :<=) (fennel.sym :<)) :changes [{:range (message.ast->range server file call)
reversed (fcollect [i (length call) 2 -1 :newText (let [new (if (sym? op :>=) (fennel.sym :<=) (fennel.sym :<))
&into (list (sym new))] reversed (fcollect [i (length call) 2 -1
(. call i))] &into (list (sym new))]
(view reversed))}]))) (. call i))]
(view reversed))}]}})))
(λ match-reference? [ast references] (λ match-reference? [ast references]
(if (sym? ast) (?. references ast :target) (if (sym? ast) (?. references ast :target)
@ -221,9 +232,10 @@ the `file.diagnostics` field, filling it with diagnostics."
(diagnostic {:range (message.ast->range server file (. ast 1)) (diagnostic {:range (message.ast->range server file (. ast 1))
:message "no pinned patterns; use case instead of match" :message "no pinned patterns; use case instead of match"
:severity message.severity.WARN :severity message.severity.WARN
:code :match-should-case} :code :match-should-case
#[{:range (message.ast->range server file (. ast 1)) :fix #{:title "Replace match with case"
:newText "case"}]))) :changes [{:range (message.ast->range server file (. ast 1))
:newText "case"}]}})))
(λ multival-in-middle-of-call [server file fun call arg index] (λ multival-in-middle-of-call [server file fun call arg index]
"generally, values and unpack are signs that the user is trying to do "generally, values and unpack are signs that the user is trying to do
@ -252,11 +264,12 @@ the `file.diagnostics` field, filling it with diagnostics."
(diagnostic {:range (message.ast->range server file binding) (diagnostic {:range (message.ast->range server file binding)
:message "use do instead of let with no bindings" :message "use do instead of let with no bindings"
:severity message.severity.WARN :severity message.severity.WARN
:code :empty-let} :code :empty-let
#[(let [{: start} (message.ast->range server file let*) :fix #{:title "Replace (let [] ...) with (do ...)"
{: end} (message.ast->range server file binding)] :changes [(let [{: start} (message.ast->range server file let*)
{:range {: start : end} {: end} (message.ast->range server file binding)]
:newText "do"})]))) {:range {: start : end}
:newText "do"})]}})))
(λ add-lint-diagnostics [server file] (λ add-lint-diagnostics [server file]
"fill up the file.diagnostics table with linting things" "fill up the file.diagnostics table with linting things"

View File

@ -9,6 +9,8 @@ LSP json objects."
(local utils (require :fennel-ls.utils)) (local utils (require :fennel-ls.utils))
(local json (require :dkjson)) (local json (require :dkjson))
(local json-array-mt {:__jsontype :array})
(λ nullify [?value] (λ nullify [?value]
(case ?value (case ?value
nil json.null nil json.null
@ -73,25 +75,18 @@ LSP json objects."
:end (utils.byte->position file.text (+ byteend 1) :end (utils.byte->position file.text (+ byteend 1)
server.position-encoding)})) server.position-encoding)}))
(λ code-action-title [diag] (λ array [?t]
(let [titles {:match-should-case "Replace match with case" (setmetatable (or ?t []) json-array-mt))
:unused-definition "Prefix with _ to silence warning"
:op-with-no-arguments "Replace with the corresponding literal"
:no-decreasing-comparison "Reverse comparison"
:unnecessary-tset "Replace with set"
:unnecessary-do-values "Remove unnecessary do or values"
:redundant-do "Remove redundant do"
:bad-unpack "Replace with table.concat call"}]
(or (. titles diag.code)
(.. "Action title missing - " diag.code))))
(λ diagnostic->code-action [_server file diagnostic ?kind] (λ diagnostic->code-action [_server {: uri} diagnostic ?kind]
(let [{: uri} file] (case-try diagnostic.fix
{:title (code-action-title diagnostic) fix (fix)
:kind ?kind {: title : changes} {: title
:edit {:changes {uri (diagnostic.quickfix)}}})) :kind ?kind
:diagnostics [diagnostic]
:edit {:changes {uri (array changes)}}}))
(λ symbol->signature-help [_server _file _call signature active-parameter] call->signature-help [_server _file _call signature active-parameter]
(let [params-count (length signature.parameters)] (let [params-count (length signature.parameters)]
{:signatures [signature] {:signatures [signature]
:activeSignature 0 ; we only ever have one signature :activeSignature 0 ; we only ever have one signature
@ -101,8 +96,7 @@ LSP json objects."
active-parameter)})) active-parameter)}))
(λ multisym->range [server file ast n] (λ multisym->range [server file ast n]
(let [spl (utils.multi-sym-split ast) (let [spl (utils.multi-sym-split ast)]
n (if (< n 0) (+ n 1 (length spl)) n)]
(case (values (utils.get-ast-info ast :bytestart) (case (values (utils.get-ast-info ast :bytestart)
(utils.get-ast-info ast :byteend)) (utils.get-ast-info ast :byteend))
(bytestart byteend) (bytestart byteend)
@ -138,10 +132,11 @@ LSP json objects."
: create-error : create-error
: ast->range : ast->range
: diagnostic->code-action : diagnostic->code-action
: symbol->signature-help : call->signature-help
: multisym->range : multisym->range
: range-and-uri : range-and-uri
: diagnostics : diagnostics
: severity : severity
: severity->string : severity->string
: show-message} : show-message
: array}

View File

@ -33,32 +33,47 @@
(fn test-fix-op-no-arguments [] (fn test-fix-op-no-arguments []
(check "(let [x (+====)] (check "(let [x (+====)]
(print x))" (print x))"
"Replace with the corresponding literal" "Replace (+) with 0"
"(let [x 0] "(let [x 0]
(print x))")) (print x))")
nil)
(fn test-fix-unused-definition [] (fn test-fix-unused-definition []
(check "(local x==== 10)" (check "(local x==== 10)"
"Prefix with _ to silence warning" "Replace x with _x"
"(local _x 10)")) "(local _x 10)")
nil)
(fn test-unnecessary-tset [] (fn test-unnecessary-tset []
(check "==(tset state :mouse 496)==" (check "==(tset state :mouse 496)=="
"Replace with set" "Replace tset with set"
"(set state.mouse 496)") "(set state.mouse 496)")
(check "==(tset state :mouse :cursor 496)==" (check "==(tset state :mouse :cursor 496)=="
"Replace with set" "Replace tset with set"
"(set state.mouse.cursor 496)") "(set state.mouse.cursor 496)")
(check "==(tset state :mouse :cursor {:x 4 :y 7})==" (check "==(tset state :mouse :cursor {:x 4 :y 7})=="
"Replace with set" "Replace tset with set"
"(set state.mouse.cursor {:x 4 :y 7})") "(set state.mouse.cursor {:x 4 :y 7})")
(check "==(tset state :mouse :cursor :x 496)==" (check "==(tset state :mouse :cursor :x 496)=="
"Replace with set" "Replace tset with set"
"(set state.mouse.cursor.x 496)")) "(set state.mouse.cursor.x 496)")
nil)
(fn test-fix-unpack []
(check "(.. (table.unpack my-ta====ble))"
"Replace with a call to table.concat"
"(table.concat my-table)")
(check "(.. 1 2 3 (table.unpa====ck my-table))"
"Replace with a call to table.concat"
"(.. 1 2 3 (table.concat my-table))")
(check-negative "(+ 1 2 3 (table.unpa====ck my-table))"
"Replace with a call to table.concat")
nil)
{: test-fix-op-no-arguments {: test-fix-op-no-arguments
: test-fix-unused-definition : test-fix-unused-definition
: test-unnecessary-tset} : test-unnecessary-tset
: test-fix-unpack}