From 3cdd0869d9d22fa34f024797c67ca08164b631cb Mon Sep 17 00:00:00 2001
From: Paul Emmerich <tandanu@deadlybossmods.com>
Date: Fri, 2 Feb 2024 23:15:45 +0100
Subject: [PATCH] Add diagnostic for inconsistent string quoting styles

---
 doc/en-us/config.md                           |  1 +
 doc/pt-br/config.md                           |  1 +
 doc/zh-cn/config.md                           |  1 +
 doc/zh-tw/config.md                           |  1 +
 locale/en-us/script.lua                       |  2 +
 locale/en-us/setting.lua                      |  2 +
 .../diagnostics/mismatched-quote-types.lua    | 39 +++++++++++++++++++
 script/proto/diagnostic.lua                   |  3 +-
 test/diagnostics/init.lua                     |  1 +
 test/diagnostics/mismatched-quote-types.lua   | 29 ++++++++++++++
 10 files changed, 79 insertions(+), 1 deletion(-)
 create mode 100644 script/core/diagnostics/mismatched-quote-types.lua
 create mode 100644 test/diagnostics/mismatched-quote-types.lua

diff --git a/doc/en-us/config.md b/doc/en-us/config.md
index f57d4d64e..4543b970e 100644
--- a/doc/en-us/config.md
+++ b/doc/en-us/config.md
@@ -290,6 +290,7 @@ Array<string>
 * ``"luadoc-miss-vararg-type"``
 * ``"luadoc-miss-version"``
 * ``"malformed-number"``
+* ``"mismatched-quote-types"``
 * ``"miss-end"``
 * ``"miss-esc-x"``
 * ``"miss-exp"``
diff --git a/doc/pt-br/config.md b/doc/pt-br/config.md
index 7add2c982..61734c035 100644
--- a/doc/pt-br/config.md
+++ b/doc/pt-br/config.md
@@ -290,6 +290,7 @@ Array<string>
 * ``"luadoc-miss-vararg-type"``
 * ``"luadoc-miss-version"``
 * ``"malformed-number"``
+* ``"mismatched-quote-types"``
 * ``"miss-end"``
 * ``"miss-esc-x"``
 * ``"miss-exp"``
diff --git a/doc/zh-cn/config.md b/doc/zh-cn/config.md
index ebb8325f4..99351e1b2 100644
--- a/doc/zh-cn/config.md
+++ b/doc/zh-cn/config.md
@@ -290,6 +290,7 @@ Array<string>
 * ``"luadoc-miss-vararg-type"``
 * ``"luadoc-miss-version"``
 * ``"malformed-number"``
+* ``"mismatched-quote-types"``
 * ``"miss-end"``
 * ``"miss-esc-x"``
 * ``"miss-exp"``
diff --git a/doc/zh-tw/config.md b/doc/zh-tw/config.md
index 8b01d78cc..ae4f21ddb 100644
--- a/doc/zh-tw/config.md
+++ b/doc/zh-tw/config.md
@@ -290,6 +290,7 @@ Array<string>
 * ``"luadoc-miss-vararg-type"``
 * ``"luadoc-miss-version"``
 * ``"malformed-number"``
+* ``"mismatched-quote-types"``
 * ``"miss-end"``
 * ``"miss-esc-x"``
 * ``"miss-exp"``
diff --git a/locale/en-us/script.lua b/locale/en-us/script.lua
index 20d858bb1..c7e830135 100644
--- a/locale/en-us/script.lua
+++ b/locale/en-us/script.lua
@@ -176,6 +176,8 @@ DIAG_INJECT_FIELD_FIX_CLASS           =
 'To do so, use `---@class` for `{node}`.'
 DIAG_INJECT_FIELD_FIX_TABLE           =
 'To allow injection, add `{fix}` to the definition.'
+DIAG_MISMATCHED_QUOTE_TYPES           =
+'This string uses different quote types than other strings in this file.'
 
 MWS_NOT_SUPPORT         =
 '{} does not support multi workspace for now, I may need to restart to support the new workspace ...'
