diff --git a/docsrc/warnings.rst b/docsrc/warnings.rst index 5b0a79d5..fe7727b3 100644 --- a/docsrc/warnings.rst +++ b/docsrc/warnings.rst @@ -62,6 +62,7 @@ Code Description 614 Trailing whitespace in a comment. 621 Inconsistent indentation (``SPACE`` followed by ``TAB``). 631 Line is too long. +701 Useless computed key ==== ============================================================================= Global variables (1xx) @@ -294,3 +295,24 @@ Additionally, separate limits can be set for three different type of lines: These types of lines are limited using CLI options named ``--[no-]max-string-line-length``, ``--[no-]max-comment-line-length``, and ``--[no-]max-code-line-length``, with similar config and inline options. + +Style issues (6xx) +----------------------- + +Useless computed key (701) +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +It’s unnecessary to use computed properties with literals such as: + +.. code-block:: lua + :linenos: + + local foo = { + ["a"] = 0, -- bad + } + foo["a"] -- bad + + local foo = { + a = 0, -- good + } + foo.a -- good diff --git a/spec/no_useless_computed_key_spec.lua b/spec/no_useless_computed_key_spec.lua new file mode 100644 index 00000000..2ff564f6 --- /dev/null +++ b/spec/no_useless_computed_key_spec.lua @@ -0,0 +1,53 @@ +local helper = require "spec.helper" + +local function assert_warnings(warnings, src) + assert.same(warnings, helper.get_stage_warnings("detect_useless_computed_key", src)) +end + +describe("useless computed property key detection", function() + it("does not detect anything wrong key is a keyword", function() + assert_warnings({}, [[ +aTable["and"] = 0 +]]) + end) + + it("does not detect anything wrong key is not a string", function() + assert_warnings({}, [[ +aTable[{}] = 0 +]]) + end) + + it("does not detect anything wrong key start with a number", function() + assert_warnings({}, [[ +aTable["1key"] = 0 +]]) + end) + + + it("detects useless computed key in table creation", function() + assert_warnings({ + {code = "701", line = 2, column = 5, end_column = 11, name = "aKey1"}, + }, [[ +local aTable = { + ["aKey1"] = 0 +} +]]) + end) + + it("detects useless computed key when affecting a value", function() + assert_warnings({ + {code = "701", line = 1, column = 8, end_column = 14, name = "aKey2"}, + }, [[ +aTable["aKey2"] = 0 +]]) + end) + + it("detects useless computed key when accessing a value", function() + assert_warnings({ + {code = "701", line = 1, column = 14, end_column = 20, name = "aKey3"}, + }, [[ +print(aTable["aKey3"]) +]]) + end) + +end) diff --git a/spec/samples/useless_computed_key.lua b/spec/samples/useless_computed_key.lua new file mode 100644 index 00000000..5b4547d4 --- /dev/null +++ b/spec/samples/useless_computed_key.lua @@ -0,0 +1,15 @@ +-- warn, could be written simply with "aKey = 0" +local aTable = { + ["aKey"] = 0 +} + +-- warn, could be written simply with "aTable.aKey = 1" +aTable["aKey"] = 1 + +-- no warn, "and" is a keyword +aTable["and"] = 0 + +-- no warn, "1key" is not a valid name for key +aTable["1key"] = 0 + +print(aTable) diff --git a/src/luacheck/stages/detect_useless_computed_key.lua b/src/luacheck/stages/detect_useless_computed_key.lua new file mode 100644 index 00000000..c4090886 --- /dev/null +++ b/src/luacheck/stages/detect_useless_computed_key.lua @@ -0,0 +1,57 @@ +local core_utils = require "luacheck.core_utils" +local utils = require "luacheck.utils" + +local stage = {} + +local function useless_computed_key_message_format() + return "It's unnecessary to use computed properties with literals such as {name!}" +end + +stage.warnings = { + ["701"] = {message_format = useless_computed_key_message_format, + fields = {"name"}} +} + +local function warn_useless_computed_key(chstate, node, symbol) + chstate:warn_range("701", node, { + name = symbol, + }) +end + +local keywords = utils.array_to_set({ + "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "goto", "if", "in", + "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while"}) + +local function check_computed_key(chstate, key_node) + if key_node.tag == "String" then + local symbol = key_node[1] + if (key_node.end_offset - key_node.offset + 1) > #symbol then + if string.gmatch(symbol, "[%a_][%a%w_]*$")() == symbol and not keywords[symbol] then + warn_useless_computed_key(chstate, key_node, symbol) + end + end + end +end + +local function check_nodes(chstate, nodes) + for _, node in ipairs(nodes) do + if type(node) == "table" then + if node.tag == "Pair" then + local key_node = node[1] + check_computed_key(chstate, key_node) + elseif node.tag == "Index" then + local key_node = node[2] + check_computed_key(chstate, key_node) + end + + check_nodes(chstate, node) + end + end +end + +function stage.run(chstate) + print("run") + check_nodes(chstate, chstate.ast) +end + +return stage diff --git a/src/luacheck/stages/init.lua b/src/luacheck/stages/init.lua index d8588785..3459103d 100644 --- a/src/luacheck/stages/init.lua +++ b/src/luacheck/stages/init.lua @@ -26,7 +26,8 @@ stages.names = { "detect_uninit_accesses", "detect_unreachable_code", "detect_unused_fields", - "detect_unused_locals" + "detect_unused_locals", + "detect_useless_computed_key" } stages.modules = {}