├── .busted
├── .gitignore
├── .luacheckrc
├── .luacov
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── appveyor.yml
├── bin
├── luacheck.bat
└── luacheck.lua
├── build
├── Makefile
└── bin
│ └── luacheck.lua
├── docsrc
├── cli.rst
├── conf.py
├── config.rst
├── index.rst
├── inline.rst
├── module.rst
└── warnings.rst
├── luacheck-dev-1.rockspec
├── scripts
├── build-binaries.sh
├── dedicated_coverage.sh
├── gen_unicode_printability_module.sh
├── package.sh
└── unicode_data_to_printability_module.lua
├── spec
├── bad_whitespace_spec.lua
├── cache_spec.lua
├── check_spec.lua
├── cli_spec.lua
├── config_spec.lua
├── configs
│ ├── bad_config.luacheckrc
│ ├── bad_custom_std_config.luacheckrc
│ ├── cli_override_config.luacheckrc
│ ├── cli_override_file_config.luacheckrc
│ ├── cli_specific_config.luacheckrc
│ ├── config.lua
│ ├── custom_fields_config.luacheckrc
│ ├── custom_stds_config.luacheckrc
│ ├── exclude_files_config.luacheckrc
│ ├── format_opts_config.luacheckrc
│ ├── global_config.luacheckrc
│ ├── import_config.luacheckrc
│ ├── invalid_config.luacheckrc
│ ├── invalid_override_config.luacheckrc
│ ├── multioverride_config.luacheckrc
│ ├── override_config.luacheckrc
│ ├── paths_config.luacheckrc
│ ├── project
│ │ ├── .luacheckrc
│ │ └── nested
│ │ │ ├── ab.lua
│ │ │ └── nested
│ │ │ └── abc.lua
│ └── runtime_bad_config.luacheckrc
├── cyclomatic_complexity_spec.lua
├── decoder_spec.lua
├── empty_blocks_spec.lua
├── expand_rockspec_spec.lua
├── filter_spec.lua
├── folder
│ ├── bad_config
│ ├── bad_rockspec
│ ├── bom
│ ├── config
│ ├── env_config
│ ├── folder1
│ │ ├── another
│ │ ├── fail
│ │ └── file
│ ├── folder2
│ │ └── garbage
│ ├── foo
│ └── rockspec
├── format_spec.lua
├── formatters
│ └── custom_formatter.lua
├── fs_spec.lua
├── globals_spec.lua
├── globbing_spec.lua
├── helper.lua
├── lexer_spec.lua
├── linearize_spec.lua
├── luacheck_spec.lua
├── options_spec.lua
├── parser_spec.lua
├── projects
│ └── default_stds
│ │ ├── .luacheckrc
│ │ ├── default_stds-scm-1.rockspec
│ │ ├── nested
│ │ └── spec
│ │ │ └── sample_spec.lua
│ │ ├── normal_file.lua
│ │ ├── sample_spec.lua
│ │ ├── test
│ │ ├── nested_normal_file.lua
│ │ └── sample_spec.lua
│ │ └── tests
│ │ ├── nested
│ │ └── sample_spec.lua
│ │ └── sample_spec.lua
├── resolve_locals_spec.lua
├── reversed_fornum_loops_spec.lua
├── rock
│ ├── bin
│ │ ├── rock.lua
│ │ └── rock.sh
│ ├── lua_modules
│ │ └── something.lua
│ ├── rock-dev-1.rockspec
│ ├── src
│ │ ├── rock.lua
│ │ └── rock
│ │ │ ├── mod.lua
│ │ │ └── thing.c
│ └── test.lua
├── rock2
│ ├── mod.lua
│ ├── rock2-dev-1.rockspec
│ └── spec
│ │ └── rock2_spec.lua
├── samples
│ ├── argparse-0.2.0.lua
│ ├── bad.rockspec
│ ├── bad_code.lua
│ ├── bad_flow.lua
│ ├── bad_whitespace.lua
│ ├── compat.lua
│ ├── custom_std_inline_options.lua
│ ├── defined.lua
│ ├── defined2.lua
│ ├── defined3.lua
│ ├── defined4.lua
│ ├── empty.lua
│ ├── global_fields.lua
│ ├── global_inline_options.lua
│ ├── globals.lua
│ ├── good_code.lua
│ ├── indirect_globals.lua
│ ├── inline_options.lua
│ ├── line_length.lua
│ ├── python_code.lua
│ ├── read_globals.lua
│ ├── read_globals_inline_options.lua
│ ├── redefined.lua
│ ├── reversed_fornum.lua
│ ├── sample.rockspec
│ ├── unused_code.lua
│ ├── unused_secondaries.lua
│ ├── utf8.lua
│ └── utf8_error.lua
├── serializer_spec.lua
├── standards_spec.lua
├── unbalanced_assignments_spec.lua
├── uninit_accesses_spec.lua
├── unreachable_code_spec.lua
├── unused_fields_spec.lua
├── unused_locals_spec.lua
└── utils_spec.lua
└── src
└── luacheck
├── builtin_standards
├── init.lua
├── love.lua
└── ngx.lua
├── cache.lua
├── check.lua
├── check_state.lua
├── config.lua
├── core_utils.lua
├── decoder.lua
├── expand_rockspec.lua
├── filter.lua
├── format.lua
├── fs.lua
├── globbing.lua
├── init.lua
├── lexer.lua
├── main.lua
├── multithreading.lua
├── options.lua
├── parser.lua
├── profiler.lua
├── runner.lua
├── serializer.lua
├── stages
├── detect_bad_whitespace.lua
├── detect_cyclomatic_complexity.lua
├── detect_empty_blocks.lua
├── detect_empty_statements.lua
├── detect_globals.lua
├── detect_reversed_fornum_loops.lua
├── detect_unbalanced_assignments.lua
├── detect_uninit_accesses.lua
├── detect_unreachable_code.lua
├── detect_unused_fields.lua
├── detect_unused_locals.lua
├── init.lua
├── linearize.lua
├── name_functions.lua
├── parse.lua
├── parse_inline_options.lua
├── resolve_locals.lua
└── unwrap_parens.lua
├── standards.lua
├── unicode.lua
├── unicode_printability_boundaries.lua
├── utils.lua
├── vendor
└── sha1
│ ├── LICENSE
│ ├── bit32_ops.lua
│ ├── bit_ops.lua
│ ├── common.lua
│ ├── init.lua
│ ├── lua53_ops.lua
│ └── pure_lua_ops.lua
└── version.lua
/.busted:
--------------------------------------------------------------------------------
1 | return {
2 | _all = {
3 | ["exclude-pattern"] = "sample_spec"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .luacheckcache
2 | luacov.stats.out
3 | luacov.report.out
4 | doc
5 | !build/
6 | build/*
7 | !build/Makefile
8 | !build/bin
9 | build/bin/*
10 | !build/bin/luacheck.lua
11 | package
12 | scripts/UnicodeData-*
13 | docsrc/.doctrees
14 |
--------------------------------------------------------------------------------
/.luacheckrc:
--------------------------------------------------------------------------------
1 | std = "min"
2 | cache = true
3 | include_files = {"src", "spec/*.lua", "scripts/*.lua", "*.rockspec", "*.luacheckrc"}
4 | exclude_files = {"src/luacheck/vendor"}
5 |
6 | files["src/luacheck/unicode_printability_boundaries.lua"].max_line_length = false
7 |
--------------------------------------------------------------------------------
/.luacov:
--------------------------------------------------------------------------------
1 | return require "spec.helper".luacov_config("")
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | sudo: false
3 |
4 | env:
5 | - LUA="lua=5.1"
6 | - LUA="lua=5.2"
7 | - LUA="lua=5.3"
8 | - LUA="luajit=2.0"
9 | - LUA="luajit=2.1"
10 |
11 | before_install:
12 | - pip install hererocks
13 | - pip install codecov
14 | - hererocks here --$LUA -r latest
15 | - source here/bin/activate
16 | - luarocks install lanes
17 | - luarocks install busted
18 | - luarocks install cluacov
19 | - luarocks install luautf8
20 | - luarocks install luasocket
21 |
22 | install:
23 | - luarocks make
24 |
25 | script:
26 | - busted -c
27 | - lua -e 'package.path="./src/?.lua;./src/?/init.lua;"..package.path' -lluacov bin/luacheck.lua luacheck-dev-1.rockspec -j2
28 | - lua -e 'package.preload.lanes=error;package.path="./src/?.lua;./src/?/init.lua;"..package.path' -lluacov bin/luacheck.lua --version | grep 'Not found'
29 | - lua -e 'package.path="./src/?.lua;./src/?/init.lua;"..package.path' -lluacov bin/luacheck.lua spec/*.lua
30 | - luacheck .
31 | - luacheck .
32 |
33 | after_script:
34 | - luacov
35 | - codecov -f luacov.report.out -X gcov
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 - 2018 Peter Melnichenko
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: 1.0.{build}
2 |
3 | shallow_clone: true
4 |
5 | environment:
6 | matrix:
7 | - LUA: "lua 5.1"
8 | - LUA: "lua 5.2"
9 | - LUA: "lua 5.3"
10 | - LUA: "luajit 2.0"
11 | - LUA: "luajit 2.1"
12 |
13 | before_build:
14 | - set PATH=C:\Python27\Scripts;%PATH%
15 | - pip install hererocks
16 | - pip install codecov
17 | - hererocks here --%LUA% -r latest
18 | - call here\bin\activate
19 | - luarocks install busted
20 | - luarocks install cluacov
21 | - luarocks install luautf8
22 | - luarocks install luasocket
23 |
24 | build_script:
25 | - luarocks make
26 |
27 | test_script:
28 | - busted -c
29 | - luacheck .
30 | - luacheck .
31 |
32 | after_test:
33 | - luacov
34 | - codecov -f luacov.report.out -X gcov
35 |
--------------------------------------------------------------------------------
/bin/luacheck.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | lua.exe "%~dp0\luacheck.lua" %*
3 |
--------------------------------------------------------------------------------
/bin/luacheck.lua:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env lua
2 | require "luacheck.main"
3 |
--------------------------------------------------------------------------------
/build/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for (cross)compiling luacheck binaries.
2 | # Do not use directly, run scripts/build-binaries.sh instead.
3 |
4 | LUA_VERSION= 5.3.5
5 | LFS_VERSION= 1.7.0-2
6 | ARGPARSE_VERSION= 0.6.0-1
7 | LANES_VERSION= 3.10.1-1
8 |
9 | LUA_DIR= lua-$(LUA_VERSION)
10 | LFS_DIR= luafilesystem-$(LFS_VERSION)/luafilesystem
11 | ARGPARSE_DIR= argparse-$(ARGPARSE_VERSION)/argparse
12 | LANES_DIR= lanes-$(LANES_VERSION)/lanes
13 |
14 | BASE_CC= gcc
15 | BASE_AR= ar rc
16 | BASE_RANLIB= ranlib
17 | BASE_STRIP= strip
18 |
19 | CROSS=
20 | CC= $(CROSS)$(BASE_CC)
21 | CFLAGS= -O2 -Wall -Wextra
22 | AR= $(CROSS)$(BASE_AR)
23 | RANLIB= $(CROSS)$(BASE_RANLIB)
24 | STRIP= $(CROSS)$(BASE_STRIP)
25 |
26 | SUFFIX=
27 | TARGET= bin/luacheck$(SUFFIX)
28 |
29 | LUA_O= $(patsubst %.c,%.o,$(filter-out $(addprefix $(LUA_DIR)/src/,lua.c luac.c print.c),$(wildcard $(LUA_DIR)/src/*.c)))
30 | LUA_A= $(LUA_DIR)/src/liblua.a
31 | LFS_O= $(patsubst %.c,%.o,$(wildcard $(LFS_DIR)/src/*.c))
32 | LFS_A= $(LFS_DIR)/src/lfs.a
33 | LANES_O= $(patsubst %.c,%.o,$(wildcard $(LANES_DIR)/src/*.c))
34 | LANES_A= $(LANES_DIR)/src/lanes.a
35 |
36 | default: $(TARGET)
37 |
38 | $(LUA_DIR):
39 | @echo
40 | @echo "=== Downloading Lua $(LUA_VERSION) ==="
41 | @echo
42 | curl "https://www.lua.org/ftp/$(LUA_DIR).tar.gz" | tar xz
43 |
44 | $(LFS_DIR):
45 | @echo
46 | @echo "=== Downloading LuaFileSystem $(LFS_VERSION) ==="
47 | @echo
48 | luarocks unpack luafilesystem $(LFS_VERSION)
49 |
50 | $(ARGPARSE_DIR):
51 | @echo
52 | @echo "=== Downloading argparse $(ARGPARSE_VERSION) ==="
53 | @echo
54 | luarocks unpack argparse $(ARGPARSE_VERSION)
55 |
56 | $(LANES_DIR):
57 | @echo
58 | @echo "=== Downloading Lanes $(LANES_VERSION) ==="
59 | @echo
60 | luarocks unpack lanes $(LANES_VERSION)
61 |
62 | fetch: $(LUA_DIR) $(LFS_DIR) $(ARGPARSE_DIR) $(LANES_DIR)
63 |
64 | $(LUA_O): CFLAGS+= $(if $(LINUX),-DLUA_USE_POSIX)
65 | $(LUA_A): $(LUA_O)
66 | $(LFS_O): CFLAGS+= -I$(LUA_DIR)/src
67 | $(LFS_A): $(LFS_O)
68 | $(LANES_O): CFLAGS+= -I$(LUA_DIR)/src
69 | $(LANES_A): $(LANES_O)
70 |
71 | %.a:
72 | $(AR) $@ $^
73 | $(RANLIB) $@
74 |
75 | bin/luacheck.lua.c: $(LUA_A) $(LFS_A) $(LANES_A)
76 | cp $(LUA_A) .
77 | cp $(LFS_A) .
78 | cp $(ARGPARSE_DIR)/src/argparse.lua .
79 | cp $(LANES_A) .
80 | cp $(LANES_DIR)/src/lanes.lua .
81 | cp -r ../src/luacheck .
82 | CC="" luastatic bin/luacheck.lua luacheck/*.lua luacheck/*/*.lua luacheck/*/*/*.lua argparse.lua lanes.lua liblua.a lfs.a lanes.a
83 |
84 | $(TARGET): bin/luacheck.lua.c
85 | $(CC) $(if $(LINUX),-static) -Os $< $(LUA_A) $(LFS_A) $(LANES_A) -I$(LUA_DIR)/src -lm $(if $(LINUX),-lpthread) -o $@
86 | $(STRIP) $@
87 |
88 | clean:
89 | rm -f $(TARGET) bin/luacheck.lua.c
90 | rm -f $(LUA_O) $(LUA_A) $(LFS_O) $(LFS_A) $(LANES_O) $(LANES_A)
91 | rm -f argparse.lua lanes.lua liblua.a lfs.a lanes.a
92 | rm -rf luacheck
93 |
94 | .PHONY: default fetch clean
95 |
--------------------------------------------------------------------------------
/build/bin/luacheck.lua:
--------------------------------------------------------------------------------
1 | -- Version of bin/luacheck.lua for use in Luacheck binaries.
2 |
3 | -- Do not load modules from filesystem in case a bundled module is broken.
4 | package.path = ""
5 | package.cpath = ""
6 |
7 | require "luacheck.main"
8 |
--------------------------------------------------------------------------------
/docsrc/index.rst:
--------------------------------------------------------------------------------
1 | Luacheck documentation
2 | ======================
3 |
4 | Contents:
5 |
6 | .. toctree::
7 |
8 | warnings
9 | cli
10 | config
11 | inline
12 | module
13 |
14 | This is documentation for 0.23.0 version of `Luacheck `_, a linter for `Lua `_.
15 |
--------------------------------------------------------------------------------
/docsrc/inline.rst:
--------------------------------------------------------------------------------
1 | Inline options
2 | ==============
3 |
4 | Luacheck supports setting some options directly in the checked files using inline configuration comments. These inline options have the highest priority, overwriting both config options and CLI options.
5 |
6 | An inline configuration comment is a short comment starting with ``luacheck:`` label, possibly after some whitespace. The body of the comment should contain comma separated options, where option invocation consists of its name plus space separated arguments. It can also contain notes enclosed in balanced parentheses, which are ignored. The following options are supported:
7 |
8 | ======================= ====================================================================
9 | Option Number of arguments
10 | ======================= ====================================================================
11 | global 0
12 | unused 0
13 | redefined 0
14 | unused args 0
15 | unused secondaries 0
16 | self 0
17 | compat 0
18 | module 0
19 | allow defined 0
20 | allow defined top 0
21 | max line length 1 (with ``no`` and no arguments disables line length checks)
22 | max code line length 1 (with ``no`` and no arguments disables code line length checks)
23 | max string line length 1 (with ``no`` and no arguments disables string line length checks)
24 | max comment line length 1 (with ``no`` and no arguments disables comment line length checks)
25 | std 1
26 | globals 0+
27 | new globals 0+
28 | read globals 0+
29 | new read globals 0+
30 | not globals 0+
31 | ignore 0+ (without arguments everything is ignored)
32 | enable 1+
33 | only 1+
34 | ======================= ====================================================================
35 |
36 | Options that take no arguments can be prefixed with ``no`` to invert their meaning. E.g. ``--luacheck: no unused args`` disables unused argument warnings.
37 |
38 | Part of the file affected by inline option dependes on where it is placed. If there is any code on the line with the option, only that line is affected; otherwise, everything till the end of the current closure is. In particular, inline options at the top of the file affect all of it:
39 |
40 | .. code-block:: lua
41 | :linenos:
42 |
43 | -- luacheck: globals g1 g2, ignore foo
44 | local foo = g1(g2) -- No warnings emitted.
45 |
46 | -- The following unused function is not reported.
47 | local function f() -- luacheck: ignore
48 | -- luacheck: globals g3
49 | g3() -- No warning.
50 | end
51 |
52 | g3() -- Warning is emitted as the inline option defining g3 only affected function f.
53 |
54 | For fine-grained control over inline option visibility use ``luacheck: push`` and ``luacheck: pop`` directives:
55 |
56 | .. code-block:: lua
57 | :linenos:
58 |
59 | -- luacheck: push ignore foo
60 | foo() -- No warning.
61 | -- luacheck: pop
62 | foo() -- Warning is emitted.
63 |
--------------------------------------------------------------------------------
/docsrc/module.rst:
--------------------------------------------------------------------------------
1 | Luacheck module
2 | ===============
3 |
4 | Use ``local luacheck = require "luacheck"`` to import ``luacheck`` module. It contains the following functions:
5 |
6 | * ``luacheck.get_report(source)``: Given source string, returns analysis data (a table).
7 | * ``luacheck.process_reports(reports, options)``: Processes array of analysis reports and applies options. ``reports[i]`` uses ``options``, ``options[i]``, ``options[i][1]``, ``options[i][2]``, ... as options, overriding each other in that order. Options table is a table with fields similar to config options; see :ref:`options`. Analysis reports with field ``fatal`` are ignored. ``process_reports`` returns final report, see :ref:`report`.
8 | * ``luacheck.check_strings(sources, options)``: Checks array of sources using options, returns final report. Tables with field ``fatal`` within ``sources`` array are ignored.
9 | * ``luacheck.check_files(files, options)``: Checks array of files using options, returns final report. Open file handles can passed instead of filenames, in which case they will be read till EOF and closed.
10 | * ``luacheck.get_message(issue)``: Returns a string message for an issue, see :ref:`report`.
11 |
12 | ``luacheck._VERSION`` contains Luacheck version as a string in ``MAJOR.MINOR.PATCH`` format.
13 |
14 | Using ``luacheck`` as a function is equivalent to calling ``luacheck.check_files``.
15 |
16 | .. _report:
17 |
18 | Report format
19 | -------------
20 |
21 | A final report is an array of file reports plus fields ``warnings``, ``errors`` and ``fatals`` containing total number of warnings, errors and fatal errors, correspondingly.
22 |
23 | A file report is an array of issues (warnings or errors). If a fatal error occurred while checking a file, its report will have ``fatal`` field containing error type and ``msg`` field containing error message.
24 |
25 | An issue is a table with field ``code`` indicating its type (see :doc:`warnings`), and fields ``line``, ``column`` and ``end_column`` pointing to the source of the warning. ``name`` field may contain name of related variable. Issues of some types can also have additional fields:
26 |
27 | ============= ==============================================================================================================
28 | Codes Additional fields
29 | ============= ==============================================================================================================
30 | 011 ``msg`` field contains syntax error message.
31 | 111 ``module`` field indicates that assignment is to a non-module global variable.
32 | 122, 142, 143 ``indirect`` field indicates that the global field was accessed using a local alias.
33 | 122, 142, 143 ``field`` field contains string representation of related global field.
34 | 211 ``func`` field indicates that unused variable is a function.
35 | 211 ``recursive`` field indicates that unused function is recursive.
36 | 211 ``mutually_recursive`` field is set for unused mutually recursive functions.
37 | 314 ``field`` field contains string representation of ununsed field or index.
38 | 011 ``prev_line``, ``prev_column``, and ``prev_end_column`` fields may point to an extra relevant location,
39 | such as the opening unpaired bracket.
40 | 4.. ``prev_line``, ``prev_column``, and ``prev_end_column`` fields contain location of the overwritten definition.
41 | 521 ``label`` field contains label name.
42 | 631 ``line_ending`` field contains ``"comment"`` or ``"string"`` if line ending is within a comment or a string.
43 | 631 ``max_length`` field contains maximum allowed line length.
44 | ============= ==============================================================================================================
45 |
46 | Other fields may be present for internal reasons.
47 |
--------------------------------------------------------------------------------
/luacheck-dev-1.rockspec:
--------------------------------------------------------------------------------
1 | package = "luacheck"
2 | version = "dev-1"
3 | source = {
4 | url = "git+https://github.com/mpeterv/luacheck.git"
5 | }
6 | description = {
7 | summary = "A static analyzer and a linter for Lua",
8 | detailed = [[
9 | Luacheck is a command-line tool for linting and static analysis of Lua code.
10 | It is able to spot usage of undefined global variables, unused local variables and
11 | a few other typical problems within Lua programs.
12 | ]],
13 | homepage = "https://github.com/mpeterv/luacheck",
14 | license = "MIT"
15 | }
16 | dependencies = {
17 | "lua >= 5.1, < 5.4",
18 | "argparse >= 0.6.0",
19 | "luafilesystem >= 1.6.3"
20 | }
21 | build = {
22 | type = "builtin",
23 | modules = {
24 | luacheck = "src/luacheck/init.lua",
25 | ["luacheck.builtin_standards"] = "src/luacheck/builtin_standards/init.lua",
26 | ["luacheck.builtin_standards.love"] = "src/luacheck/builtin_standards/love.lua",
27 | ["luacheck.builtin_standards.ngx"] = "src/luacheck/builtin_standards/ngx.lua",
28 | ["luacheck.cache"] = "src/luacheck/cache.lua",
29 | ["luacheck.check"] = "src/luacheck/check.lua",
30 | ["luacheck.check_state"] = "src/luacheck/check_state.lua",
31 | ["luacheck.config"] = "src/luacheck/config.lua",
32 | ["luacheck.core_utils"] = "src/luacheck/core_utils.lua",
33 | ["luacheck.decoder"] = "src/luacheck/decoder.lua",
34 | ["luacheck.expand_rockspec"] = "src/luacheck/expand_rockspec.lua",
35 | ["luacheck.filter"] = "src/luacheck/filter.lua",
36 | ["luacheck.format"] = "src/luacheck/format.lua",
37 | ["luacheck.fs"] = "src/luacheck/fs.lua",
38 | ["luacheck.globbing"] = "src/luacheck/globbing.lua",
39 | ["luacheck.lexer"] = "src/luacheck/lexer.lua",
40 | ["luacheck.main"] = "src/luacheck/main.lua",
41 | ["luacheck.multithreading"] = "src/luacheck/multithreading.lua",
42 | ["luacheck.options"] = "src/luacheck/options.lua",
43 | ["luacheck.parser"] = "src/luacheck/parser.lua",
44 | ["luacheck.profiler"] = "src/luacheck/profiler.lua",
45 | ["luacheck.runner"] = "src/luacheck/runner.lua",
46 | ["luacheck.serializer"] = "src/luacheck/serializer.lua",
47 | ["luacheck.stages"] = "src/luacheck/stages/init.lua",
48 | ["luacheck.stages.detect_bad_whitespace"] = "src/luacheck/stages/detect_bad_whitespace.lua",
49 | ["luacheck.stages.detect_cyclomatic_complexity"] = "src/luacheck/stages/detect_cyclomatic_complexity.lua",
50 | ["luacheck.stages.detect_empty_blocks"] = "src/luacheck/stages/detect_empty_blocks.lua",
51 | ["luacheck.stages.detect_empty_statements"] = "src/luacheck/stages/detect_empty_statements.lua",
52 | ["luacheck.stages.detect_globals"] = "src/luacheck/stages/detect_globals.lua",
53 | ["luacheck.stages.detect_reversed_fornum_loops"] = "src/luacheck/stages/detect_reversed_fornum_loops.lua",
54 | ["luacheck.stages.detect_unbalanced_assignments"] = "src/luacheck/stages/detect_unbalanced_assignments.lua",
55 | ["luacheck.stages.detect_uninit_accesses"] = "src/luacheck/stages/detect_uninit_accesses.lua",
56 | ["luacheck.stages.detect_unreachable_code"] = "src/luacheck/stages/detect_unreachable_code.lua",
57 | ["luacheck.stages.detect_unused_fields"] = "src/luacheck/stages/detect_unused_fields.lua",
58 | ["luacheck.stages.detect_unused_locals"] = "src/luacheck/stages/detect_unused_locals.lua",
59 | ["luacheck.stages.linearize"] = "src/luacheck/stages/linearize.lua",
60 | ["luacheck.stages.name_functions"] = "src/luacheck/stages/name_functions.lua",
61 | ["luacheck.stages.parse"] = "src/luacheck/stages/parse.lua",
62 | ["luacheck.stages.parse_inline_options"] = "src/luacheck/stages/parse_inline_options.lua",
63 | ["luacheck.stages.resolve_locals"] = "src/luacheck/stages/resolve_locals.lua",
64 | ["luacheck.stages.unwrap_parens"] = "src/luacheck/stages/unwrap_parens.lua",
65 | ["luacheck.standards"] = "src/luacheck/standards.lua",
66 | ["luacheck.unicode"] = "src/luacheck/unicode.lua",
67 | ["luacheck.unicode_printability_boundaries"] = "src/luacheck/unicode_printability_boundaries.lua",
68 | ["luacheck.utils"] = "src/luacheck/utils.lua",
69 | ["luacheck.vendor.sha1"] = "src/luacheck/vendor/sha1/init.lua",
70 | ["luacheck.vendor.sha1.bit_ops"] = "src/luacheck/vendor/sha1/bit_ops.lua",
71 | ["luacheck.vendor.sha1.bit32_ops"] = "src/luacheck/vendor/sha1/bit32_ops.lua",
72 | ["luacheck.vendor.sha1.common"] = "src/luacheck/vendor/sha1/common.lua",
73 | ["luacheck.vendor.sha1.lua53_ops"] = "src/luacheck/vendor/sha1/lua53_ops.lua",
74 | ["luacheck.vendor.sha1.pure_lua_ops"] = "src/luacheck/vendor/sha1/pure_lua_ops.lua",
75 | ["luacheck.version"] = "src/luacheck/version.lua"
76 | },
77 | install = {
78 | bin = {
79 | luacheck = "bin/luacheck.lua"
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/scripts/build-binaries.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -eu
3 | set -o pipefail
4 |
5 | # Builds the following binaries:
6 | # * luacheck (Linux x86-64)
7 | # * luacheck32 (Linux x86)
8 | # * luacheck.exe (Windows x86-64)
9 | # * luacheck32.exe (Windows x86)
10 | # Should be executed from root Luacheck directory.
11 | # Resulting binaries will be in `build/bin/`.
12 |
13 | cd build
14 |
15 | make fetch
16 |
17 | function build {
18 | label="$1"
19 | shift
20 |
21 | echo
22 | echo "=== Building Luacheck ($label) ==="
23 | echo
24 |
25 | make clean "$@"
26 | make "-j$(nproc)" "$@"
27 | }
28 |
29 | build "Linux x86-64" LINUX=1
30 | build "Linux x86" LINUX=1 "BASE_CC=gcc -m32" SUFFIX=32
31 | build "Windows x86-64" CROSS=x86_64-w64-mingw32- SUFFIX=.exe
32 | build "Windows x86" CROSS=i686-w64-mingw32- SUFFIX=32.exe
33 |
--------------------------------------------------------------------------------
/scripts/dedicated_coverage.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -eu
3 | set -o pipefail
4 |
5 | # Collects test coverage for luacheck modules with associated spec files.
6 | # Runs spec files from the arguments or all spec files.
7 | # Each module can be covered only from its own spec file.
8 | # Should be executed from root Luacheck directory.
9 |
10 | declare -A spec_to_module
11 | spec_to_module[spec/bad_whitespace_spec.lua]=src/luacheck/stages/detect_bad_whitespace.lua
12 | spec_to_module[spec/cache_spec.lua]=src/luacheck/cache.lua
13 | spec_to_module[spec/check_spec.lua]=src/luacheck/check.lua
14 | spec_to_module[spec/config_spec.lua]=src/luacheck/config.lua
15 | spec_to_module[spec/decoder_spec.lua]=src/luacheck/decoder.lua
16 | spec_to_module[spec/empty_blocks_spec.lua]="src/luacheck/stages/detect_empty_blocks.lua"
17 | spec_to_module[spec/expand_rockspec_spec.lua]=src/luacheck/expand_rockspec.lua
18 | spec_to_module[spec/filter_spec.lua]=src/luacheck/filter.lua
19 | spec_to_module[spec/format_spec.lua]=src/luacheck/format.lua
20 | spec_to_module[spec/fs_spec.lua]=src/luacheck/fs.lua
21 | spec_to_module[spec/globbing_spec.lua]=src/luacheck/globbing.lua
22 | spec_to_module[spec/luacheck_spec.lua]=src/luacheck/init.lua
23 | spec_to_module[spec/lexer_spec.lua]=src/luacheck/lexer.lua
24 | spec_to_module[spec/cli_spec.lua]=src/luacheck/main.lua
25 | spec_to_module[spec/options_spec.lua]=src/luacheck/options.lua
26 | spec_to_module[spec/parser_spec.lua]=src/luacheck/parser.lua
27 | spec_to_module[spec/serializer_spec.lua]=src/luacheck/serializer.lua
28 | spec_to_module[spec/cyclomatic_complexity_spec.lua]=src/luacheck/stages/detect_cyclomatic_complexity.lua
29 | spec_to_module[spec/globals_spec.lua]=src/luacheck/stages/detect_globals.lua
30 | spec_to_module[spec/reversed_fornum_loops_spec.lua]=src/luacheck/stages/detect_reversed_fornum_loops.lua
31 | spec_to_module[spec/unbalanced_assignments_spec.lua]=src/luacheck/stages/detect_unbalanced_assignments.lua
32 | spec_to_module[spec/uninit_accesses_spec.lua]=src/luacheck/stages/detect_uninit_accesses.lua
33 | spec_to_module[spec/unreachable_code_spec.lua]=src/luacheck/stages/detect_unreachable_code.lua
34 | spec_to_module[spec/unused_fields_spec.lua]=src/luacheck/stages/detect_unused_fields.lua
35 | spec_to_module[spec/unused_locals_spec.lua]=src/luacheck/stages/detect_unused_locals.lua
36 | spec_to_module[spec/linearize_spec.lua]=src/luacheck/stages/linearize.lua
37 | spec_to_module[spec/resolve_locals_spec.lua]=src/luacheck/stages/resolve_locals.lua
38 | spec_to_module[spec/standards_spec.lua]=src/luacheck/standards.lua
39 | spec_to_module[spec/utils_spec.lua]=src/luacheck/utils.lua
40 |
41 | if [ $# -eq 0 ]; then
42 | specs="$(sort <<< "${!spec_to_module[@]}")"
43 | else
44 | specs="$@"
45 | fi
46 |
47 | {
48 | echo Spec Module Hits Missed Coverage
49 |
50 | for spec in $specs; do
51 | if [ -v spec_to_module[$spec] ]; then
52 | module="${spec_to_module[$spec]}"
53 |
54 | rm -f luacov.stats.out
55 | rm -f luacov.report.out
56 |
57 | echo "busted -c $spec" >&2
58 | busted -c "$spec" >&2 || true
59 | luacov
60 | echo -n "$spec "
61 | grep -P "$module +[^ ]+ +[^ ]+ +[^ ]+" luacov.report.out || echo "$module 0 0 0.00%"
62 | echo >&2
63 | else
64 | echo "No associated module for spec $spec" >&2
65 | fi
66 | done
67 | } | column -t
68 |
--------------------------------------------------------------------------------
/scripts/gen_unicode_printability_module.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -eu
3 | set -o pipefail
4 |
5 | # Generates luacheck.unicode_printability_boundaries module given Unicode version.
6 | # Should be executed from root Luacheck directory.
7 |
8 | url="https://www.unicode.org/Public/$1/ucd/UnicodeData.txt"
9 | cache="scripts/UnicodeData-$1.txt"
10 |
11 | if [ ! -e "$cache" ]; then
12 | wget -O "$cache" "$url"
13 | fi
14 |
15 | (
16 | echo "-- Autogenerated using data from $url";
17 | lua scripts/unicode_data_to_printability_module.lua < "$cache"
18 | ) > src/luacheck/unicode_printability_boundaries.lua
19 |
--------------------------------------------------------------------------------
/scripts/package.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -eu
3 | set -o pipefail
4 |
5 | # Creates rockspec and source rock for a new Luacheck release given version number.
6 | # Should be executed from root Luacheck directory.
7 | # Resulting rockspec and rock will be in `package/`.
8 |
9 | version="$1"
10 |
11 | rm -rf package
12 | mkdir package
13 | cd package
14 |
15 |
16 | echo
17 | echo "=== Creating rockspec for Luacheck $version ==="
18 | echo
19 |
20 | luarocks new-version ../luacheck-dev-1.rockspec --tag="$version"
21 |
22 | echo
23 | echo "=== Copying Luacheck files ==="
24 | echo
25 |
26 | mkdir luacheck
27 | cp -r ../src luacheck
28 | mkdir luacheck/bin
29 | cp ../bin/luacheck.lua luacheck/bin
30 | cp -r ../doc luacheck
31 | cp ../README.md ../CHANGELOG.md ../LICENSE luacheck
32 |
33 | echo
34 | echo "=== Packing source rock for Luacheck $version ==="
35 | echo
36 |
37 | zip -r luacheck-"$version"-1.src.rock luacheck luacheck-"$version"-1.rockspec
38 |
39 | cd ..
40 |
--------------------------------------------------------------------------------
/scripts/unicode_data_to_printability_module.lua:
--------------------------------------------------------------------------------
1 | -- Reads Unicode character data in UnicodeData.txt format from stdin.
2 | -- Prints a Lua module retuning an array of first codepoints of
3 | -- each continuous block of codepoints that are all printable or all not printable.
4 | -- See https://unicode.org/reports/tr44/
5 |
6 | local category_printabilities = {
7 | Lu = true,
8 | Ll = true,
9 | Lt = true,
10 | Lm = true,
11 | Lo = true,
12 | Mn = true,
13 | Mc = true,
14 | Me = true,
15 | Nd = true,
16 | Nl = true,
17 | No = true,
18 | Pc = true,
19 | Pd = true,
20 | Ps = true,
21 | Pe = true,
22 | Pi = true,
23 | Pf = true,
24 | Po = true,
25 | Sm = true,
26 | Sc = true,
27 | Sk = true,
28 | So = true,
29 | Zs = true,
30 | Zl = false,
31 | Zp = false,
32 | Cc = false,
33 | Cf = false,
34 | Cs = false,
35 | Co = false,
36 | Cn = false
37 | }
38 |
39 | local codepoint_printabilities = {}
40 | local max_codepoint = 0
41 |
42 | local range_start_codepoint
43 |
44 | for line in io.lines() do
45 | local codepoint_hex, name, category = assert(line:match("^([^;]+);([^;]+);([^;]+)"))
46 | local codepoint = assert(tonumber("0x" .. codepoint_hex))
47 | local printability = category_printabilities[category]
48 | assert(printability ~= nil)
49 |
50 | if name:find(", First>$") then
51 | assert(not range_start_codepoint)
52 | range_start_codepoint = codepoint
53 | elseif name:find(", Last>$") then
54 | assert(range_start_codepoint and range_start_codepoint >= range_start_codepoint)
55 |
56 | for range_codepoint = range_start_codepoint, codepoint do
57 | codepoint_printabilities[range_codepoint] = printability
58 | end
59 |
60 | range_start_codepoint = nil
61 | else
62 | codepoint_printabilities[codepoint] = printability
63 | end
64 |
65 | max_codepoint = math.max(max_codepoint, codepoint)
66 | end
67 |
68 | assert(not range_start_codepoint)
69 |
70 | local parts = {"return {"}
71 | local prev_printability = true
72 |
73 | -- Iterate up to a non-existent codepoint to ensure that the last required codepoint is printed.
74 | for codepoint = 0, max_codepoint + 1 do
75 | local printability = codepoint_printabilities[codepoint] or false
76 |
77 | if printability ~= prev_printability then
78 | table.insert(parts, ("%d,"):format(codepoint))
79 | end
80 |
81 | prev_printability = printability
82 | end
83 |
84 | table.insert(parts, "}")
85 | print(table.concat(parts))
86 |
87 |
--------------------------------------------------------------------------------
/spec/bad_whitespace_spec.lua:
--------------------------------------------------------------------------------
1 | local helper = require "spec.helper"
2 |
3 | local function assert_warnings(warnings, src)
4 | assert.same(warnings, helper.get_stage_warnings("detect_bad_whitespace", src))
5 | end
6 |
7 | describe("bad whitespace detection", function()
8 | it("detects lines with only whitespace", function()
9 | assert_warnings({
10 | {code = "611", line = 1, column = 1, end_column = 4},
11 | {code = "611", line = 3, column = 1, end_column = 1}
12 | }, " \n--[[\n \n]]\n")
13 | end)
14 |
15 | it("detects trailing whitespace with different warnings code depending on line ending type", function()
16 | assert_warnings({
17 | {code = "612", line = 1, column = 8, end_column = 9},
18 | {code = "613", line = 2, column = 13, end_column = 13},
19 | {code = "612", line = 3, column = 8, end_column = 8},
20 | {code = "614", line = 4, column = 11, end_column = 14}
21 | }, "local a \nlocal b = [[ \nthing]] \nlocal c --\t\t\t\t\nlocal d\n")
22 | end)
23 |
24 | it("detects spaces followed by tabs", function()
25 | assert_warnings({
26 | {code = "621", line = 1, column = 1, end_column = 5}
27 | }, " \t \tlocal foo\n\t\t local bar\n")
28 | end)
29 |
30 | it("does not warn on spaces followed by tabs if the line has only whitespace", function()
31 | assert_warnings({
32 | {code = "611", line = 1, column = 1, end_column = 7}
33 | }, " \t \t \n")
34 | end)
35 |
36 | it("can detect both trailing whitespace and inconsistent indentation on the same line", function()
37 | assert_warnings({
38 | {code = "621", line = 1, column = 1, end_column = 2},
39 | {code = "612", line = 1, column = 10, end_column = 10}
40 | }, " \tlocal a \n")
41 | end)
42 |
43 | it("handles lack of trailing newline", function()
44 | assert_warnings({
45 | {code = "611", line = 2, column = 1, end_column = 5}
46 | }, "local a\n ")
47 |
48 | assert_warnings({
49 | {code = "612", line = 2, column = 8, end_column = 12}
50 | }, "local a\nlocal b ")
51 |
52 | assert_warnings({
53 | {code = "621", line = 1, column = 1, end_column = 2},
54 | {code = "614", line = 1, column = 13, end_column = 16}
55 | }, " \tlocal a -- ")
56 | end)
57 |
58 | it("provides correct column ranges in presence of two-byte line endings", function()
59 | assert_warnings({
60 | {code = "612", line = 1, column = 10, end_column = 13},
61 | {code = "621", line = 2, column = 1, end_column = 4},
62 | {code = "611", line = 3, column = 1, end_column = 3}
63 | }, "local foo \r\n \tlocal bar\n\r ")
64 | end)
65 |
66 | it("provides correct column ranges in presence of utf8", function()
67 | assert_warnings({
68 | {code = "612", line = 1, column = 17, end_column = 20},
69 | {code = "611", line = 2, column = 1, end_column = 4},
70 | {code = "621", line = 3, column = 1, end_column = 4},
71 | {code = "614", line = 3, column = 20, end_column = 24},
72 | }, "local foo = '\204\128\204\130' \n \n \tlocal bar -- \240\144\128\128\224\166\152 \n")
73 | end)
74 | end)
75 |
--------------------------------------------------------------------------------
/spec/cache_spec.lua:
--------------------------------------------------------------------------------
1 | local cache = require "luacheck.cache"
2 | local fs = require "luacheck.fs"
3 | local lfs = require "lfs"
4 | local sha1 = require "luacheck.vendor.sha1"
5 |
6 | setup(function()
7 |
8 | end)
9 |
10 | describe("cache", function()
11 | describe("get_default_dir", function()
12 | it("returns a string", function()
13 | assert.is_string(cache.get_default_dir())
14 | end)
15 | end)
16 |
17 | describe("new", function()
18 | it("returns nil, error message on failure to init cache", function()
19 | local c, err = cache.new("LICENSE")
20 | assert.is_nil(c)
21 | assert.is_string(err)
22 | end)
23 |
24 | it("returns Cache object on success", function()
25 | local c = cache.new("src")
26 | assert.is_table(c)
27 | end)
28 | end)
29 |
30 | describe("Cache", function()
31 | local filename = "spec/caches/file.lua"
32 | local normalized_filename = fs.normalize(fs.join(fs.get_current_dir(), filename))
33 | local cache_dir = "spec/caches"
34 | local cache_filename = fs.join(cache_dir, sha1.sha1(normalized_filename))
35 |
36 | local c
37 |
38 | before_each(function()
39 | c = cache.new(cache_dir)
40 | assert.is_table(c)
41 | end)
42 |
43 | after_each(function()
44 | os.remove(filename)
45 |
46 | if lfs.attributes(cache_filename, "mode") == "directory" then
47 | lfs.rmdir(cache_filename)
48 | else
49 | os.remove(cache_filename)
50 | end
51 | end)
52 |
53 | local function make_report(code)
54 | return {
55 | warnings = {
56 | code and {code = code}
57 | },
58 | inline_options = {},
59 | line_lengths = {}
60 | }
61 | end
62 |
63 | describe("put", function()
64 | it("returns nil on failure to store cache", function()
65 | lfs.mkdir(cache_filename)
66 | local ok = c:put(filename, make_report())
67 | assert.is_nil(ok)
68 | end)
69 |
70 | it("returns true on successfull cache store", function()
71 | local ok = c:put(filename, make_report())
72 | assert.is_true(ok)
73 | end)
74 | end)
75 |
76 | describe("get", function()
77 | it("returns nil on cache miss", function()
78 | local report, err = c:get(filename)
79 | assert.is_nil(report)
80 | assert.is_nil(err)
81 | end)
82 |
83 | it("returns nil on outdated cache", function()
84 | assert.is_true(c:put(filename, make_report()))
85 | io.open(filename, "w"):close()
86 | assert.is_true(lfs.touch(filename, os.time() + 100000))
87 | local report, err = c:get(filename)
88 | assert.is_nil(report)
89 | assert.is_nil(err)
90 | end)
91 |
92 | it("returns report on success", function()
93 | local original_report = make_report("111")
94 | assert.is_true(c:put(filename, original_report))
95 | io.open(filename, "w"):close()
96 | assert.is_true(lfs.touch(filename, os.time() - 100000))
97 | local cached_report = c:get(filename)
98 | assert.same(original_report, cached_report)
99 | end)
100 | end)
101 | end)
102 | end)
103 |
--------------------------------------------------------------------------------
/spec/configs/bad_config.luacheckrc:
--------------------------------------------------------------------------------
1 | -- let's talk about ruby
2 | def method_missing(*args); args.join(" "); end
3 | -- wat
4 |
--------------------------------------------------------------------------------
/spec/configs/bad_custom_std_config.luacheckrc:
--------------------------------------------------------------------------------
1 | stds.my_std = {
2 | read_globals = {
3 | foo = {
4 | fields = {
5 | 1, 2, 3
6 | }
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/spec/configs/cli_override_config.luacheckrc:
--------------------------------------------------------------------------------
1 | global = true
2 | globals = {"print", "setfenv", "rawlen"}
3 |
--------------------------------------------------------------------------------
/spec/configs/cli_override_file_config.luacheckrc:
--------------------------------------------------------------------------------
1 | files["spec/samples/compat.lua"] = {
2 | global = true,
3 | globals = {"print", "setfenv", "rawlen"}
4 | }
5 |
--------------------------------------------------------------------------------
/spec/configs/cli_specific_config.luacheckrc:
--------------------------------------------------------------------------------
1 | color = false
2 | codes = true
3 | formatter = function(report, file_names, options)
4 | return ([[
5 | Files: %d
6 | Warnings: %d
7 | Errors: %d
8 | Quiet: %d
9 | Color: %s
10 | Codes: %s]]):format(#file_names, report.warnings, report.errors, options.quiet,
11 | options.color and "true" or "false", options.codes and "true" or "false")
12 | end
13 |
--------------------------------------------------------------------------------
/spec/configs/config.lua:
--------------------------------------------------------------------------------
1 | return {
2 | std = "lua52"
3 | }
4 |
--------------------------------------------------------------------------------
/spec/configs/custom_fields_config.luacheckrc:
--------------------------------------------------------------------------------
1 | stds = {
2 | my_server = {
3 | read_globals = {
4 | server = {
5 | fields = {
6 | bar = {read_only = false},
7 | baz = {},
8 | sessions = {read_only = false, other_fields = true}
9 | }
10 | }
11 | }
12 | }
13 | }
14 |
15 | read_globals = {
16 | server = {
17 | fields = {
18 | bar = {},
19 | baz = {read_only = false},
20 | sessions = {read_only = false, other_fields = true}
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/spec/configs/custom_stds_config.luacheckrc:
--------------------------------------------------------------------------------
1 | stds = {
2 | my_std = {read_globals = {"print", "setfenv"}},
3 | other_std = {read_globals = {"tostring", "setfenv"}}
4 | }
5 |
6 | std = "my_std"
7 |
--------------------------------------------------------------------------------
/spec/configs/exclude_files_config.luacheckrc:
--------------------------------------------------------------------------------
1 | exclude_files = {"spec/samples/defined?.lua", "**/bad*.lua"}
2 | std = "min"
3 |
--------------------------------------------------------------------------------
/spec/configs/format_opts_config.luacheckrc:
--------------------------------------------------------------------------------
1 | quiet = 1
2 | ranges = true
3 | codes = true
4 |
--------------------------------------------------------------------------------
/spec/configs/global_config.luacheckrc:
--------------------------------------------------------------------------------
1 | globals = {"print", "setfenv", "rawlen"}
2 |
--------------------------------------------------------------------------------
/spec/configs/import_config.luacheckrc:
--------------------------------------------------------------------------------
1 | codes = true
2 | std = "lua51"
3 |
4 | return require "spec.configs.config"
5 |
--------------------------------------------------------------------------------
/spec/configs/invalid_config.luacheckrc:
--------------------------------------------------------------------------------
1 | ignore = "211"
2 |
--------------------------------------------------------------------------------
/spec/configs/invalid_override_config.luacheckrc:
--------------------------------------------------------------------------------
1 | ignore = {"211"}
2 |
3 | files["spec/foo.lua"].enable = "211"
4 |
--------------------------------------------------------------------------------
/spec/configs/multioverride_config.luacheckrc:
--------------------------------------------------------------------------------
1 | unused = false
2 |
3 | files["spec/samples/"] = {
4 | unused_args = true,
5 | enable = {"31"},
6 | ignore = {"213"}
7 | }
8 |
9 | files["spec/samples/*_code.lua"] = {
10 | enable = {"213"}
11 | }
12 |
--------------------------------------------------------------------------------
/spec/configs/override_config.luacheckrc:
--------------------------------------------------------------------------------
1 | files["spec/samples/bad_code.lua"] = {
2 | redefined = false,
3 | compat = true
4 | }
5 |
6 | files["spec/samples/unused_code.lua"].unused = false
7 |
--------------------------------------------------------------------------------
/spec/configs/paths_config.luacheckrc:
--------------------------------------------------------------------------------
1 | cache = "something.luacheckcache"
2 | formatter = "helper.fmt"
3 | include_files = {"foo", "bar"}
4 | exclude_files = {"foo/thing"}
5 |
--------------------------------------------------------------------------------
/spec/configs/project/.luacheckrc:
--------------------------------------------------------------------------------
1 | files["nested/ab.lua"].ignore = {"a"}
2 | files["nested/nested/"].enable = {"a"}
3 | files["nested/nested/"].ignore = {"b"}
4 |
--------------------------------------------------------------------------------
/spec/configs/project/nested/ab.lua:
--------------------------------------------------------------------------------
1 | print(a, b)
2 |
--------------------------------------------------------------------------------
/spec/configs/project/nested/nested/abc.lua:
--------------------------------------------------------------------------------
1 | print(a, b, c)
2 |
--------------------------------------------------------------------------------
/spec/configs/runtime_bad_config.luacheckrc:
--------------------------------------------------------------------------------
1 | (nil)()
2 |
--------------------------------------------------------------------------------
/spec/cyclomatic_complexity_spec.lua:
--------------------------------------------------------------------------------
1 | local helper = require "spec.helper"
2 |
3 | local function assert_warnings(warnings, src)
4 | assert.same(warnings, helper.get_stage_warnings("detect_cyclomatic_complexity", src))
5 | end
6 |
7 | describe("cyclomatic complexity detection", function()
8 | it("reports 1 for empty main chunk", function()
9 | assert_warnings({
10 | {code = "561", line = 1, column = 1, end_column = 1, complexity = 1, function_type = "main_chunk"}
11 | }, "")
12 | end)
13 |
14 | it("reports 1 for functions with no branches", function()
15 | assert_warnings({
16 | {code = "561", line = 1, column = 1, end_column = 1, complexity = 1, function_type = "main_chunk"}
17 | }, [[
18 | print(1)
19 |
20 | do
21 | print(2)
22 | end
23 |
24 | return 3
25 | ]])
26 | end)
27 |
28 | it("reports 2 for functions with a single if branch", function()
29 | assert_warnings({
30 | {code = "561", line = 1, column = 1, end_column = 1, complexity = 2, function_type = "main_chunk"}
31 | }, [[
32 | print(1)
33 |
34 | if ... then
35 | print(2)
36 | end
37 |
38 | print(3)
39 | ]])
40 |
41 | assert_warnings({
42 | {code = "561", line = 1, column = 1, end_column = 1, complexity = 2, function_type = "main_chunk"}
43 | }, [[
44 | print(1)
45 |
46 | if ... then
47 | print(2)
48 | else
49 | print(3)
50 | end
51 | ]])
52 | end)
53 |
54 | it("reports 2 for functions with a single loop", function()
55 | assert_warnings({
56 | {code = "561", line = 1, column = 1, end_column = 1, complexity = 2, function_type = "main_chunk"}
57 | }, [[
58 | print(1)
59 |
60 | for i = 1, 10 do
61 | print(2)
62 | end
63 |
64 | print(3)
65 | ]])
66 |
67 | assert_warnings({
68 | {code = "561", line = 1, column = 1, end_column = 1, complexity = 2, function_type = "main_chunk"}
69 | }, [[
70 | print(1)
71 |
72 | for k, v in pairs(t) do
73 | print(2)
74 | end
75 |
76 | print(3)
77 | ]])
78 |
79 | assert_warnings({
80 | {code = "561", line = 1, column = 1, end_column = 1, complexity = 2, function_type = "main_chunk"}
81 | }, [[
82 | print(1)
83 |
84 | while cond() do
85 | print(2)
86 | end
87 |
88 | print(3)
89 | ]])
90 |
91 | assert_warnings({
92 | {code = "561", line = 1, column = 1, end_column = 1, complexity = 2, function_type = "main_chunk"}
93 | }, [[
94 | print(1)
95 |
96 | repeat
97 | print(2)
98 | until cond()
99 |
100 | print(3)
101 | ]])
102 | end)
103 |
104 | it("reports 2 for functions with a single boolean operator", function()
105 | assert_warnings({
106 | {code = "561", line = 1, column = 1, end_column = 1, complexity = 2, function_type = "main_chunk"}
107 | }, [[
108 | print(a and b)
109 | ]])
110 |
111 | assert_warnings({
112 | {code = "561", line = 1, column = 1, end_column = 1, complexity = 2, function_type = "main_chunk"}
113 | }, [[
114 | print(a or b)
115 | ]])
116 | end)
117 |
118 | it("provides appropriate names and types for functions", function()
119 | assert_warnings({
120 | {code = "561", line = 1, column = 1, end_column = 1, complexity = 1, function_type = "main_chunk"},
121 | {code = "561", line = 1, column = 8, end_column = 17, complexity = 1,function_type = "function"},
122 | {code = "561", line = 2, column = 14, end_column = 27, complexity = 1, function_type = "function",
123 | function_name = "f"},
124 | {code = "561", line = 3, column = 8, end_column = 21, complexity = 1, function_type = "function",
125 | function_name = "g"},
126 | {code = "561", line = 4, column = 10, end_column = 25, complexity = 1, function_type = "function",
127 | function_name = "h"},
128 | {code = "561", line = 5, column = 25, end_column = 38, complexity = 1, function_type = "function",
129 | function_name = "t.k"},
130 | {code = "561", line = 6, column = 26, end_column = 39, complexity = 1, function_type = "function",
131 | function_name = "t.k1.k2.k3.k4"},
132 | {code = "561", line = 7, column = 11, end_column = 24, complexity = 1, function_type = "function"},
133 | {code = "561", line = 8, column = 6, end_column = 19, complexity = 1, function_type = "function"},
134 | {code = "561", line = 9, column = 4, end_column = 27, complexity = 1, function_type = "method",
135 | function_name = "t.foo.bar"}
136 | }, [[
137 | return function()
138 | local f = function() end
139 | g = function() end
140 | local function h() end
141 | local a, t = 1, {k = function() end}
142 | t.k1.k2 = {k3 = {k4 = function() end}}
143 | t[1] = function() end
144 | t[function() end] = 1
145 | function t.foo:bar() end
146 | end
147 | ]])
148 | end)
149 |
150 | it("reports correct complexity in complex cases", function()
151 | assert_warnings({
152 | {code = "561", line = 1, column = 1, end_column = 1, complexity = 8, function_type = "main_chunk"}
153 | }, [[
154 | if month == 1 then
155 | return 31
156 | elseif month == 2 then
157 | if year % 4 == 0 then
158 | return 29
159 | end
160 |
161 | return 28
162 | elseif (month <= 7 and month % 2 == 1) or (month >= 8 and month % 2 == 0) then
163 | return 31
164 | else
165 | return 30
166 | end
167 | ]])
168 |
169 | assert_warnings({
170 | {code = "561", line = 1, column = 1, end_column = 1, complexity = 4, function_type = "main_chunk"}
171 | }, [[
172 | local i, j = 0, 0
173 | local total = 0
174 | while to > 0 and i < to do
175 | while j < to do
176 | j = j + 1
177 | total = total + 1
178 | end
179 |
180 | i = i + 1
181 | end
182 |
183 | return total
184 | ]])
185 |
186 | assert_warnings({
187 | {code = "561", line = 1, column = 1, end_column = 1, complexity = 4, function_type = "main_chunk"}
188 | }, [[
189 | local i, j = 0, 0
190 | local total = 0
191 |
192 | repeat
193 | repeat
194 | j = j + 1
195 | total = total + 1
196 | until j >= to
197 |
198 | i = i + 1
199 | until i >= to or to <= 0
200 |
201 | return total
202 | ]])
203 |
204 | assert_warnings({
205 | {code = "561", line = 1, column = 1, end_column = 1, complexity = 7, function_type = "main_chunk"}
206 | }, [[
207 | for k1 in t and pairs(t) or pairs({}) do
208 | for k2 in pairs(t) do
209 | if k1 and k2 then
210 | return k1 + k2
211 | end
212 | end
213 | end
214 | ]])
215 |
216 | assert_warnings({
217 | {code = "561", line = 1, column = 1, end_column = 1, complexity = 6, function_type = "main_chunk"}
218 | }, [[
219 | for i = 1, t > 10 and 10 or t do
220 | for j = 1, t do
221 | if i + j == i * j then
222 | return i
223 | end
224 | end
225 | end
226 | ]])
227 |
228 | assert_warnings({
229 | {code = "561", line = 1, column = 1, end_column = 1, complexity = 5, function_type = "main_chunk"}
230 | }, [[
231 | local v1 = v and v*3 or 4
232 | local t = {v1 == 3 and v*v or v/3}
233 | return t
234 | ]])
235 | end)
236 | end)
237 |
--------------------------------------------------------------------------------
/spec/decoder_spec.lua:
--------------------------------------------------------------------------------
1 | local decoder = require "luacheck.decoder"
2 | local lua_utf8 = require "lua-utf8"
3 |
4 | local function assert_encoding(encoding, ...)
5 | local lib = encoding == "utf8" and lua_utf8 or string
6 | local length = select("#", ...)
7 | local bytes = lib.char(...)
8 | local chars = decoder.decode(bytes)
9 |
10 | local label_parts = {"("}
11 |
12 | for index = 1, length do
13 | table.insert(label_parts, ("\\u{%X}"):format((select(index, ...))))
14 | end
15 |
16 | table.insert(label_parts, ")")
17 | local label = table.concat(label_parts)
18 |
19 | assert.equals(length, chars:get_length(), ":get_length" .. label)
20 |
21 | for from = 1, length do
22 | for to = from, length do
23 | assert.equals(lib.sub(bytes, from, to), chars:get_substring(from, to), ":get_substring" .. label)
24 | end
25 | end
26 |
27 | local iter, state, var
28 |
29 | if encoding == "utf8" then
30 | iter, state = lua_utf8.next, bytes
31 | else
32 | iter, state, var = ipairs({...})
33 | end
34 |
35 | local index = 1
36 |
37 | for offset, codepoint in iter, state, var do
38 | assert.equals(codepoint, chars:get_codepoint(index), ":get_codepoint" .. label)
39 |
40 | local from, to, match = chars:find("(.)", index)
41 | assert.equals(offset, from, ":find" .. label)
42 | assert.equals(offset, to, ":find" .. label)
43 | assert.equals(bytes:sub(offset, offset), match, ":find" .. label)
44 | index = index + 1
45 | end
46 | end
47 |
48 | describe("decoder", function()
49 | it("decodes valid codepoints correctly", function()
50 | -- Checking literally all codepoints is very slow with coverage enabled, pick only a few.
51 | for base = 0, 0x10FFFF, 0x800 do
52 | for offset = 0, 0x100, 41 do
53 | local codepoint1 = base + offset
54 | local codepoint2 = codepoint1 + 9
55 | assert_encoding("utf8", codepoint1, codepoint2)
56 | end
57 | end
58 | end)
59 |
60 | it("falls back to latin1 on invalid utf8", function()
61 | -- Bad first byte.
62 | assert_encoding("latin1", 0xC0, 0x80, 0x80, 0x80)
63 | assert_encoding("latin1", 0x00, 0xF8, 0x80, 0x80, 0x80)
64 |
65 | -- Two bytes, bad continuation byte.
66 | assert_encoding("latin1", 0x00, 0xC0, 0x00, 0xC0, 0x80)
67 | assert_encoding("latin1", 0x00, 0xC0, 0xFF, 0xC0, 0x80)
68 |
69 | -- Three bytes, bad first continuation byte.
70 | assert_encoding("latin1", 0x00, 0xE0, 0x00, 0xC0, 0x80)
71 | assert_encoding("latin1", 0x00, 0xE0, 0xFF, 0xC0, 0x80)
72 |
73 | -- Three bytes, bad second continuation byte.
74 | assert_encoding("latin1", 0x00, 0xE0, 0x80, 0x00, 0xC0, 0x80)
75 | assert_encoding("latin1", 0x00, 0xE0, 0x80, 0xFF, 0xC0, 0x80)
76 |
77 | -- Four bytes, bad first continuation byte.
78 | assert_encoding("latin1", 0x00, 0xF0, 0x00, 0xC0, 0x80)
79 | assert_encoding("latin1", 0x00, 0xF0, 0xFF, 0xC0, 0x80)
80 |
81 | -- Four bytes, bad second continuation byte.
82 | assert_encoding("latin1", 0x00, 0xF0, 0x80, 0x00, 0xC0, 0x80)
83 | assert_encoding("latin1", 0x00, 0xF0, 0x80, 0xFF, 0xC0, 0x80)
84 |
85 | -- Four bytes, bad third continuation byte.
86 | assert_encoding("latin1", 0x00, 0xF0, 0x80, 0x80, 0x00, 0xC0, 0x80)
87 | assert_encoding("latin1", 0x00, 0xF0, 0x80, 0x80, 0xFF, 0xC0, 0x80)
88 |
89 | -- Codepoint too large.
90 | assert_encoding("latin1", 0xF7, 0x80, 0x80, 0x80, 0x00)
91 | end)
92 | end)
93 |
--------------------------------------------------------------------------------
/spec/empty_blocks_spec.lua:
--------------------------------------------------------------------------------
1 | local helper = require "spec.helper"
2 |
3 | local function assert_warnings(warnings, src)
4 | assert.same(warnings, helper.get_stage_warnings("detect_empty_blocks", src))
5 | end
6 |
7 | describe("empty block detection", function()
8 | it("detects empty blocks", function()
9 | assert_warnings({
10 | {code = "541", line = 1, column = 1, end_column = 6},
11 | {code = "542", line = 3, column = 8, end_column = 11},
12 | {code = "542", line = 5, column = 12, end_column = 15},
13 | {code = "542", line = 7, column = 1, end_column = 4}
14 | }, [[
15 | do end
16 |
17 | if ... then
18 |
19 | elseif ... then
20 |
21 | else
22 |
23 | end
24 |
25 | if ... then
26 | somehing()
27 | else
28 | something_else()
29 | end
30 |
31 | do something() end
32 |
33 | while ... do end
34 | repeat until ...
35 | ]])
36 | end)
37 |
38 | it("detects empty blocks in nested blocks and functions", function()
39 | assert_warnings({
40 | {code = "541", line = 4, column = 10, end_column = 15},
41 | {code = "541", line = 7, column = 13, end_column = 18},
42 | {code = "541", line = 12, column = 22, end_column = 27},
43 | {code = "542", line = 14, column = 27, end_column = 30}
44 | }, [[
45 | do
46 | while x do
47 | if y then
48 | do end
49 | else
50 | repeat
51 | do end
52 |
53 | function t()
54 | for i = 1, 10 do
55 | for _, v in ipairs(tab) do
56 | do end
57 |
58 | if c then end
59 | end
60 | end
61 | end
62 | until z
63 | end
64 | end
65 | end
66 | ]])
67 | end)
68 | end)
69 |
--------------------------------------------------------------------------------
/spec/expand_rockspec_spec.lua:
--------------------------------------------------------------------------------
1 | local expand_rockspec = require "luacheck.expand_rockspec"
2 | local fs = require "luacheck.fs"
3 | local P = fs.normalize
4 |
5 | describe("expand_rockspec", function()
6 | it("returns sorted array of lua files related to a rock", function()
7 | assert.same({
8 | "bar.lua",
9 | "baz.lua",
10 | "bin.lua",
11 | "foo.lua"
12 | }, expand_rockspec("spec/folder/rockspec"))
13 | end)
14 |
15 | it("autodetects modules for rockspecs without build table", function()
16 | assert.same({
17 | P"spec/rock/src/rock.lua",
18 | P"spec/rock/src/rock/mod.lua",
19 | P"spec/rock/bin/rock.lua"
20 | }, expand_rockspec("spec/rock/rock-dev-1.rockspec"))
21 | end)
22 |
23 | it("autodetects modules for rockspecs without build.modules table", function()
24 | assert.same({
25 | P"spec/rock2/mod.lua"
26 | }, expand_rockspec("spec/rock2/rock2-dev-1.rockspec"))
27 | end)
28 |
29 | it("returns nil, \"I/O\" for non-existent paths", function()
30 | local ok, err = expand_rockspec("spec/folder/non-existent")
31 | assert.is_nil(ok)
32 | assert.equal("I/O", err)
33 | end)
34 |
35 | it("returns nil, \"syntax\" for rockspecs with syntax errors", function()
36 | local ok, err = expand_rockspec("spec/folder/bad_config")
37 | assert.is_nil(ok)
38 | assert.equal("syntax", err)
39 | end)
40 |
41 | it("returns nil, \"runtime\" for rockspecs with run-time errors", function()
42 | local ok, err = expand_rockspec("spec/folder/bad_rockspec")
43 | assert.is_nil(ok)
44 | assert.equal("runtime", err)
45 | end)
46 | end)
47 |
--------------------------------------------------------------------------------
/spec/folder/bad_config:
--------------------------------------------------------------------------------
1 | foo = `bar`
2 |
--------------------------------------------------------------------------------
/spec/folder/bad_rockspec:
--------------------------------------------------------------------------------
1 | build "builtin"
2 |
--------------------------------------------------------------------------------
/spec/folder/bom:
--------------------------------------------------------------------------------
1 | foo
2 | bar
3 |
--------------------------------------------------------------------------------
/spec/folder/config:
--------------------------------------------------------------------------------
1 | foo = "bar"
2 |
--------------------------------------------------------------------------------
/spec/folder/env_config:
--------------------------------------------------------------------------------
1 | foo = bar()
2 |
--------------------------------------------------------------------------------
/spec/folder/folder1/another:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpeterv/luacheck/7360cfb4cf2c7dd8c73adf45e31a04811a745250/spec/folder/folder1/another
--------------------------------------------------------------------------------
/spec/folder/folder1/fail:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpeterv/luacheck/7360cfb4cf2c7dd8c73adf45e31a04811a745250/spec/folder/folder1/fail
--------------------------------------------------------------------------------
/spec/folder/folder1/file:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpeterv/luacheck/7360cfb4cf2c7dd8c73adf45e31a04811a745250/spec/folder/folder1/file
--------------------------------------------------------------------------------
/spec/folder/folder2/garbage:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpeterv/luacheck/7360cfb4cf2c7dd8c73adf45e31a04811a745250/spec/folder/folder2/garbage
--------------------------------------------------------------------------------
/spec/folder/foo:
--------------------------------------------------------------------------------
1 | contents
2 |
--------------------------------------------------------------------------------
/spec/folder/rockspec:
--------------------------------------------------------------------------------
1 | build = {
2 | type = "builtin",
3 | modules = {
4 | foo = "foo.lua",
5 | bar = "bar.lua",
6 | qu = "qu.c"
7 | },
8 | install = {
9 | lua = {
10 | baz = "baz.lua"
11 | },
12 | bin = {
13 | bin = "bin.lua"
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/spec/format_spec.lua:
--------------------------------------------------------------------------------
1 | local format = require "luacheck.format".format
2 |
3 | local function mark_colors(s)
4 | return (s:gsub("\27%[%d+m", "\27"):gsub("\27+", "#"))
5 | end
6 |
7 | describe("format", function()
8 | it("returns formatted report", function()
9 | assert.equal([[Checking stdin 1 warning
10 |
11 | stdin:2:7: unused global variable 'foo'
12 |
13 | Checking foo.lua 1 warning
14 |
15 | foo.lua:2:7: empty statement
16 |
17 | Checking bar.lua OK
18 | Checking baz.lua 1 error
19 |
20 | baz.lua:4:3: something went wrong
21 |
22 | Total: 2 warnings / 1 error in 4 files]], format({
23 | warnings = 2,
24 | errors = 1,
25 | fatals = 0,
26 | {
27 | {
28 | code = "131",
29 | name = "foo",
30 | line = 2,
31 | column = 7
32 | }
33 | },
34 | {
35 | {
36 | code = "551",
37 | line = 2,
38 | column = 7
39 | }
40 | },
41 | {},
42 | {
43 | {
44 | code = "011",
45 | line = 4,
46 | column = 3,
47 | msg = "something went wrong"
48 | }
49 | }
50 | }, {"stdin", "foo.lua", "bar.lua", "baz.lua"}, {color = false}))
51 | end)
52 |
53 | it("does not output OK messages with options.quiet >= 1", function()
54 | assert.equal([[Checking stdin 1 warning
55 |
56 | stdin:2:7: unused global variable 'foo'
57 |
58 | Checking foo.lua 1 warning / 1 error
59 |
60 | foo.lua:2:7: unused global variable 'foo'
61 | foo.lua:3:10: bad, bad inline option
62 |
63 | Checking baz.lua Syntax error
64 |
65 | baz.lua: error message
66 |
67 | Total: 2 warnings / 1 error in 3 files, couldn't check 1 file]], format({
68 | warnings = 2,
69 | errors = 1,
70 | fatals = 1,
71 | {
72 | {
73 | code = "131",
74 | name = "foo",
75 | line = 2,
76 | column = 7
77 | }
78 | },
79 | {
80 | {
81 | code = "131",
82 | name = "foo",
83 | line = 2,
84 | column = 7
85 | },
86 | {
87 | code = "021",
88 | msg = "bad, bad inline option",
89 | line = 3,
90 | column = 10
91 | }
92 | },
93 | {},
94 | {
95 | fatal = "syntax",
96 | msg = "error message"
97 | }
98 | }, {"stdin", "foo.lua", "bar.lua", "baz.lua"}, {quiet = 1, color = false}))
99 | end)
100 |
101 | it("does not output warnings with options.quiet >= 2", function()
102 | assert.equal([[Checking stdin 1 warning
103 | Checking foo.lua 1 warning
104 | Checking baz.lua Syntax error
105 |
106 | Total: 2 warnings / 0 errors in 3 files, couldn't check 1 file]], format({
107 | warnings = 2,
108 | errors = 0,
109 | fatals = 1,
110 | {
111 | {
112 | code = "131",
113 | name = "foo",
114 | line = 2,
115 | column = 7
116 | }
117 | },
118 | {
119 | {
120 | code = "131",
121 | name = "foo",
122 | line = 2,
123 | column = 7
124 | }
125 | },
126 | {},
127 | {
128 | fatal = "syntax"
129 | }
130 | }, {"stdin", "foo.lua", "bar.lua", "baz.lua"}, {quiet = 2, color = false}))
131 | end)
132 |
133 | it("does not output file info with options.quiet == 3", function()
134 | assert.equal("Total: 2 warnings / 0 errors in 3 files, couldn't check 1 file", format({
135 | warnings = 2,
136 | errors = 0,
137 | fatals = 1,
138 | {
139 | {
140 | code = "131",
141 | name = "foo",
142 | line = 2,
143 | column = 7
144 | }
145 | },
146 | {
147 | {
148 | code = "131",
149 | name = "foo",
150 | line = 2,
151 | column = 7
152 | }
153 | },
154 | {},
155 | {
156 | fatal = "syntax"
157 | }
158 | }, {"stdin", "foo.lua", "bar.lua", "baz.lua"}, {quiet = 3, color = false}))
159 | end)
160 |
161 | it("colors output by default", function()
162 | if package.config:sub(1, 1) == "\\" and not os.getenv("ANSICON") then
163 | pending("uses terminal colors")
164 | end
165 |
166 | assert.equal([[Checking stdin #1 warning#
167 |
168 | stdin:2:7: unused global variable #foo#
169 |
170 | Checking foo.lua #1 warning#
171 |
172 | foo.lua:2:7: unused global variable #foo#
173 |
174 | Checking bar.lua #OK#
175 | Checking baz.lua #Syntax error#
176 |
177 | baz.lua: error message
178 |
179 | Total: #2# warnings / #0# errors in 3 files, couldn't check 1 file]], mark_colors(format({
180 | warnings = 2,
181 | errors = 0,
182 | fatals = 1,
183 | {
184 | {
185 | code = "131",
186 | name = "foo",
187 | line = 2,
188 | column = 7
189 | }
190 | },
191 | {
192 | {
193 | code = "131",
194 | name = "foo",
195 | line = 2,
196 | column = 7
197 | }
198 | },
199 | {},
200 | {
201 | fatal = "syntax",
202 | msg = "error message"
203 | }
204 | }, {"stdin", "foo.lua", "bar.lua", "baz.lua"}, {})))
205 | end)
206 | end)
207 |
--------------------------------------------------------------------------------
/spec/formatters/custom_formatter.lua:
--------------------------------------------------------------------------------
1 | return function(report, file_names, options)
2 | return ([[
3 | Files: %d
4 | Formatter: %s
5 | Quiet: %d
6 | Color: %s
7 | Codes: %s]]):format(#file_names, options.formatter, options.quiet,
8 | tostring(options.color), tostring(options.codes))
9 | end
10 |
--------------------------------------------------------------------------------
/spec/fs_spec.lua:
--------------------------------------------------------------------------------
1 | local fs = require "luacheck.fs"
2 | local utils = require "luacheck.utils"
3 | local P = fs.normalize
4 |
5 | describe("fs", function()
6 | describe("is_dir", function()
7 | it("returns true for directories", function()
8 | assert.is_true(fs.is_dir("spec/folder"))
9 | end)
10 |
11 | it("returns false for files", function()
12 | assert.is_false(fs.is_dir("spec/folder/foo"))
13 | end)
14 |
15 | it("returns false for non-existent paths", function()
16 | assert.is_false(fs.is_dir("spec/folder/non-existent"))
17 | end)
18 | end)
19 |
20 | describe("is_file", function()
21 | it("returns true for files", function()
22 | assert.is_true(fs.is_file("spec/folder/foo"))
23 | end)
24 |
25 | it("returns false for directories", function()
26 | assert.is_false(fs.is_file("spec/folder"))
27 | end)
28 |
29 | it("returns false for non-existent paths", function()
30 | assert.is_false(fs.is_file("spec/folder/non-existent"))
31 | end)
32 | end)
33 |
34 | describe("extract_files", function()
35 | it("returns sorted list of files in a directory matching pattern", function()
36 | assert.same({
37 | P"spec/folder/folder1/fail",
38 | P"spec/folder/folder1/file",
39 | P"spec/folder/foo"
40 | }, fs.extract_files(P"spec/folder", "^f"))
41 | end)
42 | end)
43 |
44 | describe("get_mtime", function()
45 | it("returns modification time as a number", function()
46 | assert.number(fs.get_mtime("spec/folder/foo"))
47 | end)
48 |
49 | it("returns nil for non-existent files", function()
50 | assert.is_nil(fs.get_mtime("spec/folder/non-existent"))
51 | end)
52 | end)
53 |
54 | describe("get_current_dir", function()
55 | it("returns absolute path to current directory with trailing directory separator", function()
56 | local current_dir = fs.get_current_dir()
57 | assert.string(current_dir)
58 | assert.matches(utils.dir_sep .. "$", current_dir)
59 | assert.not_equal("", (fs.split_base(current_dir)))
60 | assert.is_true(fs.is_file(current_dir .. "spec/folder/foo"))
61 | end)
62 | end)
63 |
64 | describe("find_file", function()
65 | it("finds file in a directory", function()
66 | local path = P(fs.get_current_dir() .. "spec/folder")
67 | assert.equal(path, fs.find_file(path, "foo"))
68 | end)
69 |
70 | it("finds file in a parent directory", function()
71 | local path = P(fs.get_current_dir() .. "spec/folder")
72 | assert.equal(path, fs.find_file(fs.join(path, "folder1"), "foo"))
73 | end)
74 |
75 | it("returns nil if can't find file", function()
76 | assert.is_nil(
77 | fs.find_file(fs.get_current_dir(), "this file shouldn't exist or it will make luacheck testsuite break"))
78 | end)
79 | end)
80 | end)
81 |
--------------------------------------------------------------------------------
/spec/globals_spec.lua:
--------------------------------------------------------------------------------
1 | local helper = require "spec.helper"
2 |
3 | local function assert_warnings(warnings, src)
4 | assert.same(warnings, helper.get_stage_warnings("detect_globals", src))
5 | end
6 |
7 | describe("global detection", function()
8 | it("detects global set", function()
9 | assert_warnings({
10 | {code = "111", name = "foo", line = 1, column = 1, end_column = 3, top = true}
11 | }, [[
12 | foo = {}
13 | ]])
14 | end)
15 |
16 | it("detects global set in nested functions", function()
17 | assert_warnings({
18 | {code = "111", name = "foo", line = 2, column = 4, end_column = 6}
19 | }, [[
20 | local function bar()
21 | foo = {}
22 | end
23 | bar()
24 | ]])
25 | end)
26 |
27 | it("detects global access in multi-assignments", function()
28 | assert_warnings({
29 | {code = "111", name = "y", line = 2, column = 4, end_column = 4, top = true},
30 | {code = "113", name = "print", line = 3, column = 1, end_column = 5}
31 | }, [[
32 | local x
33 | x, y = 1
34 | print(x)
35 | ]])
36 | end)
37 |
38 | it("detects global access in self swap", function()
39 | assert_warnings({
40 | {code = "113", name = "a", line = 1, column = 11, end_column = 11},
41 | {code = "113", name = "print", line = 2, column = 1, end_column = 5}
42 | }, [[
43 | local a = a
44 | print(a)
45 | ]])
46 | end)
47 |
48 | it("detects global mutation", function()
49 | assert_warnings({
50 | {code = "112", name = "a", indexing = {false}, line = 1, column = 1, end_column = 1}
51 | }, [[
52 | a[1] = 6
53 | ]])
54 | end)
55 |
56 | it("detects indirect global field access", function()
57 | assert_warnings({
58 | {
59 | code = "113",
60 | name = "b",
61 | indexing = {false},
62 | line = 2,
63 | column = 15,
64 | end_column = 15
65 | }, {
66 | code = "113",
67 | name = "b",
68 | indexing = {false, false, "foo"},
69 | previous_indexing_len = 2,
70 | line = 3,
71 | column = 8,
72 | end_column = 12,
73 | indirect = true
74 | }
75 | }, [[
76 | local c = "foo"
77 | local alias = b[1]
78 | return alias[2][c]
79 | ]])
80 | end)
81 |
82 | it("detects indirect global field mutation", function()
83 | assert_warnings({
84 | {
85 | code = "113",
86 | name = "b",
87 | indexing = {false},
88 | line = 2,
89 | column = 15,
90 | end_column = 15
91 | }, {
92 | code = "112",
93 | name = "b",
94 | indexing = {false, false, "foo"},
95 | previous_indexing_len = 2,
96 | line = 3,
97 | column = 1,
98 | end_column = 5,
99 | indirect = true
100 | }
101 | }, [[
102 | local c = "foo"
103 | local alias = b[1]
104 | alias[2][c] = c
105 | ]])
106 | end)
107 |
108 | it("provides indexing information for warnings related to global fields", function()
109 | assert_warnings({
110 | {
111 | code = "113",
112 | name = "global",
113 | line = 2,
114 | column = 11,
115 | end_column = 16
116 | }, {
117 | code = "113",
118 | name = "global",
119 | indexing = {"foo", "bar", false},
120 | indirect = true,
121 | previous_indexing_len = 1,
122 | line = 3,
123 | column = 15,
124 | end_column = 15
125 | }, {
126 | code = "113",
127 | name = "global",
128 | indexing = {"foo", "bar", false, true},
129 | indirect = true,
130 | previous_indexing_len = 4,
131 | line = 5,
132 | column = 8,
133 | end_column = 13
134 | }
135 | }, [[
136 | local c = "foo"
137 | local g = global
138 | local alias = g[c].bar[1]
139 | local alias2 = alias
140 | return alias2[...]
141 | ]])
142 | end)
143 | end)
144 |
--------------------------------------------------------------------------------
/spec/globbing_spec.lua:
--------------------------------------------------------------------------------
1 | local globbing = require "luacheck.globbing"
2 | local fs = require "luacheck.fs"
3 |
4 | local cur_dir = fs.get_current_dir()
5 |
6 | local function check_match(expected_result, glob, path)
7 | glob = fs.normalize(fs.join(cur_dir, glob))
8 | path = fs.normalize(fs.join(cur_dir, path))
9 | assert.equal(expected_result, globbing.match(glob, path))
10 | end
11 |
12 | describe("globbing", function()
13 | describe("match", function()
14 | it("returns true on literal match", function()
15 | check_match(true, "foo/bar", "foo/bar")
16 | end)
17 |
18 | it("returns true on literal match after normalization", function()
19 | check_match(true, "foo//bar/baz/..", "./foo/bar/")
20 | end)
21 |
22 | it("returns false for on literal mismatch", function()
23 | check_match(false, "foo/bar", "foo/baz")
24 | end)
25 |
26 | it("accepts subdirectory matches", function()
27 | check_match(true, "foo/bar", "foo/bar/baz")
28 | end)
29 |
30 | it("understands wildcards", function()
31 | check_match(true, "*", "foo")
32 | check_match(true, "foo/*r", "foo/bar")
33 | check_match(true, "foo/*r", "foo/bar/baz")
34 | check_match(false, "foo/*r", "foo/baz")
35 | end)
36 |
37 | it("understands optional characters", function()
38 | check_match(false, "?", "foo")
39 | check_match(true, "???", "foo")
40 | check_match(true, "????", "foo")
41 | check_match(true, "f?o/?a?", "foo/bar")
42 | check_match(false, "f?o/?a?", "foo/abc")
43 | end)
44 |
45 | it("understands ranges and classes", function()
46 | check_match(true, "[d-h]o[something]", "foo")
47 | check_match(false, "[d-h]o[somewhere]", "bar")
48 | check_match(false, "[.-h]o[i-z]", "bar")
49 | end)
50 |
51 | it("accepts closing bracket as first class character", function()
52 | check_match(true, "[]]", "]")
53 | check_match(false, "[]]", "[")
54 | check_match(true, "[]foo][]foo][]foo]", "foo")
55 | end)
56 |
57 | it("accepts dash as first or last class character", function()
58 | check_match(true, "[-]", "-")
59 | check_match(false, "[-]", "+")
60 | check_match(true, "[---]", "-")
61 | end)
62 |
63 | it("understands negation", function()
64 | check_match(true, "[!foo][!bar][!baz]", "boo")
65 | check_match(false, "[!foo][!bar][!baz]", "far")
66 | check_match(false, "[!a-z]", "g")
67 | end)
68 |
69 | it("understands recursive globbing using **", function()
70 | check_match(true, "**/*.lua", "foo.lua")
71 | check_match(true, "**/*.lua", "foo/bar.lua")
72 | check_match(false, "foo/**/*.lua", "bar.lua")
73 | check_match(false, "foo/**/*.lua", "foo.lua")
74 | check_match(true, "foo/**/bar/*.lua", "foo/bar/baz.lua")
75 | check_match(true, "foo/**/bar/*.lua", "foo/foo2/foo3/bar/baz.lua")
76 | check_match(false, "foo/**/bar/*.lua", "foo/baz.lua")
77 | check_match(false, "foo/**/bar/*.lua", "bar/baz.lua")
78 | end)
79 | end)
80 | end)
81 |
--------------------------------------------------------------------------------
/spec/helper.lua:
--------------------------------------------------------------------------------
1 | local helper = {}
2 |
3 | local function get_lua()
4 | local index = -1
5 | local res = "lua"
6 |
7 | while arg[index] do
8 | res = arg[index]
9 | index = index - 1
10 | end
11 |
12 | return res
13 | end
14 |
15 | local dir_sep = package.config:sub(1, 1)
16 |
17 | -- Return path to root directory when run from `path`.
18 | local function antipath(path)
19 | local _, level = path:gsub("[/\\]", "")
20 | return (".."..dir_sep):rep(level)
21 | end
22 |
23 | function helper.luacov_config(prefix)
24 | return {
25 | statsfile = prefix.."luacov.stats.out",
26 | modules = {
27 | luacheck = "src/luacheck/init.lua",
28 | ["luacheck.*"] = "src",
29 | ["luacheck.*.*"] = "src",
30 | ["luacheck.*.*.*"] = "src"
31 | },
32 | exclude = {
33 | "bin/luacheck$"
34 | }
35 | }
36 | end
37 |
38 | local luacov = package.loaded["luacov.runner"]
39 | local lua
40 |
41 | -- Returns command that runs `luacheck` executable from `loc_path`.
42 | function helper.luacheck_command(loc_path)
43 | lua = lua or get_lua()
44 | loc_path = loc_path or "."
45 | local prefix = antipath(loc_path)
46 | local cmd = ("cd %s && %s"):format(loc_path, lua)
47 |
48 | -- Extend package.path to allow loading this helper and luacheck modules.
49 | cmd = cmd..(' -e "package.path=[[%s?.lua;%ssrc%s?.lua;%ssrc%s?%sinit.lua;]]..package.path"'):format(
50 | prefix, prefix, dir_sep, prefix, dir_sep, dir_sep)
51 |
52 | if luacov then
53 | -- Launch luacov.
54 | cmd = cmd..(' -e "require[[luacov.runner]](require[[spec.helper]].luacov_config([[%s]]))"'):format(prefix)
55 | end
56 |
57 | return ("%s %sbin%sluacheck.lua"):format(cmd, prefix, dir_sep)
58 | end
59 |
60 | function helper.get_chstate_after_stage(target_stage_name, source)
61 | -- Luacov isn't yet started when helper is required, defer requiring luacheck
62 | -- modules so that their main chunks get covered.
63 | local check_state = require "luacheck.check_state"
64 | local stages = require "luacheck.stages"
65 |
66 | local chstate = check_state.new(source)
67 |
68 | for index, stage_name in ipairs(stages.names) do
69 | stages.modules[index].run(chstate)
70 |
71 | if stage_name == target_stage_name then
72 | return chstate
73 | end
74 |
75 | chstate.warnings = {}
76 | end
77 |
78 | error("no stage " .. target_stage_name, 0)
79 | end
80 |
81 | function helper.get_stage_warnings(target_stage_name, source)
82 | local core_utils = require "luacheck.core_utils"
83 |
84 | local chstate = helper.get_chstate_after_stage(target_stage_name, source)
85 | core_utils.sort_by_location(chstate.warnings)
86 | return chstate.warnings
87 | end
88 |
89 | return helper
90 |
--------------------------------------------------------------------------------
/spec/projects/default_stds/.luacheckrc:
--------------------------------------------------------------------------------
1 | std = "min"
2 |
3 | files["**/test/**/*_spec.lua"] = {
4 | std = "none"
5 | }
6 |
7 | local shared_options = {ignore = {"ignored"}}
8 |
9 | files["**/spec/**/*_spec.lua"] = shared_options
10 | files["normal_file.lua"] = shared_options
11 |
12 | local function sink() end
13 |
14 | sink(it, version, math, newproxy)
15 |
--------------------------------------------------------------------------------
/spec/projects/default_stds/default_stds-scm-1.rockspec:
--------------------------------------------------------------------------------
1 | package = "default_stds"
2 | version = "scm-1"
3 | source = {
4 | url = "https://example.com"
5 | }
6 | description = {
7 | summary = "example",
8 | detailed = "example",
9 | homepage = "https://example.com",
10 | license = "MIT"
11 | }
12 | dependencies = {}
13 | it("is a rockspec")(newproxy, math, new_globals)
14 |
--------------------------------------------------------------------------------
/spec/projects/default_stds/nested/spec/sample_spec.lua:
--------------------------------------------------------------------------------
1 | it("is a test in a nested directory")(newproxy, math, version, read_globals)
2 | ignored()
3 |
--------------------------------------------------------------------------------
/spec/projects/default_stds/normal_file.lua:
--------------------------------------------------------------------------------
1 | it("is just a normal file")(newproxy, math, version, read_globals)
2 | ignored()
3 |
--------------------------------------------------------------------------------
/spec/projects/default_stds/sample_spec.lua:
--------------------------------------------------------------------------------
1 | it("is not really a test")(newproxy, math, version, read_globals)
2 |
--------------------------------------------------------------------------------
/spec/projects/default_stds/test/nested_normal_file.lua:
--------------------------------------------------------------------------------
1 | it("is a normal file in a nested directory")(newproxy, math, version, read_globals)
2 |
--------------------------------------------------------------------------------
/spec/projects/default_stds/test/sample_spec.lua:
--------------------------------------------------------------------------------
1 | it("is a test in a test directory")(newproxy, math, version, read_globals)
2 |
--------------------------------------------------------------------------------
/spec/projects/default_stds/tests/nested/sample_spec.lua:
--------------------------------------------------------------------------------
1 | it("is a test in a very nested directory")(newproxy, math, version, read_globals)
2 |
--------------------------------------------------------------------------------
/spec/projects/default_stds/tests/sample_spec.lua:
--------------------------------------------------------------------------------
1 | it("is a test")(newproxy, math, version, read_globals)
2 |
--------------------------------------------------------------------------------
/spec/resolve_locals_spec.lua:
--------------------------------------------------------------------------------
1 | local helper = require "spec.helper"
2 |
3 | local function used_variables_to_string(chstate, item)
4 | local buf = {}
5 |
6 | for var, values in pairs(item.used_values) do
7 | local values_buf = {}
8 |
9 | for _, value in ipairs(values) do
10 | table.insert(values_buf, ("%d:%d"):format(
11 | value.var_node.line, chstate:offset_to_column(value.var_node.line, value.var_node.offset)))
12 | end
13 |
14 | table.insert(buf, var.name .. " = (" .. table.concat(values_buf, ", ") .. ")")
15 | end
16 |
17 | table.sort(buf)
18 | return item.tag .. ": " .. table.concat(buf, "; ")
19 | end
20 |
21 | local function get_used_variables_as_string(src)
22 | local chstate = helper.get_chstate_after_stage("resolve_locals", src)
23 |
24 | local buf = {}
25 |
26 | for _, item in ipairs(chstate.top_line.items) do
27 | if item.accesses and next(item.accesses) then
28 | assert.is_table(item.used_values)
29 | table.insert(buf, used_variables_to_string(chstate, item))
30 | end
31 | end
32 |
33 | return table.concat(buf, "\n")
34 | end
35 |
36 | describe("resolve_locals", function()
37 | describe("when resolving values", function()
38 | it("resolves values in linear cases", function()
39 | assert.equal([[
40 | Eval: a = (1:7)]], get_used_variables_as_string([[
41 | local a = 6
42 | print(a)
43 | ]]))
44 | end)
45 |
46 | it("resolves values after ifs", function()
47 | assert.equal([[
48 | Eval: a = (1:7, 4:4)]], get_used_variables_as_string([[
49 | local a
50 |
51 | if expr then
52 | a = 5
53 | end
54 |
55 | print(a)
56 | ]]))
57 |
58 | assert.equal([[
59 | Eval: a = (4:4, 7:4, 10:7, 13:4)]], get_used_variables_as_string([[
60 | local a = 3
61 |
62 | if expr then
63 | a = 4
64 | elseif expr then
65 | a = 5
66 | a = 8
67 |
68 | if expr then
69 | a = 7
70 | end
71 | else
72 | a = 6
73 | end
74 |
75 | print(a)
76 | ]]))
77 | end)
78 |
79 | it("resolves values after loops", function()
80 | assert.equal([[
81 | Eval: a = (1:7, 5:7)
82 | Eval: a = (1:7, 5:7)]], get_used_variables_as_string([[
83 | local a
84 |
85 | while not a do
86 | if expr then
87 | a = expr2
88 | end
89 | end
90 |
91 | print(a)
92 | ]]))
93 |
94 | assert.equal([[
95 | Set: k = (2:5)
96 | Eval: v = (2:8)
97 | Eval: a = (3:4); b = (1:10)
98 | Eval: a = (1:7, 3:4); b = (1:10)]], get_used_variables_as_string([[
99 | local a, b = 1, 2
100 | for k, v in pairs(t) do
101 | a = k
102 |
103 | if v then
104 | print(a, b)
105 | end
106 | end
107 |
108 | print(a, b)
109 | ]]))
110 | end)
111 | end)
112 |
113 | describe("when resolving upvalues", function()
114 | it("resolves set upvalues naively", function()
115 | assert.equal([[
116 | Eval: f = (3:16)
117 | Eval: a = (1:7, 4:4)]], get_used_variables_as_string([[
118 | local a
119 |
120 | local function f()
121 | a = 5
122 | end
123 |
124 | f()
125 | print(a)
126 | ]]))
127 | end)
128 |
129 | it("naively determines where closure is live", function()
130 | assert.equal([[
131 | Eval: a = (1:7)
132 | Eval: a = (1:7, 6:4)]], get_used_variables_as_string([[
133 | local a = 4
134 |
135 | print(a)
136 |
137 | local function f()
138 | a = 5
139 | end
140 |
141 | print(a)
142 | ]]))
143 | end)
144 |
145 | it("naively determines where closure is live in loops", function()
146 | assert.equal([[
147 | Eval: a = (1:7, 6:22)
148 | Eval: a = (1:7, 6:22)]], get_used_variables_as_string([[
149 | local a = 4
150 |
151 | repeat
152 | print(a)
153 |
154 | escape(function() a = 5 end)
155 | until a
156 | ]]))
157 | end)
158 | end)
159 | end)
160 |
--------------------------------------------------------------------------------
/spec/reversed_fornum_loops_spec.lua:
--------------------------------------------------------------------------------
1 | local helper = require "spec.helper"
2 |
3 | local function assert_warnings(warnings, src)
4 | assert.same(warnings, helper.get_stage_warnings("detect_reversed_fornum_loops", src))
5 | end
6 |
7 | describe("reversed fornum loop detection", function()
8 | it("does not detect anything wrong if not going down from #(expr)", function()
9 | assert_warnings({}, [[
10 | for i = -10, 1 do
11 | print(i)
12 | end
13 | ]])
14 | end)
15 |
16 | it("does not detect anything wrong if limit may be greater than 1", function()
17 | assert_warnings({}, [[
18 | for i = #t, 2 do
19 | print(i)
20 | end
21 |
22 | for i = #t, x do
23 | print(i)
24 | end
25 | ]])
26 | end)
27 |
28 | it("does not detect anything wrong if step may be negative", function()
29 | assert_warnings({}, [[
30 | for i = #t, 1, -1 do
31 | print(i)
32 | end
33 |
34 | for i = #t, 1, x do
35 | print(i)
36 | end
37 | ]])
38 | end)
39 |
40 | it("detects reversed loops going from #(expr) to limit less than or equal to 1", function()
41 | assert_warnings({
42 | {code = "571", line = 1, column = 1, end_column = 16, limit = "1"},
43 | {code = "571", line = 5, column = 1, end_column = 23, limit = "0"},
44 | {code = "571", line = 9, column = 1, end_column = 32, limit = "-123.456"}
45 | }, [[
46 | for i = #t, 1 do
47 | print(t[i])
48 | end
49 |
50 | for i = #"abcdef", 0 do
51 | print(something)
52 | end
53 |
54 | for i = #(...), -123.456, 567 do
55 | print(something)
56 | end
57 | ]])
58 | end)
59 |
60 | it("detects reversed loops in nested statements and functions", function()
61 | assert_warnings({
62 | {code = "571", line = 7, column = 13, end_column = 28, limit = "1"},
63 | {code = "571", line = 8, column = 16, end_column = 31, limit = "1"},
64 | {code = "571", line = 10, column = 22, end_column = 43, limit = "1"}
65 | }, [[
66 | do
67 | print("thing")
68 |
69 | while true do
70 | repeat
71 | for i, v in ipairs(t) do
72 | for i = #a, 1 do
73 | for i = #b, 1 do
74 | function xyz()
75 | for i = #"thing", 1 do
76 | print("thing")
77 | end
78 | end
79 | end
80 | end
81 | end
82 | until foo
83 | end
84 | end
85 | ]])
86 | end)
87 | end)
88 |
--------------------------------------------------------------------------------
/spec/rock/bin/rock.lua:
--------------------------------------------------------------------------------
1 | -- nothing
2 |
--------------------------------------------------------------------------------
/spec/rock/bin/rock.sh:
--------------------------------------------------------------------------------
1 | # nothing
2 |
--------------------------------------------------------------------------------
/spec/rock/lua_modules/something.lua:
--------------------------------------------------------------------------------
1 | -- nothing
2 |
--------------------------------------------------------------------------------
/spec/rock/rock-dev-1.rockspec:
--------------------------------------------------------------------------------
1 | rockspec_format = "3.0"
2 | package = "rock"
3 | version = "dev-1"
4 | source = {
5 | url = "https://github.com/rockman/rock"
6 | }
7 | description = {
8 | license = "MIT"
9 | }
10 | dependencies = {
11 | "lua >= 5.1"
12 | }
13 | test_dependencies = {
14 | "busted = 2.0.rc12-1"
15 | }
16 | test = {
17 | type = "busted"
18 | }
19 |
--------------------------------------------------------------------------------
/spec/rock/src/rock.lua:
--------------------------------------------------------------------------------
1 | -- nothing
2 |
--------------------------------------------------------------------------------
/spec/rock/src/rock/mod.lua:
--------------------------------------------------------------------------------
1 | -- nothing
2 |
--------------------------------------------------------------------------------
/spec/rock/src/rock/thing.c:
--------------------------------------------------------------------------------
1 | // nothing
2 |
--------------------------------------------------------------------------------
/spec/rock/test.lua:
--------------------------------------------------------------------------------
1 | -- nothing
2 |
--------------------------------------------------------------------------------
/spec/rock2/mod.lua:
--------------------------------------------------------------------------------
1 | -- nothing
2 |
--------------------------------------------------------------------------------
/spec/rock2/rock2-dev-1.rockspec:
--------------------------------------------------------------------------------
1 | rockspec_format = "3.0"
2 | package = "rock2"
3 | version = "dev-1"
4 | source = {
5 | url = "https://github.com/rockman/rock2"
6 | }
7 | description = {
8 | license = "MIT"
9 | }
10 | dependencies = {
11 | "lua >= 5.1"
12 | }
13 | test_dependencies = {
14 | "busted = 2.0.rc12-1"
15 | }
16 | build = {}
17 | test = {
18 | type = "busted"
19 | }
20 |
--------------------------------------------------------------------------------
/spec/rock2/spec/rock2_spec.lua:
--------------------------------------------------------------------------------
1 | -- nothing
2 |
--------------------------------------------------------------------------------
/spec/samples/bad.rockspec:
--------------------------------------------------------------------------------
1 | bad("???")
--------------------------------------------------------------------------------
/spec/samples/bad_code.lua:
--------------------------------------------------------------------------------
1 | package.loaded[...] = {}
2 |
3 | local function helper(...)
4 | -- NYI
5 | end
6 |
7 | function embrace(opt)
8 | local opt = opt or "default"
9 | return hepler(opt.."?")
10 | end
11 |
12 |
--------------------------------------------------------------------------------
/spec/samples/bad_flow.lua:
--------------------------------------------------------------------------------
1 | if io.read("*n") == "exit" then
2 |
3 | else
4 | local a, b
5 |
6 | do
7 | -- print(something)
8 | end
9 |
10 | for _ = 1, 10 do
11 | if io.read("*n") == "foobar" then
12 | a, b = 1
13 | print(a, b)
14 | break
15 | else
16 | a, b = 1, 2, 3
17 | print(a, b)
18 | break
19 | end
20 |
21 | print("How could this happen?")
22 | end
23 | end
24 |
25 | while true do
26 | if package.loaded.foo then
27 | return 4
28 | else
29 | print(5)
30 | break
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/spec/samples/bad_whitespace.lua:
--------------------------------------------------------------------------------
1 | -- Examples of whitespace formatting violations
2 |
3 | local function trailing_whitespace_in_code()
4 | return "This is awful"
5 | end
6 |
7 | local function trailing_whitespace_in_comment()
8 | -- Less awful, but...
9 | return "Still bad"
10 | end
11 |
12 | local function trailing_whitespace_in_long_strings()
13 | return [[
14 | It gets worse
15 | Much worse
16 | ]]
17 | --[[ Same in long comments
18 | ]]
19 | end
20 |
21 | local function trailing_whitespace_mixed()
22 | return "Not much better" -- You bet!
23 | end
24 |
25 | local function whitespace_only_lines()
26 |
27 |
28 |
29 |
30 | return "Lost in space"
31 | end
32 |
33 | local function inconsistent_indentation()
34 | return "Don't do this"
35 | end
36 |
37 | return { -- fake "module" table
38 | trailing_whitespace_in_code,
39 | trailing_whitespace_in_comment,
40 | trailing_whitespace_in_long_strings,
41 | trailing_whitespace_mixed,
42 | whitespace_only_lines,
43 | inconsistent_indentation,
44 | }
45 |
--------------------------------------------------------------------------------
/spec/samples/compat.lua:
--------------------------------------------------------------------------------
1 | (setfenv and rawlen)(setfenv and rawlen)
2 |
--------------------------------------------------------------------------------
/spec/samples/custom_std_inline_options.lua:
--------------------------------------------------------------------------------
1 | -- luacheck: push
2 | -- luacheck: std +busted
3 | tostring(setfenv, print(it))
4 | -- luacheck: pop
5 | -- luacheck: std other_std
6 | tostring(setfenv, print(it))
7 |
--------------------------------------------------------------------------------
/spec/samples/defined.lua:
--------------------------------------------------------------------------------
1 | foo = {}
2 |
3 | function foo.bar()
4 | baz()
5 | end
6 |
--------------------------------------------------------------------------------
/spec/samples/defined2.lua:
--------------------------------------------------------------------------------
1 | foo.bar()
2 |
--------------------------------------------------------------------------------
/spec/samples/defined3.lua:
--------------------------------------------------------------------------------
1 | foo = {}
2 | foo = {}
3 | bar = {}
4 |
--------------------------------------------------------------------------------
/spec/samples/defined4.lua:
--------------------------------------------------------------------------------
1 | function foo()
2 | foo = 1
3 | bar = {}
4 | end
5 |
--------------------------------------------------------------------------------
/spec/samples/empty.lua:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpeterv/luacheck/7360cfb4cf2c7dd8c73adf45e31a04811a745250/spec/samples/empty.lua
--------------------------------------------------------------------------------
/spec/samples/global_fields.lua:
--------------------------------------------------------------------------------
1 | local t = table
2 | local upsert = t.upsert
3 | local update = upsert
4 | print(update.something)
5 |
6 | upsert = t.insert
7 | upsert()
8 | upsert.foo = "bar"
9 |
10 | package.loaded.hello = true
11 | package.loaded[package] = true
12 |
13 | local s1 = "find"
14 | local s2 = "gfind"
15 |
16 | -- luacheck: push std max
17 | print(string[s1])
18 | print(string[s2])
19 | -- luacheck: pop
20 |
21 | -- luacheck: push std min
22 | print(string[s1])
23 | print(string[s2])
24 | -- luacheck: pop
25 |
26 | -- luacheck: not globals string.find
27 | print(string[s1])
28 |
29 | -- luacheck: globals nest.nest.nest
30 | nest.nest = "nest"
31 |
32 | print(server)
33 | print(server.sessions)
34 | server.foo = "bar"
35 | server.bar[_G] = "baz"
36 | server.baz = "abcd"
37 | print(server.baz.abcd)
38 | server.sessions["hey"] = "you"
39 |
40 | -- luacheck: std +my_server
41 | server.bar = 1
42 | server.baz = 2
43 |
--------------------------------------------------------------------------------
/spec/samples/global_inline_options.lua:
--------------------------------------------------------------------------------
1 | -- luacheck: allow defined top
2 | foo = 4
3 | print(foo)
4 | bar = 6 -- luacheck: ignore 131
5 |
6 | function f()
7 | baz = 5
8 | -- luacheck: allow defined
9 | qu = 4
10 | print(qu)
11 | end
12 |
13 | -- luacheck: module, globals external
14 | quu = 7
15 | print(external)
16 |
17 | local function g() -- luacheck: ignore
18 | external = 8
19 | end
20 |
--------------------------------------------------------------------------------
/spec/samples/globals.lua:
--------------------------------------------------------------------------------
1 | print(setfenv(rawlen(tostring)))
2 |
--------------------------------------------------------------------------------
/spec/samples/good_code.lua:
--------------------------------------------------------------------------------
1 | local embracer = {}
2 |
3 | local function helper()
4 | -- NYI wontfix
5 | end
6 |
7 | function embracer.embrace(opt)
8 | opt = opt or "default"
9 | return helper(opt.."?")
10 | end
11 |
12 | return embracer
13 |
--------------------------------------------------------------------------------
/spec/samples/indirect_globals.lua:
--------------------------------------------------------------------------------
1 | local t = table
2 | local g = global
3 | local t_concat
4 | t_concat = t.concat
5 | t_concat.foo.bar = g:method(g, global)
6 |
--------------------------------------------------------------------------------
/spec/samples/inline_options.lua:
--------------------------------------------------------------------------------
1 | -- luacheck: ignore 4
2 | -- luacheck: ignore foo bar
3 | foo()
4 | bar()
5 |
6 | local function f(a) -- luacheck: no unused args
7 | -- luacheck: globals baz
8 | foo()
9 | bar()
10 | baz()
11 | qu() -- luacheck: globals qu
12 | qu()
13 | end
14 |
15 | baz() -- luacheck should ignore this comment
16 |
17 | -- luacheck: push ignore 2/f
18 | local f
19 | -- luacheck: push ignore 2/g
20 | local g
21 | -- luacheck: pop
22 | local f, g
23 | -- luacheck: pop
24 | local f, g
25 |
26 | -- luacheck: push
27 | local function f() --luacheck: ignore
28 | -- luacheck: pop
29 | end
30 |
31 | -- luacheck: ignore 5
32 | do end
33 | -- luacheck: enable 54
34 | do end
35 | if false then end
36 |
--------------------------------------------------------------------------------
/spec/samples/line_length.lua:
--------------------------------------------------------------------------------
1 | -- luacheck: only 63
2 | local aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
3 | ------------------------------------------------------------------------------------------------------------------------------------------ comments can be long, too
4 |
5 | -- luacheck: push no max code line length
6 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = "readable code"
7 | string = [[
8 | looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong
9 | ]]
10 | normal = "still normal"
11 |
12 | -- luacheck: push max line length 40
13 | this_overrides_code_line_specific_option = true
14 | -- luacheck: pop
15 |
16 | -- luacheck: pop
17 |
18 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = "unreadable"
19 | -- luacheck: max line length 80
20 | -- luacheck: max string line length 100
21 | -- luacheck: max comment line length 120
22 | local i_code_in_ed_in_a_terminal_with_default_width_and_i_dont_like_long_lines = true
23 | local really = false
24 | string2 = [[kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk94
25 | ]]
26 | string3 = [[kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk105
27 | ]]
28 | code() -- comment kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk105
29 | --[[ comment kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk125
30 | ]]
31 |
--------------------------------------------------------------------------------
/spec/samples/python_code.lua:
--------------------------------------------------------------------------------
1 | from __future__ import braces
2 |
3 |
--------------------------------------------------------------------------------
/spec/samples/read_globals.lua:
--------------------------------------------------------------------------------
1 | string = "foo"
2 | table.append = table.insert
3 | _ENV = nil
4 | foo = "4"; print(foo)
5 | bar = "5"; print(bar)
6 | baz[4] = "6"; print(baz)
7 |
--------------------------------------------------------------------------------
/spec/samples/read_globals_inline_options.lua:
--------------------------------------------------------------------------------
1 | -- luacheck: read globals foo bar
2 | foo(bar, baz)
3 | foo, bar, baz, baz[1] = false, true, nil, 5 -- luacheck: ignore 111/foo 121/ba.
4 | -- luacheck: globals bar baz
5 | foo, bar, baz = 678, 829, 914
6 |
--------------------------------------------------------------------------------
/spec/samples/redefined.lua:
--------------------------------------------------------------------------------
1 | local a = {}
2 |
3 | function a:b(...)
4 | local a, self = 4
5 |
6 | do
7 | local a = (...)(a)
8 | each(a, function() local self = self[5]; return self.bar end)
9 | end
10 |
11 | print(a[1])
12 | end
13 |
14 | return a
15 |
--------------------------------------------------------------------------------
/spec/samples/reversed_fornum.lua:
--------------------------------------------------------------------------------
1 | for i = #(...), -1.5 do
2 | print(i)
3 | end
4 |
--------------------------------------------------------------------------------
/spec/samples/sample.rockspec:
--------------------------------------------------------------------------------
1 | build = {
2 | type = "builtin",
3 | modules = {
4 | good = "spec/samples/good_code.lua",
5 | bad = "spec/samples/bad_code.lua",
6 | not_even_a_module = some_global
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/spec/samples/unused_code.lua:
--------------------------------------------------------------------------------
1 | local foo = {}
2 |
3 | function foo.bar(baz)
4 | for i=1, 5 do
5 | local q
6 |
7 | for a, b, c in pairs(foo) do
8 | print(4)
9 | end
10 | end
11 | end
12 |
13 | local x = 5
14 | x = 6
15 | x = 7; print(x)
16 |
17 | local y = 5;
18 | (function() print(y) end)()
19 | y = 6
20 |
21 | local z = 5;
22 | (function() z = 4 end)()
23 | z = 6
24 |
--------------------------------------------------------------------------------
/spec/samples/unused_secondaries.lua:
--------------------------------------------------------------------------------
1 | local function f() end
2 |
3 | local a, b = f()
4 | f(b)
5 |
6 | local x, y, z = f(), f()
7 | f(y)
8 |
9 | local t, o = {}
10 | o = 5
11 | print(o)
12 | o, t[1] = f()
13 | f(t)
14 |
--------------------------------------------------------------------------------
/spec/samples/utf8.lua:
--------------------------------------------------------------------------------
1 | -- 嗨,你好嗎?
2 | math["분야 명"] = math["値"]
3 | --[[комментарий]] local t = {
4 | ["päällekkäinen nimi ab"] = 1,
5 | ["päällekkäinen nimi ab"] = 2
6 | }
7 |
8 | -- líne an-fhada a choinníonn dul ag dul agus ag dul agus ag dul agus ag dul ach nach bhfuil 120 carachtar ann. Y diwed
9 |
--------------------------------------------------------------------------------
/spec/samples/utf8_error.lua:
--------------------------------------------------------------------------------
1 | -- 嗨,你好嗎?
2 | --[[GÆT]] ошибка =(
3 |
--------------------------------------------------------------------------------
/spec/serializer_spec.lua:
--------------------------------------------------------------------------------
1 | local serializer = require "luacheck.serializer"
2 |
3 | describe("serializer", function()
4 | describe("dump_result", function()
5 | -- luacheck: no max line length
6 |
7 | it("returns serialized result", function()
8 | assert.same(
9 | [[return {{{"111",5,100,102,"foo",{"faa"}},{"211",4,1,3,"bar",nil,true},{"011",nil,100000,nil,"near '\"'"}},{}}]],
10 | serializer.dump_check_result({
11 | warnings = {
12 | {code = "111", name = "foo", indexing = {"faa"}, line = 5, column = 100, end_column = 102},
13 | {code = "211", name = "bar", line = 4, column = 1, end_column = 3, secondary = true},
14 | {code = "011", column = 100000, msg = "near '\"'"}
15 | },
16 | inline_options = {}
17 | })
18 | )
19 | end)
20 |
21 | it("puts repeating string values into locals", function()
22 | assert.same(
23 | [[local A,B="111","foo";return {{{A,5,100,nil,B},{A,6,100,nil,B},{"011",nil,100000,nil,"near '\"'"}},{},{}}]],
24 | serializer.dump_check_result({
25 | warnings = {
26 | {code = "111", name = "foo", line = 5, column = 100},
27 | {code = "111", name = "foo", line = 6, column = 100, secondary = true},
28 | {code = "011", column = 100000, msg = "near '\"'"}
29 | },
30 | inline_options = {},
31 | line_lengths = {}
32 | })
33 | )
34 | end)
35 |
36 | it("uses at most 52 locals", function()
37 | local warnings = {}
38 | local expected_parts1 = {"local A"}
39 | local expected_parts2 = {'="111"'}
40 | local expected_parts3 = {";return {{"}
41 |
42 | local function add_char(b)
43 | local c = string.char(b)
44 | table.insert(warnings, {code = "111", name = c})
45 | table.insert(warnings, {code = "111", name = c})
46 | table.insert(expected_parts1, "," .. c)
47 | table.insert(expected_parts2, ',"' .. c .. '"')
48 | table.insert(expected_parts3, ('{A,nil,nil,nil,%s},{A,nil,nil,nil,%s},'):format(c, c))
49 | end
50 |
51 | local function add_extra(name)
52 | table.insert(warnings, {code = "111", name = name})
53 | table.insert(warnings, {code = "111", name = name})
54 | table.insert(expected_parts3, ('{A,nil,nil,nil,"%s"},{A,nil,nil,nil,"%s"},'):format(name, name))
55 | end
56 |
57 | for b = ("B"):byte(), ("Z"):byte() do
58 | add_char(b)
59 | end
60 |
61 | for b = ("a"):byte(), ("z"):byte() do
62 | add_char(b)
63 | end
64 |
65 | add_extra("extra1")
66 | add_extra("extra2")
67 |
68 | local expected_part1 = table.concat(expected_parts1)
69 | local expected_part2 = table.concat(expected_parts2)
70 | local expected_part3 = table.concat(expected_parts3):sub(1, -2)
71 | local expected = expected_part1 .. expected_part2 .. expected_part3 .. "},{},{}}"
72 |
73 | assert.same(expected,
74 | serializer.dump_check_result({
75 | warnings = warnings,
76 | inline_options = {},
77 | line_lengths = {}
78 | })
79 | )
80 | end)
81 |
82 | it("handles error result", function()
83 | assert.same('return {{{"011",2,4,nil,"message"}},{},{}}', serializer.dump_check_result({
84 | warnings = {
85 | {code = "011", line = 2, column = 4, msg = "message"}
86 | },
87 | inline_options = {},
88 | line_lengths = {}
89 | }))
90 | end)
91 | end)
92 | end)
93 |
--------------------------------------------------------------------------------
/spec/unbalanced_assignments_spec.lua:
--------------------------------------------------------------------------------
1 | local helper = require "spec.helper"
2 |
3 | local function assert_warnings(warnings, src)
4 | assert.same(warnings, helper.get_stage_warnings("detect_unbalanced_assignments", src))
5 | end
6 |
7 | describe("unbalanced assignment detection", function()
8 | it("detects unbalanced assignments", function()
9 | assert_warnings({
10 | {code = "532", line = 4, column = 1, end_column = 8},
11 | {code = "531", line = 5, column = 1, end_column = 14}
12 | }, [[
13 | local a, b = 4; (...)(a)
14 |
15 | a, b = (...)(); (...)(a, b)
16 | a, b = 5; (...)(a, b)
17 | a, b = 1, 2, 3; (...)(a, b)
18 | local c, d
19 | ]])
20 | end)
21 |
22 | it("detects unbalanced assignments in nested blocks and functions", function()
23 | assert_warnings({
24 | {code = "532", line = 6, column = 10, end_column = 17},
25 | {code = "532", line = 9, column = 13, end_column = 20},
26 | {code = "532", line = 14, column = 22, end_column = 29},
27 | {code = "531", line = 17, column = 25, end_column = 38}
28 | }, [[
29 | do
30 | local a, b, c, d
31 |
32 | while x do
33 | if y then
34 | a, b = 1
35 | else
36 | repeat
37 | a, b = 1
38 |
39 | function t()
40 | for i = 1, 10 do
41 | for _, v in ipairs(tab) do
42 | a, b = 1
43 |
44 | if c then
45 | a, b = 1, 2, 3
46 | end
47 | end
48 | end
49 | end
50 | until z
51 | end
52 | end
53 | end
54 | ]])
55 | end)
56 | end)
57 |
--------------------------------------------------------------------------------
/spec/uninit_accesses_spec.lua:
--------------------------------------------------------------------------------
1 | local helper = require "spec.helper"
2 |
3 | local function assert_warnings(warnings, src)
4 | assert.same(warnings, helper.get_stage_warnings("detect_uninit_accesses", src))
5 | end
6 |
7 | describe("uninitalized access detection", function()
8 | it("detects accessing uninitialized variables", function()
9 | assert_warnings({
10 | {code = "321", name = "a", line = 6, column = 12, end_column = 12}
11 | }, [[
12 | local a
13 |
14 | if ... then
15 | a = 5
16 | else
17 | a = get(a)
18 | end
19 |
20 | return a
21 | ]])
22 | end)
23 |
24 | it("detects accessing uninitialized variables in unreachable functions", function()
25 | assert_warnings({
26 | {code = "321", name = "a", line = 12, column = 20, end_column = 20}
27 | }, [[
28 | return function()
29 | return function()
30 | do return end
31 |
32 | return function(x)
33 | local a
34 |
35 | if x then
36 | a = 1
37 | return a + 2
38 | else
39 | return a + 1
40 | end
41 | end
42 | end
43 | end
44 | ]])
45 | end)
46 |
47 | it("detects mutating uninitialized variables", function()
48 | assert_warnings({
49 | {code = "341", name = "a", line = 4, column = 4, end_column = 4}
50 | }, [[
51 | local a
52 |
53 | if ... then
54 | a.k = 5
55 | else
56 | a = get(5)
57 | end
58 |
59 | return a
60 | ]])
61 | end)
62 |
63 | it("detects accessing uninitialized variables in nested functions", function()
64 | assert_warnings({
65 | {code = "321", name = "a", line = 7, column = 12, end_column = 12}
66 | }, [[
67 | return function() return function(...)
68 | local a
69 |
70 | if ... then
71 | a = 5
72 | else
73 | a = get(a)
74 | end
75 |
76 | return a
77 | end end
78 | ]])
79 | end)
80 |
81 | it("handles accesses with no reaching values", function()
82 | assert_warnings({}, [[
83 | local var = "foo"
84 | (...)(var)
85 | do return end
86 | (...)(var)
87 | ]])
88 | end)
89 |
90 | it("handles upvalue accesses with no reaching values", function()
91 | assert_warnings({}, [[
92 | local var = "foo"
93 | (...)(var)
94 | do return end
95 | (...)(function()
96 | return var
97 | end)
98 | ]])
99 | end)
100 |
101 | it("handles upvalue accesses with no reaching values in a nested function", function()
102 | assert_warnings({}, [[
103 | return function(...)
104 | local var = "foo"
105 | (...)(var)
106 | do return end
107 | (...)(function()
108 | return var
109 | end)
110 | end
111 | ]])
112 | end)
113 |
114 | it("does not detect accessing unitialized variables incorrectly in loops", function()
115 | assert_warnings({}, [[
116 | local a
117 |
118 | while not a do
119 | a = get()
120 | end
121 |
122 | return a
123 | ]])
124 | end)
125 | end)
126 |
--------------------------------------------------------------------------------
/spec/unreachable_code_spec.lua:
--------------------------------------------------------------------------------
1 | local helper = require "spec.helper"
2 |
3 | local function assert_warnings(warnings, src)
4 | assert.same(warnings, helper.get_stage_warnings("detect_unreachable_code", src))
5 | end
6 |
7 | describe("unreachable code detection", function()
8 | it("detects unreachable code", function()
9 | assert_warnings({
10 | {code = "511", line = 2, column = 1, end_column = 24}
11 | }, [[
12 | do return end
13 | if ... then return 6 end
14 | return 3
15 | ]])
16 |
17 | assert_warnings({
18 | {code = "511", line = 7, column = 1, end_column = 11},
19 | {code = "511", line = 13, column = 1, end_column = 8}
20 | }, [[
21 | if ... then
22 | return 4
23 | else
24 | return 6
25 | end
26 |
27 | if ... then
28 | return 7
29 | else
30 | return 8
31 | end
32 |
33 | return 3
34 | ]])
35 | end)
36 |
37 | it("detects unreachable code with literal conditions", function()
38 | assert_warnings({
39 | {code = "511", line = 4, column = 1, end_column = 6}
40 | }, [[
41 | while true do
42 | (...)()
43 | end
44 | return
45 | ]])
46 |
47 | assert_warnings({}, [[
48 | repeat
49 | if ... then
50 | break
51 | end
52 | until false
53 | return
54 | ]])
55 |
56 | assert_warnings({
57 | {code = "511", line = 6, column = 1, end_column = 6}
58 | }, [[
59 | repeat
60 | if nil then
61 | break
62 | end
63 | until false
64 | return
65 | ]])
66 | end)
67 |
68 | it("detects unreachable expressions", function()
69 | assert_warnings({
70 | {code = "511", line = 3, column = 7, end_column = 9}
71 | }, [[
72 | repeat
73 | return
74 | until ...
75 | ]])
76 |
77 | assert_warnings({
78 | {code = "511", line = 3, column = 8, end_column = 10}
79 | }, [[
80 | if true then
81 | (...)()
82 | elseif ... then
83 | (...)()
84 | end
85 | ]])
86 | end)
87 |
88 | it("detects unreachable functions", function()
89 | assert_warnings({
90 | {code = "511", line = 3, column = 1, end_column = 16}
91 | }, [[
92 | local f = nil
93 | do return end
94 | function f() end
95 | ]])
96 | end)
97 |
98 | it("detects unreachable code in nested function", function()
99 | assert_warnings({
100 | {code = "511", line = 4, column = 7, end_column = 12}
101 | }, [[
102 | return function()
103 | return function()
104 | do return end
105 | return
106 | end
107 | end
108 | ]])
109 | end)
110 |
111 | it("detects unreachable code in unreachable nested function", function()
112 | assert_warnings({
113 | {code = "511", line = 4, column = 4, end_column = 20},
114 | {code = "511", line = 6, column = 7, end_column = 12}
115 | }, [[
116 | return function()
117 | do return end
118 |
119 | return function()
120 | do return end
121 | return
122 | end
123 | end
124 | ]])
125 | end)
126 | end)
127 |
--------------------------------------------------------------------------------
/spec/unused_fields_spec.lua:
--------------------------------------------------------------------------------
1 | local helper = require "spec.helper"
2 |
3 | local function assert_warnings(warnings, src)
4 | assert.same(warnings, helper.get_stage_warnings("detect_unused_fields", src))
5 | end
6 |
7 | describe("unused field detection", function()
8 | it("detects unused fields in table literals", function()
9 | assert_warnings({
10 | {code = "314", field = "key", line = 3, column = 5, end_column = 9,
11 | overwritten_line = 7, overwritten_column = 4, overwritten_end_column = 6},
12 | {code = "314", field = "2", index = true, line = 6, column = 4, end_column = 4,
13 | overwritten_line = 9, overwritten_column = 5, overwritten_end_column = 9},
14 | {code = "314", field = "key", line = 7, column = 4, end_column = 6,
15 | overwritten_line = 8, overwritten_column = 4, overwritten_end_column = 6},
16 | {code = "314", field = "0.2e1", line = 9, column = 5, end_column = 9,
17 | overwritten_line = 10, overwritten_column = 5, overwritten_end_column = 5}
18 | }, [[
19 | local x, y, z = 1, 2, 3
20 | return {
21 | ["key"] = 4,
22 | [z] = 7,
23 | 1,
24 | y,
25 | key = x,
26 | key = 0,
27 | [0.2e1] = 6,
28 | [2] = 7
29 | }
30 | ]])
31 | end)
32 |
33 | it("detects unused fields in nested table literals", function()
34 | assert_warnings({
35 | {code = "314", field = "a", line = 2, column = 5, end_column = 5,
36 | overwritten_line = 2, overwritten_column = 12, overwritten_end_column = 12},
37 | {code = "314", field = "b", line = 3, column = 11, end_column = 11,
38 | overwritten_line = 3, overwritten_column = 18, overwritten_end_column = 18}
39 | }, [[
40 | return {
41 | {a = 1, a = 2},
42 | key = {b = 1, b = 2}
43 | }
44 | ]])
45 | end)
46 | end)
47 |
--------------------------------------------------------------------------------
/src/luacheck/builtin_standards/ngx.lua:
--------------------------------------------------------------------------------
1 | local standards = require "luacheck.standards"
2 |
3 | local empty = {}
4 |
5 | local luajit_string_def = standards.def_fields("byte", "char", "dump", "find", "format", "gmatch",
6 | "gsub", "len", "lower", "match", "rep", "reverse", "sub", "upper")
7 |
8 | -- Globals added by lua-nginx-module 0.10.10 in internal definition table format.
9 | -- Will be added to `luajit` std to form `ngx_lua` std.
10 | local ngx_defs = {
11 | fields = {
12 | ngx = {
13 | fields = {
14 | arg = {other_fields = true, read_only = false},
15 | var = {other_fields = true, read_only = false},
16 | OK = empty,
17 | ERROR = empty,
18 | AGAIN = empty,
19 | DONE = empty,
20 | DECLINED = empty,
21 | null = empty,
22 | HTTP_GET = empty,
23 | HTTP_HEAD = empty,
24 | HTTP_PUT = empty,
25 | HTTP_POST = empty,
26 | HTTP_DELETE = empty,
27 | HTTP_OPTIONS = empty,
28 | HTTP_MKCOL = empty,
29 | HTTP_COPY = empty,
30 | HTTP_MOVE = empty,
31 | HTTP_PROPFIND = empty,
32 | HTTP_PROPPATCH = empty,
33 | HTTP_LOCK = empty,
34 | HTTP_UNLOCK = empty,
35 | HTTP_PATCH = empty,
36 | HTTP_TRACE = empty,
37 | HTTP_CONTINUE = empty,
38 | HTTP_SWITCHING_PROTOCOLS = empty,
39 | HTTP_OK = empty,
40 | HTTP_CREATED = empty,
41 | HTTP_ACCEPTED = empty,
42 | HTTP_NO_CONTENT = empty,
43 | HTTP_PARTIAL_CONTENT = empty,
44 | HTTP_SPECIAL_RESPONSE = empty,
45 | HTTP_MOVED_PERMANENTLY = empty,
46 | HTTP_MOVED_TEMPORARILY = empty,
47 | HTTP_SEE_OTHER = empty,
48 | HTTP_NOT_MODIFIED = empty,
49 | HTTP_TEMPORARY_REDIRECT = empty,
50 | HTTP_BAD_REQUEST = empty,
51 | HTTP_UNAUTHORIZED = empty,
52 | HTTP_PAYMENT_REQUIRED = empty,
53 | HTTP_FORBIDDEN = empty,
54 | HTTP_NOT_FOUND = empty,
55 | HTTP_NOT_ALLOWED = empty,
56 | HTTP_NOT_ACCEPTABLE = empty,
57 | HTTP_REQUEST_TIMEOUT = empty,
58 | HTTP_CONFLICT = empty,
59 | HTTP_GONE = empty,
60 | HTTP_UPGRADE_REQUIRED = empty,
61 | HTTP_TOO_MANY_REQUESTS = empty,
62 | HTTP_CLOSE = empty,
63 | HTTP_ILLEGAL = empty,
64 | HTTP_INTERNAL_SERVER_ERROR = empty,
65 | HTTP_METHOD_NOT_IMPLEMENTED = empty,
66 | HTTP_BAD_GATEWAY = empty,
67 | HTTP_SERVICE_UNAVAILABLE = empty,
68 | HTTP_GATEWAY_TIMEOUT = empty,
69 | HTTP_VERSION_NOT_SUPPORTED = empty,
70 | HTTP_INSUFFICIENT_STORAGE = empty,
71 | STDERR = empty,
72 | EMERG = empty,
73 | ALERT = empty,
74 | CRIT = empty,
75 | ERR = empty,
76 | WARN = empty,
77 | NOTICE = empty,
78 | INFO = empty,
79 | DEBUG = empty,
80 | ctx = {other_fields = true, read_only = false},
81 | location = standards.def_fields("capture", "capture_multi"),
82 | status = {read_only = false},
83 | header = {other_fields = true, read_only = false},
84 | resp = standards.def_fields("get_headers"),
85 | req = standards.def_fields("is_internal", "start_time", "http_version", "raw_header",
86 | "get_method", "set_method", "set_uri", "set_uri_args", "get_uri_args",
87 | "get_post_args", "get_headers", "set_header", "clear_header", "read_body",
88 | "discard_body", "get_body_data", "get_body_file", "set_body_data",
89 | "set_body_file", "init_body", "append_body", "finish_body", "socket"),
90 | exec = empty,
91 | redirect = empty,
92 | send_headers = empty,
93 | headers_sent = empty,
94 | print = empty,
95 | say = empty,
96 | log = empty,
97 | flush = empty,
98 | exit = empty,
99 | eof = empty,
100 | sleep = empty,
101 | escape_uri = empty,
102 | unescape_uri = empty,
103 | encode_args = empty,
104 | decode_args = empty,
105 | encode_base64 = empty,
106 | decode_base64 = empty,
107 | crc32_short = empty,
108 | crc32_long = empty,
109 | hmac_sha1 = empty,
110 | md5 = empty,
111 | md5_bin = empty,
112 | sha1_bin = empty,
113 | quote_sql_str = empty,
114 | today = empty,
115 | time = empty,
116 | now = empty,
117 | update_time = empty,
118 | localtime = empty,
119 | utctime = empty,
120 | cookie_time = empty,
121 | http_time = empty,
122 | parse_http_time = empty,
123 | is_subrequest = empty,
124 | re = standards.def_fields("match", "find", "gmatch", "sub", "gsub"),
125 | shared = {other_fields = true, read_only = false},
126 | socket = standards.def_fields("udp", "tcp", "connect", "stream"),
127 | get_phase = empty,
128 | thread = standards.def_fields("spawn", "wait", "kill"),
129 | on_abort = empty,
130 | timer = standards.def_fields("at", "every", "running_count", "pending_count"),
131 | config = {
132 | fields = {
133 | subsystem = luajit_string_def,
134 | debug = empty,
135 | prefix = empty,
136 | nginx_version = empty,
137 | nginx_configure = empty,
138 | ngx_lua_version = empty,
139 | }
140 | },
141 | worker = standards.def_fields("pid", "count", "id", "exiting"),
142 | },
143 | },
144 | ndk = {
145 | fields = {
146 | set_var = {other_fields = true},
147 | },
148 | },
149 | },
150 | }
151 |
152 | return ngx_defs
153 |
--------------------------------------------------------------------------------
/src/luacheck/cache.lua:
--------------------------------------------------------------------------------
1 | local fs = require "luacheck.fs"
2 | local serializer = require "luacheck.serializer"
3 | local sha1 = require "luacheck.vendor.sha1"
4 | local utils = require "luacheck.utils"
5 |
6 | local cache = {}
7 |
8 | -- Check results can be cached inside a given cache directory.
9 | -- Check result for a file is stored in `/`.
10 | -- Cache file format: \n\n= cache_mtime then
100 | return
101 | end
102 |
103 | local fh = io.open(cache_filename, "rb")
104 |
105 | if not fh then
106 | return
107 | end
108 |
109 | if fh:read() ~= format_version then
110 | fh:close()
111 | return
112 | end
113 |
114 | if fh:read() ~= normalized_filename then
115 | fh:close()
116 | return
117 | end
118 |
119 | local serialized_result = fh:read("*a")
120 | fh:close()
121 |
122 | if not serialized_result then
123 | return nil, true
124 | end
125 |
126 | local result = serializer.load_check_result(serialized_result)
127 |
128 | if not result then
129 | return nil, true
130 | end
131 |
132 | return result
133 | end
134 |
135 | function cache.new(cache_directory)
136 | return Cache(cache_directory)
137 | end
138 |
139 | return cache
140 |
--------------------------------------------------------------------------------
/src/luacheck/check.lua:
--------------------------------------------------------------------------------
1 | local check_state = require "luacheck.check_state"
2 | local core_utils = require "luacheck.core_utils"
3 | local parse_inline_options = require "luacheck.stages.parse_inline_options"
4 | local parser = require "luacheck.parser"
5 | local stages = require "luacheck.stages"
6 | local utils = require "luacheck.utils"
7 |
8 | local inline_option_fields = utils.array_to_set(parse_inline_options.inline_option_fields)
9 |
10 | local function validate_fields(tables, per_code_fields)
11 | for _, t in ipairs(tables) do
12 | local fields_set
13 |
14 | if per_code_fields then
15 | if not t.code then
16 | error("Warning has no code", 0)
17 | end
18 |
19 | local warning_info = stages.warnings[t.code]
20 |
21 | if not warning_info then
22 | error("Unknown issue code " .. t.code, 0)
23 | end
24 |
25 | fields_set = warning_info.fields_set
26 | else
27 | fields_set = inline_option_fields
28 | end
29 |
30 | for field in pairs(t) do
31 | if not fields_set[field] then
32 | error("Unknown field " .. field .. " in " ..
33 | (per_code_fields and "issue with code " .. t.code or "inline option table"), 0)
34 | end
35 | end
36 | end
37 | end
38 |
39 | --- Checks source.
40 | -- Returns a table with results, with the following fields:
41 | -- `events`: array of issues and inline option events (options, push, or pop).
42 | -- `per_line_options`: map from line numbers to arrays of inline option events.
43 | -- `line_lengths`: map from line numbers to line lengths.
44 | -- `line_endings`: map from line numbers to "comment", "string", or `nil` base on
45 | -- whether the line ending is within a token.
46 | -- If `events` array contains a syntax error, the other fields are empty tables.
47 | local function check(source)
48 | local chstate = check_state.new(source)
49 | local ok, error_wrapper = utils.try(stages.run, chstate)
50 | local warnings, inline_options, line_lengths, line_endings
51 |
52 | if ok then
53 | warnings = chstate.warnings
54 | core_utils.sort_by_location(warnings)
55 | inline_options = chstate.inline_options
56 | line_lengths = chstate.line_lengths
57 | line_endings = chstate.line_endings
58 | else
59 | local err = error_wrapper.err
60 |
61 | if not utils.is_instance(err, parser.SyntaxError) then
62 | error(error_wrapper, 0)
63 | end
64 |
65 | local syntax_error = {
66 | code = "011",
67 | line = err.line,
68 | column = chstate:offset_to_column(err.line, err.offset),
69 | end_column = chstate:offset_to_column(err.line, err.end_offset),
70 | msg = err.msg
71 | }
72 |
73 | if err.prev_line then
74 | syntax_error.prev_line = err.prev_line
75 | syntax_error.prev_column = chstate:offset_to_column(err.prev_line, err.prev_offset)
76 | syntax_error.prev_end_column = chstate:offset_to_column(err.prev_line, err.prev_end_offset)
77 | end
78 |
79 | warnings = {syntax_error}
80 | inline_options = {}
81 | line_lengths = {}
82 | line_endings = {}
83 | end
84 |
85 | validate_fields(warnings, true)
86 | validate_fields(inline_options)
87 |
88 | return {
89 | warnings = warnings,
90 | inline_options = inline_options,
91 | line_lengths = line_lengths,
92 | line_endings = line_endings
93 | }
94 | end
95 |
96 | return check
97 |
--------------------------------------------------------------------------------
/src/luacheck/check_state.lua:
--------------------------------------------------------------------------------
1 | local utils = require "luacheck.utils"
2 |
3 | local check_state = {}
4 |
5 | local CheckState = utils.class()
6 |
7 | function CheckState:__init(source_bytes)
8 | self.source_bytes = source_bytes
9 | self.warnings = {}
10 | end
11 |
12 | -- Returns column of a character in a line given its offset.
13 | -- The column is never larger than the line length.
14 | -- This can be called if line length is not yet known.
15 | function CheckState:offset_to_column(line, offset)
16 | local line_length = self.line_lengths[line]
17 | local column = offset - self.line_offsets[line] + 1
18 |
19 | if not line_length then
20 | return column
21 | end
22 |
23 | return math.max(1, math.min(line_length, column))
24 | end
25 |
26 | function CheckState:warn_column_range(code, range, warning)
27 | warning = warning or {}
28 | warning.code = code
29 | warning.line = range.line
30 | warning.column = range.column
31 | warning.end_column = range.end_column
32 | table.insert(self.warnings, warning)
33 | return warning
34 | end
35 |
36 | function CheckState:warn(code, line, offset, end_offset, warning)
37 | warning = warning or {}
38 | warning.code = code
39 | warning.line = line
40 | warning.column = self:offset_to_column(line, offset)
41 | warning.end_column = self:offset_to_column(line, end_offset)
42 | table.insert(self.warnings, warning)
43 | return warning
44 | end
45 |
46 | function CheckState:warn_range(code, range, warning)
47 | return self:warn(code, range.line, range.offset, range.end_offset, warning)
48 | end
49 |
50 | function CheckState:warn_var(code, var, warning)
51 | warning = self:warn_range(code, var.node, warning)
52 | warning.name = var.name
53 | return warning
54 | end
55 |
56 | function CheckState:warn_value(code, value, warning)
57 | warning = self:warn_range(code, value.var_node, warning)
58 | warning.name = value.var.name
59 | return warning
60 | end
61 |
62 | function check_state.new(source_bytes)
63 | return CheckState(source_bytes)
64 | end
65 |
66 | return check_state
67 |
--------------------------------------------------------------------------------
/src/luacheck/core_utils.lua:
--------------------------------------------------------------------------------
1 | local decoder = require "luacheck.decoder"
2 | local utils = require "luacheck.utils"
3 |
4 | local core_utils = {}
5 |
6 | -- Attempts to evaluate a node as a Lua value, without resolving locals.
7 | -- Returns Lua value and its string representation on success, nothing on failure.
8 | function core_utils.eval_const_node(node)
9 | if node.tag == "True" then
10 | return true, "true"
11 | elseif node.tag == "False" then
12 | return false, "false"
13 | elseif node.tag == "String" then
14 | local chars = decoder.decode(node[1])
15 | return node[1], chars:get_printable_substring(1, chars:get_length())
16 | else
17 | local is_negative
18 |
19 | if node.tag == "Op" and node[1] == "unm" then
20 | is_negative = true
21 | node = node[2]
22 | end
23 |
24 | if node.tag ~= "Number" then
25 | return
26 | end
27 |
28 | local str = node[1]
29 |
30 | if str:find("[iIuUlL]") then
31 | -- Ignore LuaJIT cdata literals.
32 | return
33 | end
34 |
35 | -- On Lua 5.3 convert to float to get same results as on Lua 5.1 and 5.2.
36 | if _VERSION == "Lua 5.3" and not str:find("[%.eEpP]") then
37 | str = str .. ".0"
38 | end
39 |
40 | local number = tonumber(str)
41 |
42 | if not number then
43 | return
44 | end
45 |
46 | if is_negative then
47 | number = -number
48 | end
49 |
50 | if number == number and number < 1/0 and number > -1/0 then
51 | return number, (is_negative and "-" or "") .. node[1]
52 | end
53 | end
54 | end
55 |
56 | local statement_containing_tags = utils.array_to_set({"Do", "While", "Repeat", "Fornum", "Forin", "If"})
57 |
58 | -- `items` is an array of nodes or nested item arrays.
59 | local function scan_for_statements(chstate, items, tags, callback, ...)
60 | for _, item in ipairs(items) do
61 | if tags[item.tag] then
62 | callback(chstate, item, ...)
63 | end
64 |
65 | if not item.tag or statement_containing_tags[item.tag] then
66 | scan_for_statements(chstate, item, tags, callback, ...)
67 | end
68 | end
69 | end
70 |
71 | -- Calls `callback(chstate, node, ...)` for each statement node within AST with tag in given array.
72 | function core_utils.each_statement(chstate, tags_array, callback, ...)
73 | local tags = utils.array_to_set(tags_array)
74 |
75 | for _, line in ipairs(chstate.lines) do
76 | scan_for_statements(chstate, line.node[2], tags, callback, ...)
77 | end
78 | end
79 |
80 | local function location_comparator(warning1, warning2)
81 | if warning1.line ~= warning2.line then
82 | return warning1.line < warning2.line
83 | elseif warning1.column ~= warning2.column then
84 | return warning1.column < warning2.column
85 | else
86 | return warning1.code < warning2.code
87 | end
88 | end
89 |
90 | -- Sorts an array of warnings by location information as provided in `line` and `column` fields.
91 | function core_utils.sort_by_location(warnings)
92 | table.sort(warnings, location_comparator)
93 | end
94 |
95 | return core_utils
96 |
--------------------------------------------------------------------------------
/src/luacheck/decoder.lua:
--------------------------------------------------------------------------------
1 | local unicode = require "luacheck.unicode"
2 | local utils = require "luacheck.utils"
3 |
4 | local decoder = {}
5 |
6 | local sbyte = string.byte
7 | local sfind = string.find
8 | local sgsub = string.gsub
9 | local ssub = string.sub
10 |
11 | -- `LatinChars` and `UnicodeChars` objects represent source strings
12 | -- and provide Unicode-aware access to them with a common interface.
13 | -- Source bytes should not be accessed directly.
14 | -- Provided methods are:
15 | -- `Chars:get_codepoint(index)`: returns codepoint at given index as integer or nil if index is out of range.
16 | -- `Chars:get_substring(from, to)`: returns substring of original bytes corresponding to characters from `from` to `to`.
17 | -- `Chars:get_printable_substring(from. to)`: like get_substring but escapes not printable characters.
18 | -- `Chars:get_length()`: returns total number of characters.
19 | -- `Chars:find(pattern, from)`: `string.find` but `from` is in characters. Return values are still in bytes.
20 |
21 | -- `LatinChars` is an optimized special case for latin1 strings.
22 | local LatinChars = utils.class()
23 |
24 | function LatinChars:__init(bytes)
25 | self._bytes = bytes
26 | end
27 |
28 | function LatinChars:get_codepoint(index)
29 | return sbyte(self._bytes, index)
30 | end
31 |
32 | function LatinChars:get_substring(from, to)
33 | return ssub(self._bytes, from, to)
34 | end
35 |
36 | local function hexadecimal_escaper(byte)
37 | return ("\\x%02X"):format(sbyte(byte))
38 | end
39 |
40 | function LatinChars:get_printable_substring(from, to)
41 | return (sgsub(ssub(self._bytes, from, to), "[^\32-\126]", hexadecimal_escaper))
42 | end
43 |
44 | function LatinChars:get_length()
45 | return #self._bytes
46 | end
47 |
48 | function LatinChars:find(pattern, from)
49 | return sfind(self._bytes, pattern, from)
50 | end
51 |
52 | -- Decodes `bytes` as UTF8. Returns arrays of codepoints as integers and their byte offsets.
53 | -- Byte offsets have one extra item pointing to one byte past the end of `bytes`.
54 | -- On decoding error returns nothing.
55 | local function get_codepoints_and_byte_offsets(bytes)
56 | local codepoints = {}
57 | local byte_offsets = {}
58 |
59 | local byte_index = 1
60 | local codepoint_index = 1
61 |
62 | while true do
63 | byte_offsets[codepoint_index] = byte_index
64 |
65 | -- Attempt to decode the next codepoint from UTF8.
66 | local codepoint = sbyte(bytes, byte_index)
67 |
68 | if not codepoint then
69 | return codepoints, byte_offsets
70 | end
71 |
72 | byte_index = byte_index + 1
73 |
74 | if codepoint >= 0x80 then
75 | -- Not ASCII.
76 |
77 | if codepoint < 0xC0 then
78 | return
79 | end
80 |
81 | local cont = (sbyte(bytes, byte_index) or 0) - 0x80
82 |
83 | if cont < 0 or cont >= 0x40 then
84 | return
85 | end
86 |
87 | byte_index = byte_index + 1
88 |
89 | if codepoint < 0xE0 then
90 | -- Two bytes.
91 | codepoint = cont + (codepoint - 0xC0) * 0x40
92 | elseif codepoint < 0xF0 then
93 | -- Three bytes.
94 | codepoint = cont + (codepoint - 0xE0) * 0x40
95 |
96 | cont = (sbyte(bytes, byte_index) or 0) - 0x80
97 |
98 | if cont < 0 or cont >= 0x40 then
99 | return
100 | end
101 |
102 | byte_index = byte_index + 1
103 |
104 | codepoint = cont + codepoint * 0x40
105 | elseif codepoint < 0xF8 then
106 | -- Four bytes.
107 | codepoint = cont + (codepoint - 0xF0) * 0x40
108 |
109 | cont = (sbyte(bytes, byte_index) or 0) - 0x80
110 |
111 | if cont < 0 or cont >= 0x40 then
112 | return
113 | end
114 |
115 | byte_index = byte_index + 1
116 |
117 | codepoint = cont + codepoint * 0x40
118 |
119 | cont = (sbyte(bytes, byte_index) or 0) - 0x80
120 |
121 | if cont < 0 or cont >= 0x40 then
122 | return
123 | end
124 |
125 | byte_index = byte_index + 1
126 |
127 | codepoint = cont + codepoint * 0x40
128 |
129 | if codepoint > 0x10FFFF then
130 | return
131 | end
132 | else
133 | return
134 | end
135 | end
136 |
137 | codepoints[codepoint_index] = codepoint
138 | codepoint_index = codepoint_index + 1
139 | end
140 | end
141 |
142 | -- `UnicodeChars` is the general case for non-latin1 strings.
143 | -- Assumes UTF8, on decoding error falls back to latin1.
144 | local UnicodeChars = utils.class()
145 |
146 | function UnicodeChars:__init(bytes, codepoints, byte_offsets)
147 | self._bytes = bytes
148 | self._codepoints = codepoints
149 | self._byte_offsets = byte_offsets
150 | end
151 |
152 | function UnicodeChars:get_codepoint(index)
153 | return self._codepoints[index]
154 | end
155 |
156 | function UnicodeChars:get_substring(from, to)
157 | local byte_offsets = self._byte_offsets
158 | return ssub(self._bytes, byte_offsets[from], byte_offsets[to + 1] - 1)
159 | end
160 |
161 | function UnicodeChars:get_printable_substring(from, to)
162 | -- This is only called on syntax error, it's okay to be slow.
163 | local parts = {}
164 |
165 | for index = from, to do
166 | local codepoint = self._codepoints[index]
167 |
168 | if unicode.is_printable(codepoint) then
169 | table.insert(parts, self:get_substring(index, index))
170 | else
171 | table.insert(parts, (codepoint > 255 and "\\u{%X}" or "\\x%02X"):format(codepoint))
172 | end
173 | end
174 |
175 | return table.concat(parts)
176 | end
177 |
178 | function UnicodeChars:get_length()
179 | return #self._codepoints
180 | end
181 |
182 | function UnicodeChars:find(pattern, from)
183 | return sfind(self._bytes, pattern, self._byte_offsets[from])
184 | end
185 |
186 | function decoder.decode(bytes)
187 | -- Only use UnicodeChars if necessary. LatinChars isn't much faster but noticeably more memory efficient.
188 | if sfind(bytes, "[\128-\255]") then
189 | local codepoints, byte_offsets = get_codepoints_and_byte_offsets(bytes)
190 |
191 | if codepoints then
192 | return UnicodeChars(bytes, codepoints, byte_offsets)
193 | end
194 | end
195 |
196 | return LatinChars(bytes)
197 | end
198 |
199 | return decoder
200 |
--------------------------------------------------------------------------------
/src/luacheck/expand_rockspec.lua:
--------------------------------------------------------------------------------
1 | local fs = require "luacheck.fs"
2 | local utils = require "luacheck.utils"
3 |
4 | local blacklist = utils.array_to_set({"spec", ".luarocks", "lua_modules", "test.lua", "tests.lua"})
5 |
6 | -- This reimplements relevant parts of `luarocks.build.builtin.autodetect_modules`.
7 | -- Autodetection works relatively to the directory containing the rockspec.
8 | local function autodetect_modules(rockspec_path)
9 | rockspec_path = fs.normalize(rockspec_path)
10 | local base, rest = fs.split_base(rockspec_path)
11 | local project_dir = base .. (rest:match("^(.*)" .. utils.dir_sep .. ".*$") or "")
12 |
13 | if project_dir == "" then
14 | project_dir = "."
15 | end
16 |
17 | local module_dir = project_dir
18 |
19 | for _, module_subdir in ipairs({"src", "lua", "lib"}) do
20 | local full_module_dir = fs.join(project_dir, module_subdir)
21 |
22 | if fs.is_dir(full_module_dir) then
23 | module_dir = full_module_dir
24 | break
25 | end
26 | end
27 |
28 | local res = {}
29 |
30 | for _, file in ipairs((fs.extract_files(module_dir, "%.lua$"))) do
31 | -- Extract first part of the path from module_dir to the file, or file name itself.
32 | if not blacklist[file:match("^" .. module_dir:gsub("%p", "%%%0") .. "[\\/]*([^\\/]*)")] then
33 | table.insert(res, file)
34 | end
35 | end
36 |
37 | local bin_dir
38 |
39 | for _, bin_subdir in ipairs({"src/bin", "bin"}) do
40 | local full_bin_dir = fs.join(project_dir, bin_subdir)
41 |
42 | if fs.is_dir(full_bin_dir) then
43 | bin_dir = full_bin_dir
44 | end
45 | end
46 |
47 | if bin_dir then
48 | local iter, state, var = fs.dir_iter(bin_dir)
49 |
50 | if iter then
51 | for basename in iter, state, var do
52 | if basename:sub(-#".lua") == ".lua" then
53 | table.insert(res, fs.join(bin_dir, basename))
54 | end
55 | end
56 | end
57 | end
58 |
59 | return res
60 | end
61 |
62 | local function extract_lua_files(rockspec_path, rockspec)
63 | local build = rockspec.build
64 |
65 | if type(build) ~= "table" then
66 | return autodetect_modules(rockspec_path)
67 | end
68 |
69 | if not build.type or build.type == "builtin" or build.type == "module" then
70 | if not build.modules then
71 | return autodetect_modules(rockspec_path)
72 | end
73 | end
74 |
75 | local res = {}
76 |
77 | local function scan(t)
78 | if type(t) == "table" then
79 | for _, file in pairs(t) do
80 | if type(file) == "string" and file:sub(-#".lua") == ".lua" then
81 | table.insert(res, file)
82 | end
83 | end
84 | end
85 | end
86 |
87 | scan(build.modules)
88 |
89 | if type(build.install) == "table" then
90 | scan(build.install.lua)
91 | scan(build.install.bin)
92 | end
93 |
94 | table.sort(res)
95 | return res
96 | end
97 |
98 | -- Receives a name of a rockspec, returns list of related .lua files.
99 | -- On error returns nil and "I/O", "syntax", or "runtime" and error message.
100 | local function expand_rockspec(rockspec_path)
101 | local rockspec, err_type, err_msg = utils.load_config(rockspec_path)
102 |
103 | if not rockspec then
104 | return nil, err_type, err_msg
105 | end
106 |
107 | return extract_lua_files(rockspec_path, rockspec)
108 | end
109 |
110 | return expand_rockspec
111 |
--------------------------------------------------------------------------------
/src/luacheck/fs.lua:
--------------------------------------------------------------------------------
1 | local fs = {}
2 |
3 | local lfs = require "lfs"
4 | local utils = require "luacheck.utils"
5 |
6 | local function ensure_dir_sep(path)
7 | if path:sub(-1) ~= utils.dir_sep then
8 | return path .. utils.dir_sep
9 | end
10 |
11 | return path
12 | end
13 |
14 | function fs.split_base(path)
15 | if utils.is_windows then
16 | if path:match("^%a:\\") then
17 | return path:sub(1, 3), path:sub(4)
18 | else
19 | -- Disregard UNC paths and relative paths with drive letter.
20 | return "", path
21 | end
22 | else
23 | if path:match("^/") then
24 | if path:match("^//") then
25 | return "//", path:sub(3)
26 | else
27 | return "/", path:sub(2)
28 | end
29 | else
30 | return "", path
31 | end
32 | end
33 | end
34 |
35 | function fs.is_absolute(path)
36 | return fs.split_base(path) ~= ""
37 | end
38 |
39 | function fs.normalize(path)
40 | if utils.is_windows then
41 | path = path:lower()
42 | end
43 | local base, rest = fs.split_base(path)
44 | rest = rest:gsub("[/\\]", utils.dir_sep)
45 |
46 | local parts = {}
47 |
48 | for part in rest:gmatch("[^"..utils.dir_sep.."]+") do
49 | if part ~= "." then
50 | if part == ".." and #parts > 0 and parts[#parts] ~= ".." then
51 | parts[#parts] = nil
52 | else
53 | parts[#parts + 1] = part
54 | end
55 | end
56 | end
57 |
58 | if base == "" and #parts == 0 then
59 | return "."
60 | else
61 | return base..table.concat(parts, utils.dir_sep)
62 | end
63 | end
64 |
65 | local function join_two_paths(base, path)
66 | if base == "" or fs.is_absolute(path) then
67 | return path
68 | else
69 | return ensure_dir_sep(base) .. path
70 | end
71 | end
72 |
73 | function fs.join(base, ...)
74 | local res = base
75 |
76 | for i = 1, select("#", ...) do
77 | res = join_two_paths(res, select(i, ...))
78 | end
79 |
80 | return res
81 | end
82 |
83 | function fs.is_subpath(path, subpath)
84 | local base1, rest1 = fs.split_base(path)
85 | local base2, rest2 = fs.split_base(subpath)
86 |
87 | if base1 ~= base2 then
88 | return false
89 | end
90 |
91 | if rest2:sub(1, #rest1) ~= rest1 then
92 | return false
93 | end
94 |
95 | return rest1 == rest2 or rest2:sub(#rest1 + 1, #rest1 + 1) == utils.dir_sep
96 | end
97 |
98 | function fs.is_dir(path)
99 | return lfs.attributes(path, "mode") == "directory"
100 | end
101 |
102 | function fs.is_file(path)
103 | return lfs.attributes(path, "mode") == "file"
104 | end
105 |
106 | -- Searches for file starting from path, going up until the file
107 | -- is found or root directory is reached.
108 | -- Path must be absolute.
109 | -- Returns absolute and relative paths to directory containing file or nil.
110 | function fs.find_file(path, file)
111 | if fs.is_absolute(file) then
112 | return fs.is_file(file) and path, ""
113 | end
114 |
115 | path = fs.normalize(path)
116 | local base, rest = fs.split_base(path)
117 | local rel_path = ""
118 |
119 | while true do
120 | if fs.is_file(fs.join(base..rest, file)) then
121 | return base..rest, rel_path
122 | elseif rest == "" then
123 | return
124 | end
125 |
126 | rest = rest:match("^(.*)"..utils.dir_sep..".*$") or ""
127 | rel_path = rel_path..".."..utils.dir_sep
128 | end
129 | end
130 |
131 | -- Returns iterator over directory items or nil, error message.
132 | function fs.dir_iter(dir_path)
133 | local ok, iter, state, var = pcall(lfs.dir, dir_path)
134 |
135 | if not ok then
136 | local err = utils.unprefix(iter, "cannot open " .. dir_path .. ": ")
137 | return nil, "couldn't list directory: " .. err
138 | end
139 |
140 | return iter, state, var
141 | end
142 |
143 | -- Returns list of all files in directory matching pattern.
144 | -- Additionally returns a mapping from directory paths that couldn't be expanded
145 | -- to error messages.
146 | function fs.extract_files(dir_path, pattern)
147 | local res = {}
148 | local err_map = {}
149 |
150 | local function scan(dir)
151 | local iter, state, var = fs.dir_iter(dir)
152 |
153 | if not iter then
154 | err_map[dir] = state
155 | table.insert(res, dir)
156 | return
157 | end
158 |
159 | for path in iter, state, var do
160 | if path ~= "." and path ~= ".." then
161 | local full_path = fs.join(dir, path)
162 |
163 | if fs.is_dir(full_path) then
164 | scan(full_path)
165 | elseif path:match(pattern) and fs.is_file(full_path) then
166 | table.insert(res, full_path)
167 | end
168 | end
169 | end
170 | end
171 |
172 | scan(dir_path)
173 | table.sort(res)
174 | return res, err_map
175 | end
176 |
177 | local function make_absolute_dirs(dir_path)
178 | if fs.is_dir(dir_path) then
179 | return true
180 | end
181 |
182 | local upper_dir = fs.normalize(fs.join(dir_path, ".."))
183 |
184 | if upper_dir == dir_path then
185 | return nil, ("Filesystem root %s is not a directory"):format(upper_dir)
186 | end
187 |
188 | local upper_ok, upper_err = make_absolute_dirs(upper_dir)
189 |
190 | if not upper_ok then
191 | return nil, upper_err
192 | end
193 |
194 | local make_ok, make_error = lfs.mkdir(dir_path)
195 |
196 | if not make_ok then
197 | return nil, ("Couldn't make directory %s: %s"):format(dir_path, make_error)
198 | end
199 |
200 | return true
201 | end
202 |
203 | -- Ensures that a given path is a directory, creating intermediate directories if necessary.
204 | -- Returns true on success, nil and an error message on failure.
205 | function fs.make_dirs(dir_path)
206 | return make_absolute_dirs(fs.normalize(fs.join(fs.get_current_dir(), dir_path)))
207 | end
208 |
209 | -- Returns modification time for a file.
210 | function fs.get_mtime(path)
211 | return lfs.attributes(path, "modification")
212 | end
213 |
214 | -- Returns absolute path to current working directory, with trailing directory separator.
215 | function fs.get_current_dir()
216 | return ensure_dir_sep(assert(lfs.currentdir()))
217 | end
218 |
219 | return fs
220 |
--------------------------------------------------------------------------------
/src/luacheck/globbing.lua:
--------------------------------------------------------------------------------
1 | local fs = require "luacheck.fs"
2 | local utils = require "luacheck.utils"
3 |
4 | -- Only ?, *, ** and simple character classes (with ranges and negation) are supported.
5 | -- Hidden files are not treated specially. Special characters can't be escaped.
6 | local globbing = {}
7 |
8 | local function is_regular_path(glob)
9 | return not glob:find("[*?%[]")
10 | end
11 |
12 | local function get_parts(path)
13 | local parts = {}
14 |
15 | for part in path:gmatch("[^"..utils.dir_sep.."]+") do
16 | table.insert(parts, part)
17 | end
18 |
19 | return parts
20 | end
21 |
22 | local function glob_pattern_escaper(c)
23 | return ((c == "*" or c == "?") and "." or "%")..c
24 | end
25 |
26 | local function glob_range_escaper(c)
27 | return c == "-" and c or ("%"..c)
28 | end
29 |
30 | local function glob_part_to_pattern(glob_part)
31 | local buffer = {"^"}
32 | local i = 1
33 |
34 | while i <= #glob_part do
35 | local bracketless
36 | bracketless, i = glob_part:match("([^%[]*)()", i)
37 | table.insert(buffer, (bracketless:gsub("%p", glob_pattern_escaper)))
38 |
39 | if glob_part:sub(i, i) == "[" then
40 | table.insert(buffer, "[")
41 | i = i + 1
42 | local first_char = glob_part:sub(i, i)
43 |
44 | if first_char == "!" then
45 | table.insert(buffer, "^")
46 | i = i + 1
47 | elseif first_char == "]" then
48 | table.insert(buffer, "%]")
49 | i = i + 1
50 | end
51 |
52 | bracketless, i = glob_part:match("([^%]]*)()", i)
53 |
54 | if bracketless:sub(1, 1) == "-" then
55 | table.insert(buffer, "%-")
56 | bracketless = bracketless:sub(2)
57 | end
58 |
59 | local last_dash = ""
60 |
61 | if bracketless:sub(-1) == "-" then
62 | last_dash = "-"
63 | bracketless = bracketless:sub(1, -2)
64 | end
65 |
66 | table.insert(buffer, (bracketless:gsub("%p", glob_range_escaper)))
67 | table.insert(buffer, last_dash.."]")
68 | i = i + 1
69 | end
70 | end
71 |
72 | table.insert(buffer, "$")
73 | return table.concat(buffer)
74 | end
75 |
76 | local function part_match(glob_part, path_part)
77 | return utils.pmatch(path_part, glob_part_to_pattern(glob_part))
78 | end
79 |
80 | local function parts_match(glob_parts, glob_i, path_parts, path_i)
81 | local glob_part = glob_parts[glob_i]
82 |
83 | if not glob_part then
84 | -- Reached glob end, path matches the glob or its subdirectory.
85 | -- E.g. path "foo/bar/baz/src.lua" matches glob "foo/*/baz".
86 | return true
87 | end
88 |
89 | if glob_part == "**" then
90 | -- "**" can consume any number of path parts.
91 | for i = path_i, #path_parts + 1 do
92 | if parts_match(glob_parts, glob_i + 1, path_parts, i) then
93 | return true
94 | end
95 | end
96 |
97 | return false
98 | end
99 |
100 | local path_part = path_parts[path_i]
101 | return path_part and part_match(glob_part, path_part) and (
102 | parts_match(glob_parts, glob_i + 1, path_parts, path_i + 1))
103 | end
104 |
105 | -- Checks if a path matches a globbing pattern.
106 | -- Both must be absolute.
107 | function globbing.match(glob, path)
108 | if is_regular_path(glob) then
109 | return fs.is_subpath(glob, path)
110 | end
111 |
112 | local glob_base, path_base
113 | glob_base, glob = fs.split_base(glob)
114 | path_base, path = fs.split_base(path)
115 |
116 | if glob_base ~= path_base then
117 | return false
118 | end
119 |
120 | local glob_parts = get_parts(glob)
121 | local path_parts = get_parts(path)
122 | return parts_match(glob_parts, 1, path_parts, 1)
123 | end
124 |
125 | -- Checks if glob1 is less specific than glob2 and should be applied
126 | -- first in overrides.
127 | function globbing.compare(glob1, glob2)
128 | local base1, base2
129 | base1, glob1 = fs.split_base(glob1)
130 | base2, glob2 = fs.split_base(glob2)
131 |
132 | if base1 ~= base2 then
133 | return base1 < base2
134 | end
135 |
136 | local parts1 = get_parts(glob1)
137 | local parts2 = get_parts(glob2)
138 |
139 | for i = 1, math.max(#parts1, #parts2) do
140 | if not parts1[i] then
141 | return true
142 | elseif not parts2[i] then
143 | return false
144 | end
145 |
146 | if (parts1[i] == "**" or parts2[i] == "**") and parts1[i] ~= parts2[i] then
147 | return parts1[i] == "**"
148 | end
149 |
150 | local _, specials1 = parts1[i]:gsub("[%*%?%[]", {})
151 | local _, specials2 = parts2[i]:gsub("[%*%?%[]", {})
152 |
153 | if specials1 ~= specials2 then
154 | return specials1 > specials2
155 | end
156 | end
157 |
158 | return glob1 < glob2
159 | end
160 |
161 | return globbing
162 |
--------------------------------------------------------------------------------
/src/luacheck/init.lua:
--------------------------------------------------------------------------------
1 | local check = require "luacheck.check"
2 | local filter = require "luacheck.filter"
3 | local options = require "luacheck.options"
4 | local format = require "luacheck.format"
5 | local utils = require "luacheck.utils"
6 |
7 | local luacheck = {
8 | _VERSION = "0.23.0"
9 | }
10 |
11 | local function raw_validate_options(fname, opts, stds, context)
12 | local ok, err = options.validate(options.all_options, opts, stds)
13 |
14 | if not ok then
15 | if context then
16 | error(("bad argument #2 to '%s' (%s: %s)"):format(fname, context, err))
17 | else
18 | error(("bad argument #2 to '%s' (%s)"):format(fname, err))
19 | end
20 | end
21 | end
22 |
23 | local function validate_options(fname, items, opts, stds)
24 | raw_validate_options(fname, opts)
25 |
26 | if opts ~= nil then
27 | for i in ipairs(items) do
28 | raw_validate_options(fname, opts[i], stds, ("invalid options at index [%d]"):format(i))
29 |
30 | if opts[i] ~= nil then
31 | for j, nested_opts in ipairs(opts[i]) do
32 | raw_validate_options(fname, nested_opts, stds, ("invalid options at index [%d][%d]"):format(i, j))
33 | end
34 | end
35 | end
36 | end
37 | end
38 |
39 | -- Returns report for a string.
40 | function luacheck.get_report(src)
41 | local msg = ("bad argument #1 to 'luacheck.get_report' (string expected, got %s)"):format(type(src))
42 | assert(type(src) == "string", msg)
43 | return check(src)
44 | end
45 |
46 | -- Applies options to reports. Reports with .fatal field are unchanged.
47 | -- Options are applied to reports[i] in order: options, options[i], options[i][1], options[i][2], ...
48 | -- Returns new array of reports, adds .warnings, .errors and .fatals fields to this array.
49 | function luacheck.process_reports(reports, opts, stds)
50 | local msg = ("bad argument #1 to 'luacheck.process_reports' (table expected, got %s)"):format(type(reports))
51 | assert(type(reports) == "table", msg)
52 | validate_options("luacheck.process_reports", reports, opts, stds)
53 | local report = filter.filter(reports, opts, stds)
54 | report.warnings = 0
55 | report.errors = 0
56 | report.fatals = 0
57 |
58 | for _, file_report in ipairs(report) do
59 | if file_report.fatal then
60 | report.fatals = report.fatals + 1
61 | else
62 | for _, event in ipairs(file_report) do
63 | if event.code:sub(1, 1) == "0" then
64 | report.errors = report.errors + 1
65 | else
66 | report.warnings = report.warnings + 1
67 | end
68 | end
69 | end
70 | end
71 |
72 | return report
73 | end
74 |
75 | -- Checks strings with options, returns report.
76 | -- Tables with .fatal field are unchanged.
77 | function luacheck.check_strings(srcs, opts)
78 | local msg = ("bad argument #1 to 'luacheck.check_strings' (table expected, got %s)"):format(type(srcs))
79 | assert(type(srcs) == "table", msg)
80 |
81 | for _, item in ipairs(srcs) do
82 | msg = ("bad argument #1 to 'luacheck.check_strings' (array of strings or tables expected, got %s)"):format(
83 | type(item))
84 | assert(type(item) == "string" or type(item) == "table", msg)
85 | end
86 |
87 | validate_options("luacheck.check_strings", srcs, opts)
88 |
89 | local reports = {}
90 |
91 | for i, src in ipairs(srcs) do
92 | if type(src) == "table" and src.fatal then
93 | reports[i] = src
94 | else
95 | reports[i] = luacheck.get_report(src)
96 | end
97 | end
98 |
99 | return luacheck.process_reports(reports, opts)
100 | end
101 |
102 | function luacheck.check_files(files, opts)
103 | local msg = ("bad argument #1 to 'luacheck.check_files' (table expected, got %s)"):format(type(files))
104 | assert(type(files) == "table", msg)
105 |
106 | for _, item in ipairs(files) do
107 | msg = ("bad argument #1 to 'luacheck.check_files' (array of paths or file handles expected, got %s)"):format(
108 | type(item))
109 | assert(type(item) == "string" or io.type(item) == "file", msg
110 | )
111 | end
112 |
113 | validate_options("luacheck.check_files", files, opts)
114 |
115 | local srcs = {}
116 |
117 | for i, file in ipairs(files) do
118 | local src, err = utils.read_file(file)
119 | srcs[i] = src or {fatal = "I/O", msg = err}
120 | end
121 |
122 | return luacheck.check_strings(srcs, opts)
123 | end
124 |
125 | function luacheck.get_message(issue)
126 | local msg = ("bad argument #1 to 'luacheck.get_message' (table expected, got %s)"):format(type(issue))
127 | assert(type(issue) == "table", msg)
128 | return format.get_message(issue)
129 | end
130 |
131 | setmetatable(luacheck, {__call = function(_, ...)
132 | return luacheck.check_files(...)
133 | end})
134 |
135 | return luacheck
136 |
--------------------------------------------------------------------------------
/src/luacheck/multithreading.lua:
--------------------------------------------------------------------------------
1 | local utils = require "luacheck.utils"
2 |
3 | local multithreading = {}
4 |
5 | local lanes_ok, lanes = pcall(require, "lanes")
6 | lanes_ok = lanes_ok and pcall(lanes.configure)
7 | multithreading.has_lanes = lanes_ok
8 | multithreading.lanes = lanes
9 | multithreading.default_jobs = 1
10 |
11 | if not lanes_ok then
12 | return multithreading
13 | end
14 |
15 | local cpu_number_detection_commands = {}
16 |
17 | if utils.is_windows then
18 | cpu_number_detection_commands[1] = "echo %NUMBER_OF_PROCESSORS%"
19 | else
20 | cpu_number_detection_commands[1] = "getconf _NPROCESSORS_ONLN 2>&1"
21 | cpu_number_detection_commands[2] = "sysctl -n hw.ncpu 2>&1"
22 | cpu_number_detection_commands[3] = "psrinfo -p 2>&1"
23 | end
24 |
25 | for _, command in ipairs(cpu_number_detection_commands) do
26 | local handler = io.popen(command)
27 |
28 | if handler then
29 | local output = handler:read("*a")
30 | handler:close()
31 |
32 | if output then
33 | local cpu_number = tonumber(utils.strip(output))
34 |
35 | if cpu_number then
36 | multithreading.default_jobs = math.floor(math.max(cpu_number, 1))
37 | break
38 | end
39 | end
40 | end
41 | end
42 |
43 | -- Reads pairs {key, arg} from given linda slot until it gets nil as arg.
44 | -- Returns table with pairs [key] = func(arg).
45 | local function worker_task(linda, input_slot, func)
46 | local results = {}
47 |
48 | while true do
49 | local _, pair = linda:receive(nil, input_slot)
50 | local key, arg = pair[1], pair[2]
51 |
52 | if arg == nil then
53 | return results
54 | end
55 |
56 | results[key] = func(arg)
57 | end
58 | end
59 |
60 | local function protected_worker_task(...)
61 | return true, utils.try(worker_task, ...)
62 | end
63 |
64 | local worker_gen = lanes.gen("*", protected_worker_task)
65 |
66 | -- Maps func over array, performing at most jobs calls in parallel.
67 | function multithreading.pmap(func, array, jobs)
68 | jobs = jobs or multithreading.default_jobs
69 | jobs = math.min(jobs, #array)
70 |
71 | if jobs < 2 then
72 | return utils.map(func, array)
73 | end
74 |
75 | local workers = {}
76 | local linda = lanes.linda()
77 |
78 | for i = 1, jobs do
79 | workers[i] = worker_gen(linda, 0, func)
80 | end
81 |
82 | for i, item in ipairs(array) do
83 | linda:send(nil, 0, {i, item})
84 | end
85 |
86 | for _ = 1, jobs do
87 | linda:send(nil, 0, {})
88 | end
89 |
90 | local results = {}
91 |
92 | for _, worker in ipairs(workers) do
93 | local _, ok, worker_results = assert(worker:join())
94 |
95 | if ok then
96 | utils.update(results, worker_results)
97 | else
98 | error(worker_results, 0)
99 | end
100 | end
101 |
102 | return results
103 | end
104 |
105 | return multithreading
106 |
--------------------------------------------------------------------------------
/src/luacheck/profiler.lua:
--------------------------------------------------------------------------------
1 | -- Require luasocket only when needed.
2 | local socket
3 |
4 | local profiler = {}
5 |
6 | local metrics = {
7 | {name = "Wall", get = function() return socket.gettime() end},
8 | {name = "CPU", get = os.clock},
9 | {name = "Memory", get = function() return collectgarbage("count") end}
10 | }
11 |
12 | local functions = {
13 | {name = "sha1", module = "vendor.sha1"},
14 | {name = "load", module = "cache"},
15 | {name = "update", module = "cache"},
16 | {name = "decode", module = "decoder"},
17 | {name = "parse", module = "parser"},
18 | {name = "dump_check_result", module = "serializer"},
19 | {name = "load_check_result", module = "serializer"},
20 | {name = "run", module = "stages.unwrap_parens"},
21 | {name = "run", module = "stages.parse_inline_options"},
22 | {name = "run", module = "stages.linearize"},
23 | {name = "run", module = "stages.name_functions"},
24 | {name = "run", module = "stages.resolve_locals"},
25 | {name = "run", module = "stages.detect_bad_whitespace"},
26 | {name = "run", module = "stages.detect_cyclomatic_complexity"},
27 | {name = "run", module = "stages.detect_empty_blocks"},
28 | {name = "run", module = "stages.detect_empty_statements"},
29 | {name = "run", module = "stages.detect_globals"},
30 | {name = "run", module = "stages.detect_reversed_fornum_loops"},
31 | {name = "run", module = "stages.detect_unbalanced_assignments"},
32 | {name = "run", module = "stages.detect_uninit_accesses"},
33 | {name = "run", module = "stages.detect_unreachable_code"},
34 | {name = "run", module = "stages.detect_unused_fields"},
35 | {name = "run", module = "stages.detect_unused_locals"},
36 | {name = "filter", module = "filter"},
37 | {name = "normalize", module = "options"}
38 | }
39 |
40 | local stats = {}
41 | local start_values = {}
42 |
43 | local function start_phase(name)
44 | for _, metric in ipairs(metrics) do
45 | start_values[metric][name] = metric.get()
46 | end
47 | end
48 |
49 | local function stop_phase(name)
50 | for _, metric in ipairs(metrics) do
51 | local increment = metric.get() - start_values[metric][name]
52 | stats[metric][name] = (stats[metric][name] or 0) + increment
53 | end
54 | end
55 |
56 | local phase_stack = {}
57 |
58 | local function push_phase(name)
59 | local prev_name = phase_stack[#phase_stack]
60 |
61 | if prev_name then
62 | stop_phase(prev_name)
63 | end
64 |
65 | table.insert(phase_stack, name)
66 | start_phase(name)
67 | end
68 |
69 | local function pop_phase(name)
70 | assert(name == table.remove(phase_stack))
71 | stop_phase(name)
72 | local prev_name = phase_stack[#phase_stack]
73 |
74 | if prev_name then
75 | start_phase(prev_name)
76 | end
77 | end
78 |
79 | local function continue_wrapper(name, ...)
80 | pop_phase(name)
81 | return ...
82 | end
83 |
84 | local function wrap(fn, name)
85 | return function(...)
86 | push_phase(name)
87 | return continue_wrapper(name, fn(...))
88 | end
89 | end
90 |
91 | local function patch(fn)
92 | local mod = require("luacheck." .. fn.module)
93 | local orig = mod[fn.name]
94 | local new = wrap(orig, fn.module .. "." .. fn.name)
95 | mod[fn.name] = new
96 | end
97 |
98 | function profiler.init()
99 | socket = require "socket"
100 | collectgarbage("stop")
101 |
102 | for _, metric in ipairs(metrics) do
103 | stats[metric] = {}
104 | start_values[metric] = {}
105 | end
106 |
107 | for _, fn in ipairs(functions) do
108 | patch(fn)
109 | end
110 |
111 | push_phase("other")
112 | end
113 |
114 | function profiler.report()
115 | pop_phase("other")
116 |
117 | for _, metric in ipairs(metrics) do
118 | local names = {}
119 | local total = 0
120 |
121 | for name, value in pairs(stats[metric]) do
122 | table.insert(names, name)
123 | total = total + value
124 | end
125 |
126 | table.sort(names, function(name1, name2)
127 | local stats1 = stats[metric][name1]
128 | local stats2 = stats[metric][name2]
129 |
130 | if stats1 ~= stats2 then
131 | return stats1 > stats2
132 | else
133 | return name1 < name2
134 | end
135 | end)
136 |
137 | print(metric.name)
138 | print()
139 |
140 | for _, name in ipairs(names) do
141 | print(("%s - %f (%f%%)"):format(name, stats[metric][name], stats[metric][name] / total * 100))
142 | end
143 |
144 | print(("Total - %f"):format(total))
145 | print()
146 | end
147 | end
148 |
149 | return profiler
150 |
--------------------------------------------------------------------------------
/src/luacheck/serializer.lua:
--------------------------------------------------------------------------------
1 | local parse_inline_options = require "luacheck.stages.parse_inline_options"
2 | local stages = require "luacheck.stages"
3 | local utils = require "luacheck.utils"
4 |
5 | local serializer = {}
6 |
7 | local option_fields = {
8 | "ignore", "std", "globals", "unused_args", "self", "compat", "global", "unused", "redefined",
9 | "unused_secondaries", "allow_defined", "allow_defined_top", "module",
10 | "read_globals", "new_globals", "new_read_globals", "enable", "only", "not_globals",
11 | "max_line_length", "max_code_line_length", "max_string_line_length", "max_comment_line_length",
12 | "max_cyclomatic_complexity"
13 | }
14 |
15 | local function compress_table(t, fields)
16 | local res = {}
17 |
18 | for index, field in ipairs(fields) do
19 | local value = t[field]
20 |
21 | if value ~= nil then
22 | if field == "options" then
23 | value = compress_table(value, option_fields)
24 | end
25 |
26 | res[index] = value
27 | end
28 | end
29 |
30 | return res
31 | end
32 |
33 | local function compress_tables(tables, per_code_fields)
34 | local res = {}
35 |
36 | for _, t in ipairs(tables) do
37 | local fields = per_code_fields and stages.warnings[t.code].fields or parse_inline_options.inline_option_fields
38 | table.insert(res, compress_table(t, fields))
39 | end
40 |
41 | return res
42 | end
43 |
44 | local function compress_result(result)
45 | local res = {}
46 | res[1] = compress_tables(result.warnings, true)
47 | res[2] = compress_tables(result.inline_options)
48 | res[3] = result.line_lengths
49 | res[4] = result.line_endings
50 | return res
51 | end
52 |
53 | local function decompress_table(t, fields)
54 | local res = {}
55 |
56 | for index, field in ipairs(fields) do
57 | local value = t[index]
58 |
59 | if value ~= nil then
60 | if field == "options" then
61 | value = decompress_table(value, option_fields)
62 | end
63 |
64 | res[field] = value
65 | end
66 | end
67 |
68 | return res
69 | end
70 |
71 | local function decompress_tables(tables, per_code_fields)
72 | local res = {}
73 |
74 | for _, t in ipairs(tables) do
75 | local fields
76 |
77 | if per_code_fields then
78 | fields = stages.warnings[t[1]].fields
79 | else
80 | fields = parse_inline_options.inline_option_fields
81 | end
82 |
83 | table.insert(res, decompress_table(t, fields))
84 | end
85 |
86 | return res
87 | end
88 |
89 | local function decompress_result(compressed)
90 | local result = {}
91 | result.warnings = decompress_tables(compressed[1], true)
92 | result.inline_options = decompress_tables(compressed[2])
93 | result.line_lengths = compressed[3]
94 | result.line_endings = compressed[4]
95 | return result
96 | end
97 |
98 | local function get_local_name(index)
99 | return string.char(index + (index > 26 and 70 or 64))
100 | end
101 |
102 | local function max_n(t)
103 | local res = 0
104 |
105 | for k in pairs(t) do
106 | res = math.max(res, k)
107 | end
108 |
109 | return res
110 | end
111 |
112 | -- Serializes a value into buffer.
113 | -- `strings` is a table mapping string values to where they first occured or to name of local
114 | -- variable used to represent it.
115 | -- Array part contains representations of values saved into locals.
116 | local function add_value(buffer, strings, value, level)
117 | if type(value) == "string" then
118 | local prev = strings[value]
119 |
120 | if type(prev) == "string" then
121 | -- There is a local with such value.
122 | table.insert(buffer, prev)
123 | elseif type(prev) == "number" and #strings < 52 then
124 | -- Value is used second time, put it into a local.
125 | table.insert(strings, ("%q"):format(value))
126 | local local_name = get_local_name(#strings)
127 | buffer[prev] = local_name
128 | table.insert(buffer, local_name)
129 | strings[value] = local_name
130 | else
131 | table.insert(buffer, ("%q"):format(value))
132 | strings[value] = #buffer
133 | end
134 | elseif type(value) == "table" then
135 | -- Level 1 has the result, level 2 has warning/inline option/line info arrays,
136 | -- level 3 has warnings/inline option containers, level 4 has inline options.
137 | local allow_sparse = level ~= 3
138 | local nil_tail_start
139 | local is_sparse
140 | local put_one
141 | table.insert(buffer, "{")
142 |
143 | for index = 1, max_n(value) do
144 | local item = value[index]
145 |
146 | if item == nil then
147 | is_sparse = allow_sparse
148 | nil_tail_start = nil_tail_start or index
149 | else
150 | if put_one then
151 | table.insert(buffer, ",")
152 | end
153 |
154 | if is_sparse then
155 | table.insert(buffer, ("[%d]="):format(index))
156 | elseif nil_tail_start then
157 | for _ = nil_tail_start, index - 1 do
158 | table.insert(buffer, "nil,")
159 | end
160 |
161 | nil_tail_start = nil
162 | end
163 |
164 | add_value(buffer, strings, item, level + 1)
165 | put_one = true
166 | end
167 | end
168 |
169 | table.insert(buffer, "}")
170 | else
171 | table.insert(buffer, tostring(value))
172 | end
173 | end
174 |
175 | -- Serializes check result, returns a string.
176 | function serializer.dump_check_result(result)
177 | local strings = {}
178 | local buffer = {"", "return "}
179 | add_value(buffer, strings, compress_result(result), 1)
180 |
181 | if strings[1] then
182 | local names = {}
183 |
184 | for index in ipairs(strings) do
185 | table.insert(names, get_local_name(index))
186 | end
187 |
188 | buffer[1] = "local " .. table.concat(names, ",") .. "=" .. table.concat(strings, ",") .. ";"
189 | end
190 |
191 | return table.concat(buffer)
192 | end
193 |
194 | -- Loads check result from a string, returns check result table or nothing on error.
195 | function serializer.load_check_result(dumped)
196 | local func = utils.load(dumped, {})
197 |
198 | if not func then
199 | return
200 | end
201 |
202 | local ok, compressed_result = pcall(func)
203 |
204 | if not ok or type(compressed_result) ~= "table" then
205 | return
206 | end
207 |
208 | return decompress_result(compressed_result)
209 | end
210 |
211 | return serializer
212 |
--------------------------------------------------------------------------------
/src/luacheck/stages/detect_bad_whitespace.lua:
--------------------------------------------------------------------------------
1 | local stage = {}
2 |
3 | stage.warnings = {
4 | ["611"] = {message_format = "line contains only whitespace", fields = {}},
5 | ["612"] = {message_format = "line contains trailing whitespace", fields = {}},
6 | ["613"] = {message_format = "trailing whitespace in a string", fields = {}},
7 | ["614"] = {message_format = "trailing whitespace in a comment", fields = {}},
8 | ["621"] = {message_format = "inconsistent indentation (SPACE followed by TAB)", fields = {}}
9 | }
10 |
11 | function stage.run(chstate)
12 | local num_lines = #chstate.line_offsets
13 |
14 | for line_number = 1, num_lines do
15 | local line_offset = chstate.line_offsets[line_number]
16 | local line_length = chstate.line_lengths[line_number]
17 |
18 | if line_length > 0 then
19 | local trailing_ws_pattern
20 |
21 | if line_number == num_lines then
22 | trailing_ws_pattern = "^[^\r\n]-()[ \t\f\v]+()[\r\n]?$"
23 | else
24 | trailing_ws_pattern = "^[^\r\n]-()[ \t\f\v]+()[\r\n]"
25 | end
26 |
27 | local line_start_byte, _, trailing_ws_start_byte, line_end_byte = chstate.source:find(
28 | trailing_ws_pattern, line_offset)
29 |
30 | local trailing_ws_code
31 |
32 | if trailing_ws_start_byte then
33 |
34 | if trailing_ws_start_byte == line_start_byte then
35 | -- Line contains only whitespace (thus never considered "code").
36 | trailing_ws_code = "611"
37 | elseif not chstate.line_endings[line_number] then
38 | -- Trailing whitespace on code line or after long comment.
39 | trailing_ws_code = "612"
40 | elseif chstate.line_endings[line_number] == "string" then
41 | -- Trailing whitespace embedded in a string literal.
42 | trailing_ws_code = "613"
43 | elseif chstate.line_endings[line_number] == "comment" then
44 | -- Trailing whitespace at the end of a line comment or inside long comment.
45 | trailing_ws_code = "614"
46 | end
47 |
48 | -- The difference between the start and the end of the warning range
49 | -- is the same in bytes and in characters because whitespace characters are ASCII.
50 | -- Can calculate one based on the three others.
51 | local trailing_ws_end_byte = line_end_byte - 1
52 | local trailing_ws_end_char = line_offset + line_length - 1
53 | local trailing_ws_start_char = trailing_ws_end_char - (trailing_ws_end_byte - trailing_ws_start_byte)
54 |
55 | chstate:warn(trailing_ws_code, line_number, trailing_ws_start_char, trailing_ws_end_char)
56 | end
57 |
58 | -- Don't look for inconsistent whitespace in pure whitespace lines.
59 | if trailing_ws_code ~= "611" then
60 | local leading_ws_start_byte, leading_ws_end_byte = chstate.source:find(
61 | "^[ \t\f\v]- \t[ \t\f\v]*", line_offset)
62 |
63 | if leading_ws_start_byte then
64 | -- Inconsistent leading whitespace (SPACE followed by TAB).
65 |
66 | -- Calculate warning end in characters using same logic as above.
67 | local leading_ws_start_char = line_offset
68 | local leading_ws_end_char = leading_ws_start_char + (leading_ws_end_byte - leading_ws_start_byte)
69 | chstate:warn("621", line_number, line_offset, leading_ws_end_char)
70 | end
71 | end
72 | end
73 | end
74 | end
75 |
76 | return stage
77 |
--------------------------------------------------------------------------------
/src/luacheck/stages/detect_cyclomatic_complexity.lua:
--------------------------------------------------------------------------------
1 | local utils = require "luacheck.utils"
2 |
3 | local stage = {}
4 |
5 | local function cyclomatic_complexity_message_format(warning)
6 | local template = "cyclomatic complexity of %s is too high ({complexity} > {max_complexity})"
7 |
8 | local function_descr
9 |
10 | if warning.function_type == "main_chunk" then
11 | function_descr = "main chunk"
12 | elseif warning.function_name then
13 | function_descr = "{function_type} {function_name!}"
14 | else
15 | function_descr = "function"
16 | end
17 |
18 | return template:format(function_descr)
19 | end
20 |
21 | stage.warnings = {
22 | ["561"] = {message_format = cyclomatic_complexity_message_format,
23 | fields = {"complexity", "function_type", "function_name"}}
24 | }
25 |
26 | local function warn_cyclomatic_complexity(chstate, line, complexity)
27 | if line == chstate.top_line then
28 | chstate:warn("561", 1, 1, 1, {
29 | complexity = complexity,
30 | function_type = "main_chunk"
31 | })
32 | else
33 | local node = line.node
34 |
35 | chstate:warn_range("561", node, {
36 | complexity = complexity,
37 | function_type = node[1][1] and node[1][1].implicit and "method" or "function",
38 | function_name = node.name
39 | })
40 | end
41 | end
42 |
43 | local CyclomaticComplexityMetric = utils.class()
44 |
45 | function CyclomaticComplexityMetric:incr_decisions(count)
46 | self.count = self.count + count
47 | end
48 |
49 | function CyclomaticComplexityMetric:calc_expr(node)
50 | if node.tag == "Op" and (node[1] == "and" or node[1] == "or") then
51 | self:incr_decisions(1)
52 | end
53 |
54 | if node.tag ~= "Function" then
55 | self:calc_exprs(node)
56 | end
57 | end
58 |
59 | function CyclomaticComplexityMetric:calc_exprs(exprs)
60 | for _, expr in ipairs(exprs) do
61 | if type(expr) == "table" then
62 | self:calc_expr(expr)
63 | end
64 | end
65 | end
66 |
67 | function CyclomaticComplexityMetric:calc_item_Eval(item)
68 | self:calc_expr(item.node)
69 | end
70 |
71 | function CyclomaticComplexityMetric:calc_item_Local(item)
72 | if item.rhs then
73 | self:calc_exprs(item.rhs)
74 | end
75 | end
76 |
77 | function CyclomaticComplexityMetric:calc_item_Set(item)
78 | self:calc_exprs(item.rhs)
79 | end
80 |
81 | function CyclomaticComplexityMetric:calc_item(item)
82 | local f = self["calc_item_" .. item.tag]
83 | if f then
84 | f(self, item)
85 | end
86 | end
87 |
88 | function CyclomaticComplexityMetric:calc_items(items)
89 | for _, item in ipairs(items) do
90 | self:calc_item(item)
91 | end
92 | end
93 |
94 | -- stmt if: {condition, block; condition, block; ... else_block}
95 | function CyclomaticComplexityMetric:calc_stmt_If(node)
96 | for i = 1, #node - 1, 2 do
97 | self:incr_decisions(1)
98 | self:calc_stmts(node[i+1])
99 | end
100 |
101 | if #node % 2 == 1 then
102 | self:calc_stmts(node[#node])
103 | end
104 | end
105 |
106 | -- stmt while: {condition, block}
107 | function CyclomaticComplexityMetric:calc_stmt_While(node)
108 | self:incr_decisions(1)
109 | self:calc_stmts(node[2])
110 | end
111 |
112 | -- stmt repeat: {block, condition}
113 | function CyclomaticComplexityMetric:calc_stmt_Repeat(node)
114 | self:incr_decisions(1)
115 | self:calc_stmts(node[1])
116 | end
117 |
118 | -- stmt forin: {iter_vars, expression_list, block}
119 | function CyclomaticComplexityMetric:calc_stmt_Forin(node)
120 | self:incr_decisions(1)
121 | self:calc_stmts(node[3])
122 | end
123 |
124 | -- stmt fornum: {first_var, expression, expression, expression[optional], block}
125 | function CyclomaticComplexityMetric:calc_stmt_Fornum(node)
126 | self:incr_decisions(1)
127 | self:calc_stmts(node[5] or node[4])
128 | end
129 |
130 | function CyclomaticComplexityMetric:calc_stmt(node)
131 | local f = self["calc_stmt_" .. node.tag]
132 | if f then
133 | f(self, node)
134 | end
135 | end
136 |
137 | function CyclomaticComplexityMetric:calc_stmts(stmts)
138 | for _, stmt in ipairs(stmts) do
139 | self:calc_stmt(stmt)
140 | end
141 | end
142 |
143 | -- Cyclomatic complexity of a function equals to the number of decision points plus 1.
144 | function CyclomaticComplexityMetric:report(chstate, line)
145 | self.count = 1
146 | self:calc_stmts(line.node[2])
147 | self:calc_items(line.items)
148 | warn_cyclomatic_complexity(chstate, line, self.count)
149 | end
150 |
151 | function stage.run(chstate)
152 | local ccmetric = CyclomaticComplexityMetric()
153 |
154 | for _, line in ipairs(chstate.lines) do
155 | ccmetric:report(chstate, line)
156 | end
157 | end
158 |
159 | return stage
160 |
--------------------------------------------------------------------------------
/src/luacheck/stages/detect_empty_blocks.lua:
--------------------------------------------------------------------------------
1 | local core_utils = require "luacheck.core_utils"
2 |
3 | local stage = {}
4 |
5 | stage.warnings = {
6 | ["541"] = {message_format = "empty do..end block", fields = {}},
7 | ["542"] = {message_format = "empty if branch", fields = {}}
8 | }
9 |
10 | local function check_block(chstate, block, code)
11 | if #block == 0 then
12 | chstate:warn_range(code, block)
13 | end
14 | end
15 |
16 |
17 | local function check_node(chstate, node)
18 | if node.tag == "Do" then
19 | check_block(chstate, node, "541")
20 | return
21 | end
22 |
23 | for index = 2, #node, 2 do
24 | check_block(chstate, node[index], "542")
25 | end
26 |
27 | if #node % 2 == 1 then
28 | check_block(chstate, node[#node], "542")
29 | end
30 | end
31 |
32 | function stage.run(chstate)
33 | core_utils.each_statement(chstate, {"Do", "If"}, check_node)
34 | end
35 |
36 | return stage
37 |
--------------------------------------------------------------------------------
/src/luacheck/stages/detect_empty_statements.lua:
--------------------------------------------------------------------------------
1 | local stage = {}
2 |
3 | stage.warnings = {
4 | ["551"] = {message_format = "empty statement", fields = {}}
5 | }
6 |
7 | function stage.run(chstate)
8 | for _, range in ipairs(chstate.useless_semicolons) do
9 | chstate:warn_range("551", range)
10 | end
11 | end
12 |
13 | return stage
14 |
--------------------------------------------------------------------------------
/src/luacheck/stages/detect_reversed_fornum_loops.lua:
--------------------------------------------------------------------------------
1 | local core_utils = require "luacheck.core_utils"
2 |
3 | local stage = {}
4 |
5 | stage.warnings = {
6 | ["571"] = {message_format = "numeric for loop goes from #(expr) down to {limit} but loop step is not negative",
7 | fields = {"limit"}}
8 | }
9 |
10 | local function check_fornum(chstate, node)
11 | if node[2].tag ~= "Op" or node[2][1] ~= "len" then
12 | return
13 | end
14 |
15 | local limit, limit_repr = core_utils.eval_const_node(node[3])
16 |
17 | if not limit or limit > 1 then
18 | return
19 | end
20 |
21 | local step = 1
22 |
23 | if node[5] then
24 | step = core_utils.eval_const_node(node[4])
25 | end
26 |
27 | if step and step >= 0 then
28 | chstate:warn_range("571", node, {
29 | limit = limit_repr
30 | })
31 | end
32 | end
33 |
34 | -- Warns about loops trying to go from `#(expr)` to `1` with positive step.
35 | function stage.run(chstate)
36 | core_utils.each_statement(chstate, {"Fornum"}, check_fornum)
37 | end
38 |
39 | return stage
40 |
--------------------------------------------------------------------------------
/src/luacheck/stages/detect_unbalanced_assignments.lua:
--------------------------------------------------------------------------------
1 | local core_utils = require "luacheck.core_utils"
2 |
3 | local stage = {}
4 |
5 | stage.warnings = {
6 | ["531"] = {message_format = "right side of assignment has more values than left side expects", fields = {}},
7 | ["532"] = {message_format = "right side of assignment has less values than left side expects", fields = {}}
8 | }
9 |
10 | local function is_unpacking(node)
11 | return node.tag == "Dots" or node.tag == "Call" or node.tag == "Invoke"
12 | end
13 |
14 | local function check_assignment(chstate, node)
15 | local rhs = node[2]
16 |
17 | if not rhs then
18 | return
19 | end
20 |
21 | local lhs = node[1]
22 |
23 | if #rhs > #lhs then
24 | chstate:warn_range("531", node)
25 | elseif #rhs < #lhs and node.tag == "Set" and not is_unpacking(rhs[#rhs]) then
26 | chstate:warn_range("532", node)
27 | end
28 | end
29 |
30 | function stage.run(chstate)
31 | core_utils.each_statement(chstate, {"Set", "Local"}, check_assignment)
32 | end
33 |
34 | return stage
35 |
--------------------------------------------------------------------------------
/src/luacheck/stages/detect_uninit_accesses.lua:
--------------------------------------------------------------------------------
1 | local stage = {}
2 |
3 | stage.warnings = {
4 | ["321"] = {message_format = "accessing uninitialized variable {name!}", fields = {"name"}},
5 | ["341"] = {message_format = "mutating uninitialized variable {name!}", fields = {"name"}}
6 | }
7 |
8 | local function detect_uninit_access_in_line(chstate, line)
9 | for _, item in ipairs(line.items) do
10 | for _, action_key in ipairs({"accesses", "mutations"}) do
11 | local code = action_key == "accesses" and "321" or "341"
12 | local item_var_map = item[action_key]
13 |
14 | if item_var_map then
15 | for var, accessing_nodes in pairs(item_var_map) do
16 | -- If there are no values at all reaching this access, not even the empty one,
17 | -- this item (or a closure containing it) is not reachable from variable definition.
18 | -- It will be reported as unreachable code, no need to report uninitalized accesses in it.
19 | if item.used_values[var] then
20 | -- If this variable is has only one, empty value then it's already reported as never set,
21 | -- no need to report each access.
22 | if not (#var.values == 1 and var.values[1].empty) then
23 | local all_possible_values_empty = true
24 |
25 | for _, possible_value in ipairs(item.used_values[var]) do
26 | if not possible_value.empty then
27 | all_possible_values_empty = false
28 | break
29 | end
30 | end
31 |
32 | if all_possible_values_empty then
33 | for _, accessing_node in ipairs(accessing_nodes) do
34 | chstate:warn_range(code, accessing_node, {
35 | name = accessing_node[1]
36 | })
37 | end
38 | end
39 | end
40 | end
41 | end
42 | end
43 | end
44 | end
45 | end
46 |
47 | -- Warns about accesses and mutations that don't resolve to any values except initial empty one.
48 | function stage.run(chstate)
49 | for _, line in ipairs(chstate.lines) do
50 | detect_uninit_access_in_line(chstate, line)
51 | end
52 | end
53 |
54 | return stage
55 |
--------------------------------------------------------------------------------
/src/luacheck/stages/detect_unreachable_code.lua:
--------------------------------------------------------------------------------
1 | local stage = {}
2 |
3 | stage.warnings = {
4 | ["511"] = {message_format = "unreachable code", fields = {}},
5 | ["512"] = {message_format = "loop is executed at most once", fields = {}}
6 | }
7 |
8 | local function noop_callback() end
9 |
10 | local function detect_unreachable_code(chstate, line)
11 | local reachable_indexes = {}
12 |
13 | -- Mark all items reachable from the function start.
14 | line:walk(reachable_indexes, 1, noop_callback)
15 |
16 | -- All remaining items are unreachable.
17 | -- However, there is no point in reporting all of them.
18 | -- Only report those that are not reachable from any already reported ones.
19 | for item_index, item in ipairs(line.items) do
20 | if not reachable_indexes[item_index] then
21 | if item.node then
22 | chstate:warn_range(item.loop_end and "512" or "511", item.node)
23 | -- Mark all items reachable from the item just reported.
24 | line:walk(reachable_indexes, item_index, noop_callback)
25 | end
26 | end
27 | end
28 | end
29 |
30 | function stage.run(chstate)
31 | for _, line in ipairs(chstate.lines) do
32 | detect_unreachable_code(chstate, line)
33 | end
34 | end
35 |
36 | return stage
37 |
--------------------------------------------------------------------------------
/src/luacheck/stages/detect_unused_fields.lua:
--------------------------------------------------------------------------------
1 | local core_utils = require "luacheck.core_utils"
2 |
3 | local stage = {}
4 |
5 | local function unused_field_value_message_format(warning)
6 | local target = warning.index and "index" or "field"
7 | return "value assigned to " .. target .. " {field!} is overwritten on line {overwritten_line} before use"
8 | end
9 |
10 | stage.warnings = {
11 | ["314"] = {message_format = unused_field_value_message_format,
12 | fields = {"field", "index", "overwritten_line","overwritten_column", "overwritten_end_column"}}
13 | }
14 |
15 | local function warn_unused_field_value(chstate, node, field_repr, is_index, overwriting_node)
16 | chstate:warn_range("314", node, {
17 | field = field_repr,
18 | index = is_index,
19 | overwritten_line = overwriting_node.line,
20 | overwritten_column = chstate:offset_to_column(overwriting_node.line, overwriting_node.offset),
21 | overwritten_end_column = chstate:offset_to_column(overwriting_node.line, overwriting_node.end_offset)
22 | })
23 | end
24 |
25 | local function check_table(chstate, node)
26 | local array_index = 1.0
27 | local key_value_to_node = {}
28 | local key_node_to_repr = {}
29 | local index_key_nodes = {}
30 |
31 | for _, pair in ipairs(node) do
32 | local key_value
33 | local key_repr
34 | local key_node
35 |
36 | if pair.tag == "Pair" then
37 | key_node = pair[1]
38 | key_value, key_repr = core_utils.eval_const_node(key_node)
39 | else
40 | key_node = pair
41 | key_value = array_index
42 | key_repr = tostring(math.floor(key_value))
43 | array_index = array_index + 1.0
44 | end
45 |
46 | if key_value then
47 | local prev_key_node = key_value_to_node[key_value]
48 | local prev_key_repr = key_node_to_repr[prev_key_node]
49 | local prev_key_is_index = index_key_nodes[prev_key_node]
50 |
51 | if prev_key_node then
52 | warn_unused_field_value(chstate, prev_key_node, prev_key_repr, prev_key_is_index, key_node)
53 | end
54 |
55 | key_value_to_node[key_value] = key_node
56 | key_node_to_repr[key_node] = key_repr
57 |
58 | if pair.tag ~= "Pair" then
59 | index_key_nodes[key_node] = true
60 | end
61 | end
62 | end
63 | end
64 |
65 | local function check_nodes(chstate, nodes)
66 | for _, node in ipairs(nodes) do
67 | if type(node) == "table" then
68 | if node.tag == "Table" then
69 | check_table(chstate, node)
70 | end
71 |
72 | check_nodes(chstate, node)
73 | end
74 | end
75 | end
76 |
77 | function stage.run(chstate)
78 | check_nodes(chstate, chstate.ast)
79 | end
80 |
81 | return stage
82 |
--------------------------------------------------------------------------------
/src/luacheck/stages/init.lua:
--------------------------------------------------------------------------------
1 | local utils = require "luacheck.utils"
2 |
3 | local stages = {}
4 |
5 | -- Checking is organized into stages run one after another.
6 | -- Each stage is in its own module and provides `run` function operating on a check state,
7 | -- and optionally `warnings` table mapping issue codes to tables with fields `message_format`
8 | -- containing format string for the issue or a function returning it given the issue,
9 | -- and `fields` containing array of extra fields this warning can have.
10 |
11 | stages.names = {
12 | "parse",
13 | "unwrap_parens",
14 | "linearize",
15 | "parse_inline_options",
16 | "name_functions",
17 | "resolve_locals",
18 | "detect_bad_whitespace",
19 | "detect_cyclomatic_complexity",
20 | "detect_empty_blocks",
21 | "detect_empty_statements",
22 | "detect_globals",
23 | "detect_reversed_fornum_loops",
24 | "detect_unbalanced_assignments",
25 | "detect_uninit_accesses",
26 | "detect_unreachable_code",
27 | "detect_unused_fields",
28 | "detect_unused_locals"
29 | }
30 |
31 | stages.modules = {}
32 |
33 | for _, name in ipairs(stages.names) do
34 | table.insert(stages.modules, require("luacheck.stages." .. name))
35 | end
36 |
37 | stages.warnings = {}
38 |
39 | local base_fields = {"code", "line", "column", "end_column"}
40 |
41 | local function register_warnings(warnings)
42 | for code, warning in pairs(warnings) do
43 | assert(not stages.warnings[code])
44 | assert(warning.message_format)
45 | assert(warning.fields)
46 |
47 | local full_fields = utils.concat_arrays({base_fields, warning.fields})
48 |
49 | stages.warnings[code] = {
50 | message_format = warning.message_format,
51 | fields = full_fields,
52 | fields_set = utils.array_to_set(full_fields)
53 | }
54 | end
55 | end
56 |
57 | -- Issues that do not originate from normal check stages (excluding global related ones).
58 | register_warnings({
59 | ["011"] = {message_format = "{msg}", fields = {"msg", "prev_line", "prev_column", "prev_end_column"}},
60 | ["631"] = {message_format = "line is too long ({end_column} > {max_length})", fields = {}}
61 | })
62 |
63 | for _, stage_module in ipairs(stages.modules) do
64 | if stage_module.warnings then
65 | register_warnings(stage_module.warnings)
66 | end
67 | end
68 |
69 | function stages.run(chstate)
70 | for _, stage_module in ipairs(stages.modules) do
71 | stage_module.run(chstate)
72 | end
73 | end
74 |
75 | return stages
76 |
--------------------------------------------------------------------------------
/src/luacheck/stages/name_functions.lua:
--------------------------------------------------------------------------------
1 | local stage = {}
2 |
3 | local function get_index_name(base_name, key_node)
4 | if key_node.tag == "String" then
5 | return base_name .. "." .. key_node[1]
6 | end
7 | end
8 |
9 | local function get_full_field_name(node)
10 | if node.tag == "Id" then
11 | return node[1]
12 | elseif node.tag == "Index" then
13 | local base_name = get_full_field_name(node[1])
14 | return base_name and get_index_name(base_name, node[2])
15 | end
16 | end
17 |
18 | local handle_node
19 |
20 | local function handle_nodes(nodes)
21 | for _, node in ipairs(nodes) do
22 | if type(node) == "table" then
23 | handle_node(node)
24 | end
25 | end
26 | end
27 |
28 | function handle_node(node, name)
29 | if node.tag == "Function" then
30 | node.name = name
31 | handle_nodes(node[2])
32 | elseif node.tag == "Set" or node.tag == "Local" or node.tag == "Localrec" then
33 | local lhs = node[1]
34 | local rhs = node[2]
35 |
36 | -- No need to handle LHS if there is no RHS, it's always just a list of locals in that case.
37 | if rhs then
38 | handle_nodes(lhs)
39 |
40 | for index, rhs_node in ipairs(rhs) do
41 | local lhs_node = lhs[index]
42 | local field_name = lhs_node and get_full_field_name(lhs_node)
43 | handle_node(rhs_node, field_name)
44 | end
45 | end
46 | elseif node.tag == "Table" and name then
47 | for _, pair_node in ipairs(node) do
48 | if pair_node.tag == "Pair" then
49 | local key_node = pair_node[1]
50 | local value_node = pair_node[2]
51 | handle_node(key_node)
52 | handle_node(value_node, get_index_name(name, key_node))
53 | else
54 | handle_node(pair_node)
55 | end
56 | end
57 | else
58 | handle_nodes(node)
59 | end
60 | end
61 |
62 | -- Adds `name` field to `Function` ast nodes when possible:
63 | -- * Function assigned to a variable (doesn't matter if local or global): "foo".
64 | -- * Function assigned to a field: "foo.bar.baz".
65 | -- Function can be in a table assigned to a variable or a field, e.g. `foo.bar = {baz = function() ... end}`.
66 | -- * Otherwise: `nil`.
67 | function stage.run(chstate)
68 | handle_nodes(chstate.ast)
69 | end
70 |
71 | return stage
72 |
--------------------------------------------------------------------------------
/src/luacheck/stages/parse.lua:
--------------------------------------------------------------------------------
1 | local decoder = require "luacheck.decoder"
2 | local parser = require "luacheck.parser"
3 |
4 | local stage = {}
5 |
6 | function stage.run(chstate)
7 | chstate.source = decoder.decode(chstate.source_bytes)
8 | chstate.line_offsets = {}
9 | chstate.line_lengths = {}
10 | local ast, comments, code_lines, line_endings, useless_semicolons = parser.parse(
11 | chstate.source, chstate.line_offsets, chstate.line_lengths)
12 | chstate.ast = ast
13 | chstate.comments = comments
14 | chstate.code_lines = code_lines
15 | chstate.line_endings = line_endings
16 | chstate.useless_semicolons = useless_semicolons
17 | end
18 |
19 | return stage
20 |
--------------------------------------------------------------------------------
/src/luacheck/stages/unwrap_parens.lua:
--------------------------------------------------------------------------------
1 | local stage = {}
2 |
3 | -- Mutates an array of nodes and non-tables, unwrapping Paren nodes.
4 | -- If list_start is given, tail Paren is not unwrapped if it's unpacking and past list_start index.
5 | local function handle_nodes(nodes, list_start)
6 | local num_nodes = #nodes
7 |
8 | for index = 1, num_nodes do
9 | local node = nodes[index]
10 |
11 | if type(node) == "table" then
12 | local tag = node.tag
13 |
14 | if tag == "Table" or tag == "Return" then
15 | handle_nodes(node, 1)
16 | elseif tag == "Call" then
17 | handle_nodes(node, 2)
18 | elseif tag == "Invoke" then
19 | handle_nodes(node, 3)
20 | elseif tag == "Forin" then
21 | handle_nodes(node[2], 1)
22 | handle_nodes(node[3])
23 | elseif tag == "Local" then
24 | if node[2] then
25 | handle_nodes(node[2])
26 | end
27 | elseif tag == "Set" then
28 | handle_nodes(node[1])
29 | handle_nodes(node[2], 1)
30 | else
31 | handle_nodes(node)
32 |
33 | if tag == "Paren" and (not list_start or index < list_start or index ~= num_nodes) then
34 | local inner_node = node[1]
35 |
36 | if inner_node.tag ~= "Call" and inner_node.tag ~= "Invoke" and inner_node.tag ~= "Dots" then
37 | nodes[index] = inner_node
38 | end
39 | end
40 | end
41 | end
42 | end
43 | end
44 |
45 | -- Mutates AST, unwrapping Paren nodes.
46 | -- Paren nodes are preserved only when they matter:
47 | -- at the ends of expression lists with potentially multi-value inner expressions.
48 | function stage.run(chstate)
49 | handle_nodes(chstate.ast)
50 | end
51 |
52 | return stage
53 |
--------------------------------------------------------------------------------
/src/luacheck/unicode.lua:
--------------------------------------------------------------------------------
1 | local unicode_printability_boundaries = require "luacheck.unicode_printability_boundaries"
2 |
3 | local unicode = {}
4 |
5 | -- unicode_printability_boundaries is an array of first codepoints of
6 | -- each continuous block of codepoints that are all printable or all not printable.
7 |
8 | function unicode.is_printable(codepoint)
9 | -- Binary search for index of the first boundary less than or equal to given codepoint.
10 | local floor_boundary_index
11 |
12 | -- Target index is always in [begin_index..end_index).
13 | local begin_index = 1
14 | local end_index = #unicode_printability_boundaries + 1
15 |
16 | while end_index - begin_index > 1 do
17 | local mid_index = math.floor((begin_index + end_index) / 2)
18 | local mid_codepoint = unicode_printability_boundaries[mid_index]
19 |
20 | if codepoint < mid_codepoint then
21 | end_index = mid_index
22 | elseif codepoint > mid_codepoint then
23 | begin_index = mid_index
24 | else
25 | floor_boundary_index = mid_index
26 | break
27 | end
28 | end
29 |
30 | floor_boundary_index = floor_boundary_index or begin_index
31 | -- floor_boundary_index is the number of the block containing codepoint.
32 | -- Printable and not printable blocks alternate and the first one is not printable (zero is not printable).
33 | return floor_boundary_index % 2 == 0
34 | end
35 |
36 | return unicode
37 |
--------------------------------------------------------------------------------
/src/luacheck/vendor/sha1/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 Enrique García Cota, Eike Decker, Jeffrey Friedl
2 | Copyright (c) 2018 Peter Melnichenko
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a
5 | copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included
13 | in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/luacheck/vendor/sha1/bit32_ops.lua:
--------------------------------------------------------------------------------
1 | local bit32 = require "bit32"
2 |
3 | local ops = {}
4 |
5 | local band = bit32.band
6 | local bor = bit32.bor
7 | local bxor = bit32.bxor
8 |
9 | ops.uint32_lrot = bit32.lrotate
10 | ops.byte_xor = bxor
11 | ops.uint32_xor_3 = bxor
12 | ops.uint32_xor_4 = bxor
13 |
14 | function ops.uint32_ternary(a, b, c)
15 | -- c ~ (a & (b ~ c)) has less bitwise operations than (a & b) | (~a & c).
16 | return bxor(c, band(a, bxor(b, c)))
17 | end
18 |
19 | function ops.uint32_majority(a, b, c)
20 | -- (a & (b | c)) | (b & c) has less bitwise operations than (a & b) | (a & c) | (b & c).
21 | return bor(band(a, bor(b, c)), band(b, c))
22 | end
23 |
24 | return ops
25 |
--------------------------------------------------------------------------------
/src/luacheck/vendor/sha1/bit_ops.lua:
--------------------------------------------------------------------------------
1 | local bit = require "bit"
2 |
3 | local ops = {}
4 |
5 | local band = bit.band
6 | local bor = bit.bor
7 | local bxor = bit.bxor
8 |
9 | ops.uint32_lrot = bit.rol
10 | ops.byte_xor = bxor
11 | ops.uint32_xor_3 = bxor
12 | ops.uint32_xor_4 = bxor
13 |
14 | function ops.uint32_ternary(a, b, c)
15 | -- c ~ (a & (b ~ c)) has less bitwise operations than (a & b) | (~a & c).
16 | return bxor(c, band(a, bxor(b, c)))
17 | end
18 |
19 | function ops.uint32_majority(a, b, c)
20 | -- (a & (b | c)) | (b & c) has less bitwise operations than (a & b) | (a & c) | (b & c).
21 | return bor(band(a, bor(b, c)), band(b, c))
22 | end
23 |
24 | return ops
25 |
--------------------------------------------------------------------------------
/src/luacheck/vendor/sha1/common.lua:
--------------------------------------------------------------------------------
1 | local common = {}
2 |
3 | -- Merges four bytes into a uint32 number.
4 | function common.bytes_to_uint32(a, b, c, d)
5 | return a * 0x1000000 + b * 0x10000 + c * 0x100 + d
6 | end
7 |
8 | -- Splits a uint32 number into four bytes.
9 | function common.uint32_to_bytes(a)
10 | local a4 = a % 256
11 | a = (a - a4) / 256
12 | local a3 = a % 256
13 | a = (a - a3) / 256
14 | local a2 = a % 256
15 | local a1 = (a - a2) / 256
16 | return a1, a2, a3, a4
17 | end
18 |
19 |
20 | return common
21 |
--------------------------------------------------------------------------------
/src/luacheck/vendor/sha1/init.lua:
--------------------------------------------------------------------------------
1 | local common = require "luacheck.vendor.sha1.common"
2 |
3 | local sha1 = {
4 | -- Meta fields retained for compatibility.
5 | _VERSION = "sha.lua 0.6.0",
6 | _URL = "https://github.com/mpeterv/sha1",
7 | _DESCRIPTION = [[
8 | SHA-1 secure hash and HMAC-SHA1 signature computation in Lua,
9 | using bit and bit32 modules and Lua 5.3 operators when available
10 | and falling back to a pure Lua implementation on Lua 5.1.
11 | Based on code orignally by Jeffrey Friedl and modified by
12 | Eike Decker and Enrique García Cota.]],
13 | _LICENSE = [[
14 | MIT LICENSE
15 |
16 | Copyright (c) 2013 Enrique García Cota, Eike Decker, Jeffrey Friedl
17 | Copyright (c) 2018 Peter Melnichenko
18 |
19 | Permission is hereby granted, free of charge, to any person obtaining a
20 | copy of this software and associated documentation files (the
21 | "Software"), to deal in the Software without restriction, including
22 | without limitation the rights to use, copy, modify, merge, publish,
23 | distribute, sublicense, and/or sell copies of the Software, and to
24 | permit persons to whom the Software is furnished to do so, subject to
25 | the following conditions:
26 |
27 | The above copyright notice and this permission notice shall be included
28 | in all copies or substantial portions of the Software.
29 |
30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
31 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
32 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
33 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
34 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
35 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
36 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.]]
37 | }
38 |
39 | sha1.version = "0.6.0"
40 |
41 | local function choose_ops()
42 | if _VERSION:find("5%.3") then
43 | return "lua53_ops"
44 | elseif pcall(require, "bit") then
45 | return "bit_ops"
46 | elseif pcall(require, "bit32") then
47 | return "bit32_ops"
48 | else
49 | return "pure_lua_ops"
50 | end
51 | end
52 |
53 | local ops = require("luacheck.vendor.sha1." .. choose_ops())
54 | local uint32_lrot = ops.uint32_lrot
55 | local byte_xor = ops.byte_xor
56 | local uint32_xor_3 = ops.uint32_xor_3
57 | local uint32_xor_4 = ops.uint32_xor_4
58 | local uint32_ternary = ops.uint32_ternary
59 | local uint32_majority = ops.uint32_majority
60 |
61 | local bytes_to_uint32 = common.bytes_to_uint32
62 | local uint32_to_bytes = common.uint32_to_bytes
63 |
64 | local sbyte = string.byte
65 | local schar = string.char
66 | local sformat = string.format
67 | local srep = string.rep
68 |
69 | local function hex_to_binary(hex)
70 | return (hex:gsub("..", function(hexval)
71 | return schar(tonumber(hexval, 16))
72 | end))
73 | end
74 |
75 | -- Calculates SHA1 for a string, returns it encoded as 40 hexadecimal digits.
76 | function sha1.sha1(str)
77 | -- Input preprocessing.
78 | -- First, append a `1` bit and seven `0` bits.
79 | local first_append = schar(0x80)
80 |
81 | -- Next, append some zero bytes to make the length of the final message a multiple of 64.
82 | -- Eight more bytes will be added next.
83 | local non_zero_message_bytes = #str + 1 + 8
84 | local second_append = srep(schar(0), -non_zero_message_bytes % 64)
85 |
86 | -- Finally, append the length of the original message in bits as a 64-bit number.
87 | -- Assume that it fits into the lower 32 bits.
88 | local third_append = schar(0, 0, 0, 0, uint32_to_bytes(#str * 8))
89 |
90 | str = str .. first_append .. second_append .. third_append
91 | assert(#str % 64 == 0)
92 |
93 | -- Initialize hash value.
94 | local h0 = 0x67452301
95 | local h1 = 0xEFCDAB89
96 | local h2 = 0x98BADCFE
97 | local h3 = 0x10325476
98 | local h4 = 0xC3D2E1F0
99 |
100 | local w = {}
101 |
102 | -- Process the input in successive 64-byte chunks.
103 | for chunk_start = 1, #str, 64 do
104 | -- Load the chunk into W[0..15] as uint32 numbers.
105 | local uint32_start = chunk_start
106 |
107 | for i = 0, 15 do
108 | w[i] = bytes_to_uint32(sbyte(str, uint32_start, uint32_start + 3))
109 | uint32_start = uint32_start + 4
110 | end
111 |
112 | -- Extend the input vector.
113 | for i = 16, 79 do
114 | w[i] = uint32_lrot(uint32_xor_4(w[i - 3], w[i - 8], w[i - 14], w[i - 16]), 1)
115 | end
116 |
117 | -- Initialize hash value for this chunk.
118 | local a = h0
119 | local b = h1
120 | local c = h2
121 | local d = h3
122 | local e = h4
123 |
124 | -- Main loop.
125 | for i = 0, 79 do
126 | local f
127 | local k
128 |
129 | if i <= 19 then
130 | f = uint32_ternary(b, c, d)
131 | k = 0x5A827999
132 | elseif i <= 39 then
133 | f = uint32_xor_3(b, c, d)
134 | k = 0x6ED9EBA1
135 | elseif i <= 59 then
136 | f = uint32_majority(b, c, d)
137 | k = 0x8F1BBCDC
138 | else
139 | f = uint32_xor_3(b, c, d)
140 | k = 0xCA62C1D6
141 | end
142 |
143 | local temp = (uint32_lrot(a, 5) + f + e + k + w[i]) % 0x100000000
144 | e = d
145 | d = c
146 | c = uint32_lrot(b, 30)
147 | b = a
148 | a = temp
149 | end
150 |
151 | -- Add this chunk's hash to result so far.
152 | h0 = (h0 + a) % 0x100000000
153 | h1 = (h1 + b) % 0x100000000
154 | h2 = (h2 + c) % 0x100000000
155 | h3 = (h3 + d) % 0x100000000
156 | h4 = (h4 + e) % 0x100000000
157 | end
158 |
159 | return sformat("%08x%08x%08x%08x%08x", h0, h1, h2, h3, h4)
160 | end
161 |
162 | function sha1.binary(str)
163 | return hex_to_binary(sha1.sha1(str))
164 | end
165 |
166 | -- Precalculate replacement tables.
167 | local xor_with_0x5c = {}
168 | local xor_with_0x36 = {}
169 |
170 | for i = 0, 0xff do
171 | xor_with_0x5c[schar(i)] = schar(byte_xor(0x5c, i))
172 | xor_with_0x36[schar(i)] = schar(byte_xor(0x36, i))
173 | end
174 |
175 | -- 512 bits.
176 | local BLOCK_SIZE = 64
177 |
178 | function sha1.hmac(key, text)
179 | if #key > BLOCK_SIZE then
180 | key = sha1.binary(key)
181 | end
182 |
183 | local key_xord_with_0x36 = key:gsub('.', xor_with_0x36) .. srep(schar(0x36), BLOCK_SIZE - #key)
184 | local key_xord_with_0x5c = key:gsub('.', xor_with_0x5c) .. srep(schar(0x5c), BLOCK_SIZE - #key)
185 |
186 | return sha1.sha1(key_xord_with_0x5c .. sha1.binary(key_xord_with_0x36 .. text))
187 | end
188 |
189 | function sha1.hmac_binary(key, text)
190 | return hex_to_binary(sha1.hmac(key, text))
191 | end
192 |
193 | setmetatable(sha1, {__call = function(_, str) return sha1.sha1(str) end})
194 |
195 | return sha1
196 |
--------------------------------------------------------------------------------
/src/luacheck/vendor/sha1/lua53_ops.lua:
--------------------------------------------------------------------------------
1 | local ops = {}
2 |
3 | function ops.uint32_lrot(a, bits)
4 | return ((a << bits) & 0xFFFFFFFF) | (a >> (32 - bits))
5 | end
6 |
7 | function ops.byte_xor(a, b)
8 | return a ~ b
9 | end
10 |
11 | function ops.uint32_xor_3(a, b, c)
12 | return a ~ b ~ c
13 | end
14 |
15 | function ops.uint32_xor_4(a, b, c, d)
16 | return a ~ b ~ c ~ d
17 | end
18 |
19 | function ops.uint32_ternary(a, b, c)
20 | -- c ~ (a & (b ~ c)) has less bitwise operations than (a & b) | (~a & c).
21 | return c ~ (a & (b ~ c))
22 | end
23 |
24 | function ops.uint32_majority(a, b, c)
25 | -- (a & (b | c)) | (b & c) has less bitwise operations than (a & b) | (a & c) | (b & c).
26 | return (a & (b | c)) | (b & c)
27 | end
28 |
29 | return ops
30 |
--------------------------------------------------------------------------------
/src/luacheck/vendor/sha1/pure_lua_ops.lua:
--------------------------------------------------------------------------------
1 | local common = require "luacheck.vendor.sha1.common"
2 |
3 | local ops = {}
4 |
5 | local bytes_to_uint32 = common.bytes_to_uint32
6 | local uint32_to_bytes = common.uint32_to_bytes
7 |
8 | function ops.uint32_lrot(a, bits)
9 | local power = 2 ^ bits
10 | local inv_power = 0x100000000 / power
11 | local lower_bits = a % inv_power
12 | return (lower_bits * power) + ((a - lower_bits) / inv_power)
13 | end
14 |
15 | -- Build caches for bitwise `and` and `xor` over bytes to speed up uint32 operations.
16 | -- Building the cache by simply applying these operators over all pairs is too slow and
17 | -- duplicates a lot of work over different bits of inputs.
18 | -- Instead, when building a cache over bytes, for each pair of bytes split both arguments
19 | -- into two 4-bit numbers, calculate values over these two halves, then join the results into a byte again.
20 | -- While there are 256 * 256 = 65536 pairs of bytes, there are only 16 * 16 = 256 pairs
21 | -- of 4-bit numbers, so that building an 8-bit cache given a 4-bit cache is rather efficient.
22 | -- The same logic is applied recursively to make a 4-bit cache from a 2-bit cache and a 2-bit
23 | -- cache from a 1-bit cache, which is calculated given the 1-bit version of the operator.
24 |
25 | -- Returns a cache containing all values of a bitwise operator over numbers with given number of bits,
26 | -- given an operator over single bits.
27 | -- Value of `op(a, b)` is stored in `cache[a * (2 ^ bits) + b]`.
28 | local function make_op_cache(bit_op, bits)
29 | if bits == 1 then
30 | return {[0] = bit_op(0, 0), bit_op(0, 1), bit_op(1, 0), bit_op(1, 1)}
31 | end
32 |
33 | local half_bits = bits / 2
34 | local size = 2 ^ bits
35 | local half_size = 2 ^ half_bits
36 | local half_cache = make_op_cache(bit_op, half_bits)
37 |
38 | local cache = {}
39 |
40 | -- The implementation used is an optimized version of the following reference one,
41 | -- with intermediate calculations reused and moved to the outermost loop possible.
42 | -- It's possible to reorder the loops and move the calculation of one of the
43 | -- half-results one level up, but then the cache is not filled in a proper array order
44 | -- and its access performance suffers.
45 |
46 | -- for a1 = 0, half_size - 1 do
47 | -- for a2 = 0, half_size - 1 do
48 | -- for b1 = 0, half_size - 1 do
49 | -- for b2 = 0, half_size - 1 do
50 | -- local a = a1 * half_size + a2
51 | -- local b = b1 * half_size + b2
52 | -- local v1 = half_cache[a1 * half_size + b1]
53 | -- local v2 = half_cache[a2 * half_size + b2]
54 | -- local v = v1 * half_size + v2
55 | -- cache[a * size + b] = v
56 | -- end
57 | -- end
58 | -- end
59 | -- end
60 |
61 | for a1 = 0, half_size - 1 do
62 | local a1_half_size = a1 * half_size
63 |
64 | for a2 = 0, half_size - 1 do
65 | local a2_size = a2 * half_size
66 | local a_size = (a1_half_size + a2) * size
67 |
68 | for b1 = 0, half_size - 1 do
69 | local a_size_plus_b1_half_size = a_size + b1 * half_size
70 | local v1_half_size = half_cache[a1_half_size + b1] * half_size
71 |
72 | for b2 = 0, half_size - 1 do
73 | cache[a_size_plus_b1_half_size + b2] = v1_half_size + half_cache[a2_size + b2]
74 | end
75 | end
76 | end
77 | end
78 |
79 | return cache
80 | end
81 |
82 | local byte_and_cache = make_op_cache(function(a, b) return a * b end, 8)
83 | local byte_xor_cache = make_op_cache(function(a, b) return a == b and 0 or 1 end, 8)
84 |
85 | function ops.byte_xor(a, b)
86 | return byte_xor_cache[a * 256 + b]
87 | end
88 |
89 | function ops.uint32_xor_3(a, b, c)
90 | local a1, a2, a3, a4 = uint32_to_bytes(a)
91 | local b1, b2, b3, b4 = uint32_to_bytes(b)
92 | local c1, c2, c3, c4 = uint32_to_bytes(c)
93 |
94 | return bytes_to_uint32(
95 | byte_xor_cache[a1 * 256 + byte_xor_cache[b1 * 256 + c1]],
96 | byte_xor_cache[a2 * 256 + byte_xor_cache[b2 * 256 + c2]],
97 | byte_xor_cache[a3 * 256 + byte_xor_cache[b3 * 256 + c3]],
98 | byte_xor_cache[a4 * 256 + byte_xor_cache[b4 * 256 + c4]]
99 | )
100 | end
101 |
102 | function ops.uint32_xor_4(a, b, c, d)
103 | local a1, a2, a3, a4 = uint32_to_bytes(a)
104 | local b1, b2, b3, b4 = uint32_to_bytes(b)
105 | local c1, c2, c3, c4 = uint32_to_bytes(c)
106 | local d1, d2, d3, d4 = uint32_to_bytes(d)
107 |
108 | return bytes_to_uint32(
109 | byte_xor_cache[a1 * 256 + byte_xor_cache[b1 * 256 + byte_xor_cache[c1 * 256 + d1]]],
110 | byte_xor_cache[a2 * 256 + byte_xor_cache[b2 * 256 + byte_xor_cache[c2 * 256 + d2]]],
111 | byte_xor_cache[a3 * 256 + byte_xor_cache[b3 * 256 + byte_xor_cache[c3 * 256 + d3]]],
112 | byte_xor_cache[a4 * 256 + byte_xor_cache[b4 * 256 + byte_xor_cache[c4 * 256 + d4]]]
113 | )
114 | end
115 |
116 | function ops.uint32_ternary(a, b, c)
117 | local a1, a2, a3, a4 = uint32_to_bytes(a)
118 | local b1, b2, b3, b4 = uint32_to_bytes(b)
119 | local c1, c2, c3, c4 = uint32_to_bytes(c)
120 |
121 | -- (a & b) + (~a & c) has less bitwise operations than (a & b) | (~a & c).
122 | return bytes_to_uint32(
123 | byte_and_cache[b1 * 256 + a1] + byte_and_cache[c1 * 256 + 255 - a1],
124 | byte_and_cache[b2 * 256 + a2] + byte_and_cache[c2 * 256 + 255 - a2],
125 | byte_and_cache[b3 * 256 + a3] + byte_and_cache[c3 * 256 + 255 - a3],
126 | byte_and_cache[b4 * 256 + a4] + byte_and_cache[c4 * 256 + 255 - a4]
127 | )
128 | end
129 |
130 | function ops.uint32_majority(a, b, c)
131 | local a1, a2, a3, a4 = uint32_to_bytes(a)
132 | local b1, b2, b3, b4 = uint32_to_bytes(b)
133 | local c1, c2, c3, c4 = uint32_to_bytes(c)
134 |
135 | -- (a & b) + (c & (a ~ b)) has less bitwise operations than (a & b) | (a & c) | (b & c).
136 | return bytes_to_uint32(
137 | byte_and_cache[a1 * 256 + b1] + byte_and_cache[c1 * 256 + byte_xor_cache[a1 * 256 + b1]],
138 | byte_and_cache[a2 * 256 + b2] + byte_and_cache[c2 * 256 + byte_xor_cache[a2 * 256 + b2]],
139 | byte_and_cache[a3 * 256 + b3] + byte_and_cache[c3 * 256 + byte_xor_cache[a3 * 256 + b3]],
140 | byte_and_cache[a4 * 256 + b4] + byte_and_cache[c4 * 256 + byte_xor_cache[a4 * 256 + b4]]
141 | )
142 | end
143 |
144 | return ops
145 |
--------------------------------------------------------------------------------
/src/luacheck/version.lua:
--------------------------------------------------------------------------------
1 | local argparse = require "argparse"
2 | local lfs = require "lfs"
3 | local luacheck = require "luacheck"
4 | local multithreading = require "luacheck.multithreading"
5 | local utils = require "luacheck.utils"
6 |
7 | local version = {}
8 |
9 | version.luacheck = luacheck._VERSION
10 |
11 | if rawget(_G, "jit") then
12 | version.lua = rawget(_G, "jit").version
13 | elseif _VERSION:find("^Lua ") then
14 | version.lua = "PUC-Rio " .. _VERSION
15 | else
16 | version.lua = _VERSION
17 | end
18 |
19 | version.argparse = argparse.version
20 |
21 | version.lfs = utils.unprefix(lfs._VERSION, "LuaFileSystem ")
22 |
23 | if multithreading.has_lanes then
24 | version.lanes = multithreading.lanes.ABOUT.version
25 | else
26 | version.lanes = "Not found"
27 | end
28 |
29 | version.string = ([[
30 | Luacheck: %s
31 | Lua: %s
32 | Argparse: %s
33 | LuaFileSystem: %s
34 | LuaLanes: %s]]):format(version.luacheck, version.lua, version.argparse, version.lfs, version.lanes)
35 |
36 | return version
37 |
--------------------------------------------------------------------------------