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
(analyzer.find-nearest-definition server this-file symbol byte)
{: 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)
:kind documentHighlightKind.Read})]
(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)
byte (utils.position->byte file.text position server.position-encoding)]
(case-try (analyzer.find-nearest-call server file byte)
(symbol active-parameter)
(analyzer.find-definition server file symbol)
(call active-parameter)
(analyzer.find-definition server file call)
{:indeterminate nil &as definition}
(formatter.signature-help-format definition)
signature
(message.symbol->signature-help server file symbol
(message.call->signature-help server file call
signature
active-parameter)
(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}}]
(let [file (files.get-by-uri server uri)]
(icollect [_ diagnostic (ipairs file.diagnostics)]
(if (and (overlap? diagnostic.range range)
diagnostic.quickfix)
(icollect [_ diagnostic (ipairs file.diagnostics) &into (message.array)]
(if (overlap? diagnostic.range range)
(message.diagnostic->code-action server file diagnostic :quickfix)))))
(λ 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))
:__index #(. $1 :self $2)})
(fn diagnostic [self quickfix]
(setmetatable {: self : quickfix} diagnostic-mt))
(fn diagnostic [self]
(let [fix self.fix]
(set self.fix nil)
(setmetatable {: self : fix} diagnostic-mt)))
(fn could-be-rewritten-as-sym? [str]
(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)
:message (.. "unused definition: " (tostring symbol))
:severity message.severity.WARN
:code :unused-definition}
#[{:range (message.ast->range server file symbol)
:newText (.. "_" (tostring symbol))}])))
:code :unused-definition
:fix #{:title (.. "Replace " (tostring symbol) " with _" (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`
(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)
:message (.. "unnecessary " (tostring head))
:severity message.severity.WARN
:code :unnecessary-tset}
#[{:range (message.ast->range server file call)
:newText (make-new-text call)}])))
:code :unnecessary-tset
:fix #{:title "Replace tset with set"
:changes [{:range (message.ast->range server file call)
:newText (make-new-text call)}]}})))
(λ unnecessary-do-values [server file head call]
(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)
:message (.. "unnecessary " (tostring head))
:severity message.severity.WARN
:code :unnecessary-do-values}
#[{:range (message.ast->range server file call)
:newText (view (. call 2))}])))
:code :unnecessary-do-values
:fix #{:title "Unwrap the expression"
:changes [{:range (message.ast->range server file call)
:newText (view (. call 2))}]}})))
(local implicit-do-forms (collect [form {: body-form?} (pairs (fennel.syntax))]
(values form body-form?)))
(λ redundant-do [server file head call]
(let [last-body (. call (length call))]
(if (and (. implicit-do-forms (tostring head)) (. file.lexical call)
(list? last-body) (sym? (. last-body 1) :do))
(if (and (. implicit-do-forms (tostring head))
(. 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)
:message "redundant do"
:severity message.severity.WARN
:code :redundant-do}
#[{:range (message.ast->range server file last-body)
:newText (table.concat
(fcollect [i 2 (length last-body)]
(view (. last-body i)))
" ")}]))))
:code :redundant-do
:fix #{:title "Unwrap the expression"
:changes [{:range (message.ast->range server file last-body)
:newText (table.concat
(fcollect [i 2 (length last-body)]
(view (. last-body i)))
" ")}]}}))))
(λ bad-unpack [server file op call]
"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 "
"arguments to (" (tostring op) ")")))
:severity message.severity.WARN
:code :bad-unpack}
(if (and (= (length call) 2)
(= (length (. call 2)) 2)
(sym? op ".."))
#[{:range (message.ast->range server file call)
:newText (.. "(table.concat " (view (. call 2 2)) ")")}])))))
:code :bad-unpack
:fix (if (and (= (length last-item) 2)
(sym? op ".."))
#{:title "Replace with a call to table.concat"
:changes [{:range (message.ast->range server file (if (= 2 (length call)) call last-item))
:newText (.. "(table.concat " (view (. last-item 2)) ")")}]})}))))
(λ var-never-set [server file symbol definition]
(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)
:message (.. "write " (view identity) " instead of (" (tostring op) ")")
:severity message.severity.WARN
:code :op-with-no-arguments}
#[{:range (message.ast->range server file call)
:newText (view identity)}]))))
:code :op-with-no-arguments
:fix #{:title (.. "Replace (" (tostring op) ") with " (view identity))
:changes [{:range (message.ast->range server file call)
:newText (view identity)}]}}))))
(λ no-decreasing-comparison [server file op call]
(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)
:message "Use increasing operator instead of decreasing"
:severity message.severity.WARN
:code :no-decreasing-comparison}
#[{:range (message.ast->range server file call)
:newText (let [new (if (sym? op :>=) (fennel.sym :<=) (fennel.sym :<))
reversed (fcollect [i (length call) 2 -1
&into (list (sym new))]
(. call i))]
(view reversed))}])))
:code :no-decreasing-comparison
:fix #{:title "Reverse the comparison"
:changes [{:range (message.ast->range server file call)
:newText (let [new (if (sym? op :>=) (fennel.sym :<=) (fennel.sym :<))
reversed (fcollect [i (length call) 2 -1
&into (list (sym new))]
(. call i))]
(view reversed))}]}})))
(λ match-reference? [ast references]
(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))
:message "no pinned patterns; use case instead of match"
:severity message.severity.WARN
:code :match-should-case}
#[{:range (message.ast->range server file (. ast 1))
:newText "case"}])))
:code :match-should-case
:fix #{:title "Replace match with case"
:changes [{:range (message.ast->range server file (. ast 1))
:newText "case"}]}})))
(λ multival-in-middle-of-call [server file fun call arg index]
"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)
:message "use do instead of let with no bindings"
:severity message.severity.WARN
:code :empty-let}
#[(let [{: start} (message.ast->range server file let*)
{: end} (message.ast->range server file binding)]
{:range {: start : end}
:newText "do"})])))
:code :empty-let
:fix #{:title "Replace (let [] ...) with (do ...)"
:changes [(let [{: start} (message.ast->range server file let*)
{: end} (message.ast->range server file binding)]
{:range {: start : end}
:newText "do"})]}})))
(λ add-lint-diagnostics [server file]
"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 json (require :dkjson))
(local json-array-mt {:__jsontype :array})
(λ nullify [?value]
(case ?value
nil json.null
@ -73,25 +75,18 @@ LSP json objects."
:end (utils.byte->position file.text (+ byteend 1)
server.position-encoding)}))
(λ code-action-title [diag]
(let [titles {:match-should-case "Replace match with case"
: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))))
(λ array [?t]
(setmetatable (or ?t []) json-array-mt))
(λ diagnostic->code-action [_server file diagnostic ?kind]
(let [{: uri} file]
{:title (code-action-title diagnostic)
:kind ?kind
:edit {:changes {uri (diagnostic.quickfix)}}}))
(λ diagnostic->code-action [_server {: uri} diagnostic ?kind]
(case-try diagnostic.fix
fix (fix)
{: title : changes} {: title
: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)]
{:signatures [signature]
:activeSignature 0 ; we only ever have one signature
@ -101,8 +96,7 @@ LSP json objects."
active-parameter)}))
(λ multisym->range [server file ast n]
(let [spl (utils.multi-sym-split ast)
n (if (< n 0) (+ n 1 (length spl)) n)]
(let [spl (utils.multi-sym-split ast)]
(case (values (utils.get-ast-info ast :bytestart)
(utils.get-ast-info ast :byteend))
(bytestart byteend)
@ -138,10 +132,11 @@ LSP json objects."
: create-error
: ast->range
: diagnostic->code-action
: symbol->signature-help
: call->signature-help
: multisym->range
: range-and-uri
: diagnostics
: severity
: severity->string
: show-message}
: show-message
: array}

View File

@ -33,32 +33,47 @@
(fn test-fix-op-no-arguments []
(check "(let [x (+====)]
(print x))"
"Replace with the corresponding literal"
"Replace (+) with 0"
"(let [x 0]
(print x))"))
(print x))")
nil)
(fn test-fix-unused-definition []
(check "(local x==== 10)"
"Prefix with _ to silence warning"
"(local _x 10)"))
"Replace x with _x"
"(local _x 10)")
nil)
(fn test-unnecessary-tset []
(check "==(tset state :mouse 496)=="
"Replace with set"
"Replace tset with set"
"(set state.mouse 496)")
(check "==(tset state :mouse :cursor 496)=="
"Replace with set"
"Replace tset with set"
"(set state.mouse.cursor 496)")
(check "==(tset state :mouse :cursor {:x 4 :y 7})=="
"Replace with set"
"Replace tset with set"
"(set state.mouse.cursor {:x 4 :y 7})")
(check "==(tset state :mouse :cursor :x 496)=="
"Replace with set"
"(set state.mouse.cursor.x 496)"))
"Replace tset with set"
"(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-unused-definition
: test-unnecessary-tset}
: test-unnecessary-tset
: test-fix-unpack}