diff --git a/locale/en-us/setting.lua b/locale/en-us/setting.lua
index 80926d12d..f2798769f 100644
--- a/locale/en-us/setting.lua
+++ b/locale/en-us/setting.lua
@@ -419,6 +419,8 @@ config.diagnostics['unreachable-code']      =
 'Enable diagnostics for unreachable code.'
 config.diagnostics['global-element']       =
 'Enable diagnostics to warn about global elements.'
+config.diagnostics['mismatched-quote-types']=
+'Enable diagnostics for mismatched quote types on string literals within a file.'
 config.typeFormat.config                    =
 'Configures the formatting behavior while typing Lua code.'
 config.typeFormat.config.auto_complete_end  =
diff --git a/script/core/diagnostics/mismatched-quote-types.lua b/script/core/diagnostics/mismatched-quote-types.lua
new file mode 100644
index 000000000..a46693af7
--- /dev/null
+++ b/script/core/diagnostics/mismatched-quote-types.lua
@@ -0,0 +1,39 @@
+local files   = require 'files'
+local guide   = require 'parser.guide'
+local lang    = require 'language'
+
+---@async
+return function (uri, callback)
+    local ast = files.getState(uri)
+    if not ast then
+        return
+    end
+
+    local singleQuotedStrings = {}
+    local doubleQuotedStrings = {}
+    guide.eachSourceType(ast.ast, 'string', function (source)
+        local str = source[1]
+        local quoteType = source[2]
+        if quoteType == "'" and not str:find('"') then
+            singleQuotedStrings[#singleQuotedStrings+1] = source
+        elseif quoteType == '"' and not str:find("'") then
+            doubleQuotedStrings[#doubleQuotedStrings+1] = source
+        end
+    end)
+    if #singleQuotedStrings == 0 or #doubleQuotedStrings == 0 then
+        return
+    end
+    local minorityQuoteTypes
+    if #singleQuotedStrings > #doubleQuotedStrings then
+        minorityQuoteTypes = doubleQuotedStrings
+    else
+        minorityQuoteTypes = singleQuotedStrings
+    end
+    for _, source in ipairs(minorityQuoteTypes) do
+        callback {
+            start   = source.start,
+            finish  = source.finish,
+            message = lang.script.DIAG_MISMATCHED_QUOTE_TYPES,
+        }
+    end
+end
diff --git a/script/proto/diagnostic.lua b/script/proto/diagnostic.lua
index 61b8ff4bd..5840e2eb6 100644
--- a/script/proto/diagnostic.lua
+++ b/script/proto/diagnostic.lua
@@ -113,7 +113,8 @@ m.register {
 }
 
 m.register {
-    'codestyle-check'
+    'codestyle-check',
+	'mismatched-quote-types'
 } {
     group    = 'codestyle',
     severity = 'Warning',
diff --git a/test/diagnostics/init.lua b/test/diagnostics/init.lua
index 99a5dc248..5d1c1b93c 100644
--- a/test/diagnostics/init.lua
+++ b/test/diagnostics/init.lua
@@ -93,6 +93,7 @@ check 'incomplete-signature-doc'
 check 'inject-field'
 check 'invisible'
 check 'lowercase-global'
+check 'mismatched-quote-types'
 check 'missing-fields'
 check 'missing-global-doc'
 check 'missing-local-export-doc'
diff --git a/test/diagnostics/mismatched-quote-types.lua b/test/diagnostics/mismatched-quote-types.lua
new file mode 100644
index 000000000..0b8e43dea
--- /dev/null
+++ b/test/diagnostics/mismatched-quote-types.lua
@@ -0,0 +1,29 @@
+TEST [[
+	local x = 'foo'
+	local y = 'bar'
+	local z = <!"baz"!>
+]]
+
+TEST [[
+	local x = <!'foo'!>
+	local y = "bar"
+	local z = "baz"
+]]
+
+TEST [=[
+	local x = [[foo]]
+	local y = [[bar]]
+	local z = "baz"
+]=]
+
+TEST [[
+	local x = "foo"
+	local y = "bar"
+	local z = 'b"a"z'
+]]
+
+TEST [[
+	local x = 'foo'
+	local y = 'bar'
+	local z = "b'a'z"
+]]