├── .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 a​b"] = 1, 5 | ["päällekkäinen nimi a​b"] = 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 | --------------------------------------------------------------------------------