pax_global_header00006660000000000000000000000064141045143700014511gustar00rootroot0000000000000052 comment=abf7386a892df4ce566fef9e4640ddbf9af78411 luacheck-0.25.0/000077500000000000000000000000001410451437000133545ustar00rootroot00000000000000luacheck-0.25.0/.busted000066400000000000000000000001061410451437000146400ustar00rootroot00000000000000return { _all = { ["exclude-pattern"] = "sample_spec" } } luacheck-0.25.0/.gitignore000066400000000000000000000002641410451437000153460ustar00rootroot00000000000000.luacheckcache luacov.stats.out luacov.report.out doc !build/ build/* !build/Makefile !build/bin build/bin/* !build/bin/luacheck.lua package scripts/UnicodeData-* docsrc/.doctrees luacheck-0.25.0/.luacheckrc000066400000000000000000000003511410451437000154600ustar00rootroot00000000000000std = "min" cache = true include_files = {"src", "spec/*.lua", "scripts/*.lua", "*.rockspec", "*.luacheckrc"} exclude_files = {"src/luacheck/vendor"} files["src/luacheck/unicode_printability_boundaries.lua"].max_line_length = false luacheck-0.25.0/.luacov000066400000000000000000000000571410451437000146500ustar00rootroot00000000000000return require "spec.helper".luacov_config("") luacheck-0.25.0/.travis.yml000066400000000000000000000017031410451437000154660ustar00rootroot00000000000000language: python sudo: false env: - LUA="lua=5.1" - LUA="lua=5.2" - LUA="lua=5.3" - LUA="lua=5.4" - LUA="luajit=2.0" - LUA="luajit=2.1" before_install: - pip install hererocks - pip install codecov - hererocks here --$LUA -r latest - source here/bin/activate - luarocks install lanes - luarocks install busted - luarocks install cluacov - luarocks install luautf8 - luarocks install luasocket install: - luarocks make script: - busted -c - lua -e 'package.path="./src/?.lua;./src/?/init.lua;"..package.path' -lluacov bin/luacheck.lua luacheck-dev-1.rockspec -j2 - lua -e 'package.preload.lanes=error;package.path="./src/?.lua;./src/?/init.lua;"..package.path' -lluacov bin/luacheck.lua --version | grep 'Not found' - lua -e 'package.path="./src/?.lua;./src/?/init.lua;"..package.path' -lluacov bin/luacheck.lua spec/*.lua - luacheck . - luacheck . after_script: - luacov - codecov -f luacov.report.out -X gcov luacheck-0.25.0/CHANGELOG.md000066400000000000000000000542751410451437000152020ustar00rootroot00000000000000# Changelog ## 0.25.0 (2020-08-25) ### New features * New values for CLI option --std: lua54, lua54c * New lua54 standard library definitions ### Fixes * Lua 5.4 allows 31bits utf8 codepoint ## 0.24.0 (2020-08-20) ### Changes * Caching now uses files in a global directory instead of local `.luacheckcache` file. Default cache directory is `%LOCALAPPDATA%\Luacheck\Cache` on Windows, `~/Library/Caches/Luacheck` on OS X/macOS, and `$XDG_CACHE_HOME/luacheck` or `~/.config/luacheck` on other systems. ### Fixes * Fixes for using Luacheck with Lua 5.4: - Parse 5.4 attributes in `local` declarations (but ignores them for now) - Luacheck itself can also run with Lua 5.4 * Fixed `randomize` missing from `busted` set of standard globals (#183). * Added additional `table` and `thread` definitions for `ngx_lua`. * Added additional `match` definition for `busted`. ### Miscellaneous * Upgraded Windows binary components: Lua 5.3.4 -> 5.3.5, LuaFileSystem 1.6.3 -> 1.7.0. ## 0.23.0 (2018-09-18) ### Breaking changes * Removed `--no-inline` CLI option and `inline` config option, inline options are now always enabled. * Inline comments are now supposed to be only in short comments but not long ones. * Installer script (install.lua) is removed. Luacheck can still be installed manually by recursively copying `src/*` to a directory in `package.path` and copying `bin/luacheck.lua` to a directory in `PATH` as `luacheck`. ### New features and improvements * Warning columns are now reported in Unicode codepoints if input is valid UTF-8 (#45). * Added indentaion-based guessing of a better location for missing `end` and `until` syntax errors. * Added `luacheckrc` set of allowed globals containing globals used in Luacheck config to set options. * Added default stds equivalent to predefined per-path std overrides in config: - `files["**/spec/**/*_spec.lua"].std = "+busted"`; - `files["**/test/**/*_spec.lua"].std = "+busted"`; - `files["**/tests/**/*_spec.lua"].std = "+busted"`; - `files["**/*.rockspec"].std = "+rockspec"`; - `files["**/*.luacheckrc"].std = "+luacheckrc"`. * Added detection of numeric for loops going from `#t` to `1` without negative step (#160). * Added support for LuaRocks 3 module autodetection when checking rockspecs (#176). * Updated `love` standard for LÖVE 11.1 (#178). ### Changes * Default set of standard globals is now always `max`, allowing globals of all Lua versions. `_G` std is deprecated. ### Fixes * Added missing globals to `rockspec` std: `hooks`, `deploy`, `build_dependencies`, `test_dependencies`, and `test`. * Fixed line lengths appearing in the output before other warnings on the same line even if their column numbers are smaller. ### Miscellaneous * Luacheck now depends on argparse instead of bundling it. * LuaFileSystem dependency is now required. ## 0.22.1 (2018-07-01) ### Improvements * Reduced amount of RAM used when checking a large number of files. ## 0.22.0 (2018-05-09) ### New features and improvements * Added detection of cyclomatic complexity, with warnings emitted for functions with complexity higher than a configurable limit; disabled by default (#141). * Added a built-in formatter printing warnings and errors in a format understood by MSBuild/Visual Studio if `luacheck` is used as a custom build step (#142). * `ranges` and `quiet` options can now be used in config, e.g. `quiet = 1` to disable `OK` lines in default formatter output. * `luacheck` module now adds `prev_line`, `prev_column`, and `prev_end_column` fields to syntax error events if they refer to some extra location: redefined label errors point to the previous definition, unpaired tokens such as `function`/`end` point to the the first token (#134). * `luacheck` module now adds `prev_end_column` field to warning events that already have `prev_line` and `prev_column` fields, and `overwritten_end_column` for warnings with `overwritten_line` and `overwritten_column`. * Improved error messages for invalid options and config: when an option is invalid, extra context is provided instead of just the name. * Custom stds are now validated on config load. * When recursively checking a directory, each failure to list a nested directory is now reported separately, and other found files and directories are checked anyway. Previously any error when listing any nested directory resulted in immediate failure for the entire parent directory (#159). * When `--[no-]cache` CLI option is used with LuaFileSystem not found, it is now ignored instead of causing an error. Missing LuaFileSystem is now mentioned in the help message next to features disabled without it. ### Fixes * Fixed errors or incorrect reporting when unused mutually recursive functions have other values assigned to their local variables. * Fixed unused values in infinite loops sometimes reported as overwritten by another value. * Fixed caching not working properly when cache is enabled in config loaded from a parent directory. * Fixed per-path config overrides not working on Windows when paths in config and input paths use different case (#150). * Added missing definition of `love.handlers` to `love` std (#161). ### Miscellaneous * Installer script (install.lua) is deprecated. Future versions of Luacheck may have required dependencies. Luacheck can still be installed manually by recursively copying `src/luacheck` to a directory in `package.path` and copying `bin/luacheck.lua` to a directory in `PATH` as `luacheck`. ## 0.21.2 (2017-11-13) ### Fixes * Fixed error when an upvalue is accessed from an unreachable closure (#139). * Fixed unreachable code and accessing uninitialized variables not being detected inside unreachable functions. ## 0.21.1 (2017-09-10) ### Fixes * Added missing definition of `ngx.ERROR` constant to `ngx_lua` std (#123). * Fixed unused values and initialized accesses not being reported when the access is in a closure defined in code path incompatible with the value assignment (#126). ## 0.21.0 (2017-09-04) ### New features and improvements * Column range for `line is too long` warning now starts at the first character beyond the length limit instead of the very first character of the line (#117). * Error messages for invalid inline options are now a bit better, including reason why an inline option invocation is invalid and what is the name of the option at fault. * `ngx_lua` std now contains full API definition for lua-nginx-module 0.10.10, so that operations on unknown fields within `ngx` global are now reported (#118). ### Fixes * `luacheck` no longer aborts on internal error while checking files in parallel. ## 0.20.0 (2017-05-08) ### Breaking changes * `luacheck` now exits with `2` instead of `1` if there are syntax errors or invalid inline options present. It now exits with `3` instead of `2` if I/O errors are present. It now exits with `4` instead of `3` on a critical error (#94). ### New features and improvements * If project-specific `.luacheckrc` is not found, `luacheck` will now use config from some global location if it is present there. Default global location is `%LOCALAPPDATA%\Luacheck\.luacheckrc` on Windows, `~/Library/Application Support/Luacheck/.luacheckrc` on OS X/macOS, and `$XDG_CONFIG_HOME/luacheck/.luacheckrc` or `~/.config/luacheck/.luacheckrc` on other systems. This behaviour can be tweaked with `--default-config` and `--no-default-config` options (#102). * New `--[no-]max-code-line-length`, `--[no-]max-string-line-length`, `--[no-]max-comment-line-length` CLI options and corresponding config and inline options that limit line length only for subsets of lines based on line type: string lines have their line endings within a string, comment lines have their line endings within a comment, other lines are code lines (#100). * New `love` std set containing globals added by Love2D framework (#108). * For warnings about unused values and fields, if the value is always overwritten by a single other value, location of the overwriting assignment is mentioned in the warning message (#106). * When attempting to check a directory while LuaFileSystem is not installed, `luacheck` now mentions that LuaFileSystem is required in the error message (#103). * Improved warning message for unbalanced assignment warnings (#104). ## 0.19.1 (2017-03-12) ### Miscellaneous * Added warning code for `line is too long` warnings to documentation (#98). * Added binary executable file for Windows. ## 0.19.0 (2017-03-03) ### Breaking changes * New format for defining standard sets of globals that can describe all allowed fields of each global. ### New features and improvements * Luacheck can now detect mutations and accesses of specific fields within globals. Standard global definitions have been updated to provide precise lists of allowed fields. This also works through local aliases (e.g. `local t = table; t.upsert()` produces a warning, but `local t = table; t.insert()` does not). * Default set of allowed globals is now equal to globals normally provided by version of Lua used to run Luacheck, instead of all globals set in the interpreter while it runs Luacheck. * All options that operate on lists of global names can now use field names as well. E.g. `--not-globals string.len` undefines standard field `string.len`. Additionally, config options `globals`, `new_globals`, `read_globals`, `new_read_globals` can use a table-based format to define trees of allowed fields. * Lines that are longer than some maximum length are now reported. Default limit is 120. Limit can be changed using `max_line_length` option. * Warnings related to trailing whitespace in comments and inside string literals now use separate warning codes. * Luacheck no longer reports a crash with a long traceback when interrupted, instead it simply exits with an error message. ### Fixes * Fixed inconsistent indentation not being detected on lines with trailing whitespace. ## 0.18.0 (2017-01-10) ### New features and improvements * Indirect mutations of read-only globals through local aliases are now detected (e.g. `local t = table; t.foo = "bar"`). * New CLI, config, and inline option `not_globals` for removing defined standard and custom globals (#88). * Custom globals defined as mutable using `globals` option can now be set to read-only using `read_globals` option in overwriting settings (previously `globals` had priority over `read_globals` even if `read_globals` was the last option used). * Luacheck exit codes are now documented. ### Fixes * Warnings that are explictly enabled by inline options are now correctly reported. E.g. `--luacheck: std none` now results in warnings for any used globals (#51). ## 0.17.1 (2016-12-22) ### Fixes * Fixed error when using cache and there are warnings with codes `314` or `521`. * Globals in `rockspec` std and `ngx` global in `ngx_lua` std are no longer read-only (#87). * Reverted changes to exit codes that conflicted with assumptions made by luacheck checker in Syntastic (#85). ## 0.17.0 (2016-11-18) ### New features and improvements * Trailing whitespace and inconsistent indentation (tabs after spaces) are now detected (#79). ## 0.16.3 (2016-10-27) ### Fixes * Fixed version number (#75). ## 0.16.2 (2016-10-25) ### Fixes * Fixed error in some cases when a function declaration is unreachable (#74). ## 0.16.1 (2016-09-29) ### Fixes * Fixed false positive for `variable/value is mutated but never accessed` warning when initial value of a variable comes from `X and Y`, `X or Y`, or `(X())` expression (#72). ## 0.16.0 (2016-08-30) ### New features and improvements * Local tables which are mutated but not used are now detected (#61). * Mutations of global variables with key chains of length > 1 are now correctly reported as mutations, not accesses. * Completely unused variables named `_` are now reported (#66). ### Fixes * `luacheck: ignore` now correctly filters out `5xx` and `314` warnings (#71). ## 0.15.1 (2016-06-09) ### Fixes * Fixed JUnit formatter not escaping XML entities (#62). ## 0.15.0 (2016-04-18) ### New features and improvements * New `rockspec` std set containing globals allowed in rockspecs (#55). ### Fixes * Fixed error when checking a file with a hexadecimal number using Lua 5.1 on Windows (#57). * Fixed luacheck using wrong path when checking a file in a subdirectory with single character name (#59). ## 0.14.0 (2016-02-25) ### New features and improvements * Duplicated keys in table literals are detected (#48). * Unused recursive and mutually recursive functions assigned to local variables are detected (#50). * Globs can be used to select paths when applying option overrides in config (#52). * Inline options can contain notes in parentheses. * `--jobs` option (multithreading) is used by default with LuaLanes found, number of threads used is set to number of available processing units. * Better error messages are provided on I/O and other errors when reading files, loading configs and rockspecs, etc. * Better path handling when recursively checking directories ending with slash. ## 0.13.0 (2016-01-04) ### New features and improvements * Empty statements (semicolons without preceding statements) are reported (#44). * Inline option `luacheck: push` can be followed by other options on the same line, e.g. `luacheck: push ignore`. * Better syntax error messages. * When recursively checking directories and `--include-files` is used, files are not filtered by `.lua` extension (#43). ### Fixes * Fixed crash when source ends with `.`, `"\` or `"\u{`. ## 0.12.0 (2015-11-02) ### New features and improvements * New `ngx_lua` globals set for Openresty ngx_lua module (#41). * Better CLI error messages. ### Fixes * Fixed duplicate `uninitialized access` and `unreachable code` warnings in nested functions. ### Miscellaneous * RTD theme is no longer required when building docs. * HTML docs are no longer stored in the repo. ## 0.11.1 (2015-08-09) ### Improvements * More accurate analysis around literal conditions, e.g. `while true do ... end`. * Extra threads are not created when number of files is less than value of `--jobs` option. ### Fixes * Fixed crash on unreachable repeat condition (#36). * Fixed crash when using `--ranges` with cache. * Fixed incorrect output or crashes when loading cache created by a different version (#37). * Fixed crash when an upvalue is followed by an infinite loop. ## 0.11.0 (2015-07-18) ### Breaking changes * Removed `--no-unused-globals` option, use `--ignore 13` instead. * Removed `.vararg` field for warnings related to varargs, check `.name == "..."` instead. * Errors now also have codes, starting with `0`, and are returned together with warnings from `luacheck.*` functions (#31). ### New features and improvements * During config lookup all directories starting from current one and up to file system root are traversed in search of config. Path-related data from config loaded from an upper directory is adjusted to work as if Luacheck was started from the directory with config (#20). * New `--exclude-files` and `--include-files` options for file filtering using globbing patterns (#21). * More CLI and config options can be used inline. * Underscores in inline option names can be replaced with spaces. * Inline options without arguments can be prefixed with `no` to invert meaning. * New built-in global set `busted` containing globals of Busted testing framework. * Stable interface for editor plugins. * New `luacheck.get_message` function for formatting a message for a warning or error. * Sets of standard globals can be merged using `+`. * If value of `std` option starts with `+`, new set is added to the old one instead of overwriting it, * New `--filename` option allows using real file name for picking config per-path overrides while passing source through stdin or a temporary file. * New `--ranges` option provides column ranges for tokens related to warnings (#32). * New `--no-self` option for ignoring warnings related to implicit `self` argument. * Config options can now be returned as a table. * Config now has access to all regular globals in its environment. * New sets of standard globals can be created by mutating global `stds` in config. * `formatter` config option now accepts functions. * Warnings returned from `luacheck.*` functions now have = `.end_column` field with last column of related token. * JUnit formatter now produces a testcase per each issue. ### Fixes * Fixed validation error when attempting to use `formatter` option in config. * Fixed incorrect error location for `invalid escape sequence` syntax errors. * FIxed spurious quotes in typecheck error messages in `luacheck.*` functions. * UTF BOM is now stripped when reading files. ## 0.10.0 (2015-03-13) ### Breaking changes * Removed `--limit`/`-l` option, use inline options to ignore warnings that are OK. * Removed `--no-unused-values`/`-v` option, use `--no-unused-secondaries`/`-s` instead. * Removed `--no-unset` option, use `--ignore 221` instead. ### New features and improvements * Added caching of check results (`--cache` and `--no-cache` options). * Added parallel checking (`--jobs`/`-j` option). * Added reporting of syntax error message and location in CLI (#17). * Added `--version` command for showing versions of Luacheck and its dependencies. * Added more functions to `luacheck` Lua module. ### Fixes * Fixed file status label not being colored when using `-qq`. ### Miscellaneous * Added installer script (`install.lua`). ## 0.9.0 (2015-02-15) ### New features and improvements * Added inline options: a way to precisely configure luacheck using inline comments of special format (#16). * Added an option to use custom output formatters; TAP and JUnit formatters are built-in (#19). ### Fixes * Fixed crash when checking stdin using a config with overrides. ## 0.8.0 (2015-01-19) ### New features and improvements * Added detection of unused labels. * Added detection of unreachable code. * Added detection of loops that can be executed at most once. * Added detection of uninitialized variables. * Added detection of shadowed local variables. * Added detection of empty blocks. * Added detection of unbalanced assignments. * New warning categorization system using warning codes. * Added possibility to mark globals as read-only (most standard globals are so by default). * Added possibility to overwrite options on per-directory basis in config. * Some CLI-specific options can now be used in config (e.g. `color`). * Added standard global sets for Lua 5.3. ### Miscellaneous * Removed unnecessary dependencies. * Simplified manual installation (#12). * Added executable wrapper for Windows (#14). ## 0.7.3 (2015-01-05) ### Fixes * Fixed false `unused variable` and `unused value` warnings when a closure accessing a variable is created in a nested block (#10). ## 0.7.2 (2015-01-03) ### Improvements * Improved analysis quality w.r.t unused values using flow-sensitive analysis. ## 0.7.1 (2014-12-16) ### Improvements * When `--no-color` is used, identifiers are now quoted in warning messages (#8). ### Fixes * Fixed priority of options: CLI options override config per-file overrides, which override general config. * Fixed ignoring `std` option in CLI when `compat` option is used in config. ## 0.7.0 (2014-11-23) ### New features and improvements * Added `--allow-defined-top` and `--module` options for more flexible checking of files which are supposed to set globals (#7). * Added `--no-unused-secondaries` option for removing warnings about unused values set together with used ones. * Added detection of variables that are used but never set. ### Fixes * Fixed ignoring `std` config option. * Fixed incompatibility with Lua 5.3. ## 0.6.0 (2014-11-01) ### New features and improvements * Luacheck can now check programs which use syntax introduced in Lua 5.2, Lua 5.3 and LuaJIT 2.0. * Luacheck is now faster. * Luacheck now exits with an error if it couldn't load a config due to an I/O, syntax, runtime or validation error. ### Miscellaneous * Removed dependency on MetaLua parser. ## 0.5.0 (2014-09-06) ### Breaking changes * Changed the interface of `luacheck` module. * Changed what `-qq` and `-qqq` do. ### New features and improvements * Added an option to disable colourization of output (#2). * Added an option to allow implicit global variable definition. * Filter out warnings about redefined `_` (#5). * `--globals`, `--ignore` and `--only` can now be used several times. * Passing `-` as an argument now checks stdin. * Passing a directory as an argument checks all `.lua` files inside it. * Added config loading (#1). * Added `--std` option, adding globals via `--globals` now does not require passing a dash. * Added `--new-globals` option. ## 0.4.1 (2014-08-25) ### Miscellaneous * Updated to argparse 0.3.0 ## 0.4.0 (2014-05-31) ### New features and improvements * Unused values (e.g. `local a = expr1; ... a = expr2`) are now detected. * In CLI, rockspecs (arguments ending with `.rockspec`) now expand into list of related `.lua` files. * Unused varargs are now detected. ## 0.3.0 (2014-04-25) ### New features and improvements * Luacheck is now _ENV-aware: "globals" inside chunks with custom `_ENV` are ignored, but their presence marks the `_ENV` variable as used; accessing the outermost ("default") `_ENV` is permitted, too. * In `--globals` option of the CLI hyphen now expands to all standard global variables. * New `-c`/`--compat` flag defines some additional globals for Lua 5.1/5.2 compatibility (e.g. `setfenv`). * New `-l`/`--limit` option allows setting a limit of warnings. If the limit is not exceeded, the CLI exits with `0`. * The `-q`/`--quiet` flag now can be used several times (`-q`/`-qq`/`-qqq`) to make the CLI more or less quiet. ## 0.2.0 (2014-04-05) ### New features and improvements * Command-line interface now prints per-file reports as they are produced instead of waiting for all files to be checked. * Luacheck now recognizes different types of variables (normal locals, function arguments and loop variables) and reports them accordingly. * Luacheck now distinguishes accessing global variables from setting them. * In command-line interface `-q` switch makes luacheck only print total number of warnings instead of suppressing output completely. ## 0.1.0 (2014-03-25) The first release. luacheck-0.25.0/LICENSE000066400000000000000000000021031410451437000143550ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 - 2018 Peter Melnichenko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. luacheck-0.25.0/README.md000066400000000000000000000167301410451437000146420ustar00rootroot00000000000000# Luacheck [![Join the chat at https://gitter.im/luacheck/Lobby](https://badges.gitter.im/luacheck/Lobby.svg)](https://gitter.im/luacheck/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/luarocks/luacheck.png?branch=master)](https://travis-ci.org/luarocks/luacheck) [![Windows build status](https://ci.appveyor.com/api/projects/status/pgox2vvelagw1fux/branch/master?svg=true&passingText=Windows%20build%20passing&failingText=Windows%20build%20failing)](https://ci.appveyor.com/project/luarocks/luacheck/branch/master) [![codecov](https://codecov.io/gh/luarocks/luacheck/branch/master/graph/badge.svg)](https://codecov.io/gh/luarocks/luacheck) [![License](https://img.shields.io/badge/License-MIT-brightgreen.svg)](LICENSE) ## Contents * [Overview](#overview) * [Installation](#installation) * [Basic usage](#basic-usage) * [Related projects](#related-projects) * [Documentation](#documentation) * [Development](#development) * [Building and testing](#building-and-testing) * [License](#license) ## Overview Luacheck is a static analyzer and a linter for [Lua](http://www.lua.org). Luacheck detects various issues such as usage of undefined global variables, unused variables and values, accessing uninitialized variables, unreachable code and more. Most aspects of checking are configurable: there are options for defining custom project-related globals, for selecting set of standard globals (version of Lua standard library), for filtering warnings by type and name of related variable, etc. The options can be used on the command line, put into a config or directly into checked files as Lua comments. Luacheck supports checking Lua files using syntax of Lua 5.1, Lua 5.2, Lua 5.3 and LuaJIT. Luacheck itself is written in Lua and runs on all of mentioned Lua versions. ## Installation ### Using LuaRocks From your command line run the following command (using `sudo` if necessary): ``` luarocks install luacheck ``` For parallel checking Luacheck additionally requires [LuaLanes](https://github.com/LuaLanes/lanes), which can be installed using LuaRocks as well (`luarocks install lanes`). ### Windows binary download For Windows there is single-file 64-bit binary distribution, bundling Lua 5.3.4, Luacheck, LuaFileSystem, and LuaLanes using [LuaStatic](https://github.com/ers35/luastatic): [download](https://github.com/luarocks/luacheck/releases/download/0.25.0/luacheck.exe). ## Basic usage After Luacheck is installed, run `luacheck` program from the command line. Pass a list of files, [rockspecs](https://github.com/luarocks/luarocks/wiki/Rockspec-format) or directories (requires LuaFileSystem) to be checked: ``` luacheck src extra_file.lua another_file.lua ``` ``` Checking src/good_code.lua OK Checking src/bad_code.lua 3 warnings src/bad_code.lua:3:23: unused variable length argument src/bad_code.lua:7:10: setting non-standard global variable embrace src/bad_code.lua:8:10: variable opt was previously defined as an argument on line 7 Checking src/python_code.lua 1 error src/python_code.lua:1:6: expected '=' near '__future__' Checking extra_file.lua 5 warnings extra_file.lua:3:18: unused argument baz extra_file.lua:4:8: unused loop variable i extra_file.lua:13:7: accessing uninitialized variable a extra_file.lua:14:1: value assigned to variable x is unused extra_file.lua:21:7: variable z is never accessed Checking another_file.lua 2 warnings another_file.lua:2:7: unused variable height another_file.lua:3:7: accessing undefined variable heigth Total: 10 warnings / 1 error in 5 files ``` For more info, see [documentation](https://luacheck.readthedocs.io/en/stable/). ## Related projects ### Editor support There are a few plugins which allow using Luacheck directly inside an editor, showing warnings inline: * For Vim, [Syntastic](https://github.com/vim-syntastic/syntastic) contains [luacheck checker](https://github.com/vim-syntastic/syntastic/wiki/Lua%3A---luacheck); * For Sublime Text 3 there is [SublimeLinter-luacheck](https://packagecontrol.io/packages/SublimeLinter-luacheck) which requires [SublimeLinter](https://sublimelinter.readthedocs.io/en/latest/); * For Atom there is [linter-luacheck](https://atom.io/packages/linter-luacheck) which requires [AtomLinter](https://github.com/steelbrain/linter); * For Emacs, [Flycheck](http://www.flycheck.org/en/latest/) contains [luacheck checker](http://www.flycheck.org/en/latest/languages.html#lua); * For Brackets, there is [linter.luacheck](https://github.com/Malcolm3141/brackets-luacheck) extension; * For Visual Studio code there is [vscode-luacheck](https://marketplace.visualstudio.com/items?itemName=dwenegar.vscode-luacheck) extension. [vscode-lua](https://marketplace.visualstudio.com/items?itemName=trixnz.vscode-lua) extension also includes Luacheck support. If you are a plugin developer, see [recommended way of using Luacheck in a plugin](http://luacheck.readthedocs.org/en/stable/cli.html#stable-interface-for-editor-plugins-and-tools). ### Other projects * [Luacheck bindings for Node.js](https://www.npmjs.com/package/luacheck); * [Luacheck plugin for Gulp](https://www.npmjs.com/package/gulp-luacheck). ## Documentation Documentation is available [online](https://luacheck.readthedocs.io/en/stable/). If Luacheck has been installed using LuaRocks, it can be browsed offline using `luarocks doc luacheck` command. Documentation can be built using [Sphinx](http://sphinx-doc.org/): `sphinx-build docsrc doc`, the files will be found inside `doc/`. ## Development Luacheck is currently in development. The latest released version is 0.25.0. The interface of the `luacheck` module may change between minor releases. The command line interface is fairly stable. Use the Luacheck issue tracker on GitHub to submit bugs, suggestions and questions. Any pull requests are welcome, too. ## Building and testing After the Luacheck repo is cloned and changes are made, run `luarocks make` (using `sudo` if necessary) from its root directory to install dev version of Luacheck. To run Luacheck using sources in current directory without installing it, run `lua -e 'package.path="./src/?.lua;./src/?/init.lua;"..package.path' bin/luacheck.lua ...`. To test Luacheck, ensure that you have [busted](http://olivinelabs.com/busted/) and [luautf8](https://github.com/starwing/luautf8) installed and run `busted`. ## License ``` The MIT License (MIT) Copyright (c) 2014 - 2018 Peter Melnichenko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` luacheck-0.25.0/appveyor.yml000066400000000000000000000011641410451437000157460ustar00rootroot00000000000000version: 1.0.{build} shallow_clone: true environment: matrix: - LUA: "lua 5.1" - LUA: "lua 5.2" - LUA: "lua 5.3" - LUA: "lua 5.4" - LUA: "luajit 2.0" - LUA: "luajit 2.1" before_build: - set PATH=C:\Python27\Scripts;%PATH% - pip install hererocks - pip install codecov - hererocks here --%LUA% -r latest - call here\bin\activate - luarocks install busted - luarocks install cluacov - luarocks install luautf8 - luarocks install luasocket build_script: - luarocks make test_script: - busted -c - luacheck . - luacheck . after_test: - luacov - codecov -f luacov.report.out -X gcov luacheck-0.25.0/bin/000077500000000000000000000000001410451437000141245ustar00rootroot00000000000000luacheck-0.25.0/bin/luacheck.bat000066400000000000000000000000521410451437000163700ustar00rootroot00000000000000@echo off lua.exe "%~dp0\luacheck.lua" %* luacheck-0.25.0/bin/luacheck.lua000077500000000000000000000000531410451437000164070ustar00rootroot00000000000000#!/usr/bin/env lua require "luacheck.main" luacheck-0.25.0/build/000077500000000000000000000000001410451437000144535ustar00rootroot00000000000000luacheck-0.25.0/build/Makefile000066400000000000000000000050141410451437000161130ustar00rootroot00000000000000# Makefile for (cross)compiling luacheck binaries. # Do not use directly, run scripts/build-binaries.sh instead. LUA_VERSION= 5.3.5 LFS_VERSION= 1.7.0-2 ARGPARSE_VERSION= 0.6.0-1 LANES_VERSION= 3.10.1-1 LUA_DIR= lua-$(LUA_VERSION) LFS_DIR= luafilesystem-$(LFS_VERSION)/luafilesystem ARGPARSE_DIR= argparse-$(ARGPARSE_VERSION)/argparse LANES_DIR= lanes-$(LANES_VERSION)/lanes BASE_CC= gcc BASE_AR= ar rc BASE_RANLIB= ranlib BASE_STRIP= strip BASE_NM= nm CROSS= CC= $(CROSS)$(BASE_CC) CFLAGS= -O2 -Wall -Wextra AR= $(CROSS)$(BASE_AR) NM= $(CROSS)$(BASE_NM) RANLIB= $(CROSS)$(BASE_RANLIB) STRIP= $(CROSS)$(BASE_STRIP) SUFFIX= TARGET= bin/luacheck$(SUFFIX) LUA_O= $(patsubst %.c,%.o,$(filter-out $(addprefix $(LUA_DIR)/src/,lua.c luac.c print.c),$(wildcard $(LUA_DIR)/src/*.c))) LUA_A= $(LUA_DIR)/src/liblua.a LFS_O= $(patsubst %.c,%.o,$(wildcard $(LFS_DIR)/src/*.c)) LFS_A= $(LFS_DIR)/src/lfs.a LANES_O= $(patsubst %.c,%.o,$(wildcard $(LANES_DIR)/src/*.c)) LANES_A= $(LANES_DIR)/src/lanes.a default: $(TARGET) $(LUA_DIR): @echo @echo "=== Downloading Lua $(LUA_VERSION) ===" @echo curl "https://www.lua.org/ftp/$(LUA_DIR).tar.gz" | tar xz $(LFS_DIR): @echo @echo "=== Downloading LuaFileSystem $(LFS_VERSION) ===" @echo luarocks unpack luafilesystem $(LFS_VERSION) $(ARGPARSE_DIR): @echo @echo "=== Downloading argparse $(ARGPARSE_VERSION) ===" @echo luarocks unpack argparse $(ARGPARSE_VERSION) $(LANES_DIR): @echo @echo "=== Downloading Lanes $(LANES_VERSION) ===" @echo luarocks unpack lanes $(LANES_VERSION) fetch: $(LUA_DIR) $(LFS_DIR) $(ARGPARSE_DIR) $(LANES_DIR) $(LUA_O): CFLAGS+= $(if $(LINUX),-DLUA_USE_POSIX) $(LUA_A): $(LUA_O) $(LFS_O): CFLAGS+= -I$(LUA_DIR)/src $(LFS_A): $(LFS_O) $(LANES_O): CFLAGS+= -I$(LUA_DIR)/src $(LANES_A): $(LANES_O) %.a: $(AR) $@ $^ $(RANLIB) $@ $(TARGET): $(LUA_A) $(LFS_A) $(LANES_A) cp $(LUA_A) . cp $(LFS_A) . cp $(ARGPARSE_DIR)/src/argparse.lua . cp $(LANES_A) . cp $(LANES_DIR)/src/lanes.lua . cp -r ../src/luacheck . cp -f bin/luacheck.lua bin/luacheck_bin.lua CC=$(CC) NM=$(NM) RANLIB=$(RANLIB) luastatic bin/luacheck_bin.lua luacheck/*.lua luacheck/*/*.lua luacheck/*/*/*.lua argparse.lua lanes.lua $(LUA_A) $(LFS_A) $(LANES_A) -lm $(if $(LINUX),-lpthread) -I$(LUA_DIR)/src rm luacheck_bin.luastatic.c $(STRIP) luacheck_bin* mv luacheck_bin* $(TARGET) clean: rm -f $(TARGET) luacheck.luastatic.c rm -f $(LUA_O) $(LUA_A) $(LFS_O) $(LFS_A) $(LANES_O) $(LANES_A) rm -f argparse.lua lanes.lua lfs.a lanes.a liblua.a rm -rf luacheck rm -f luacheck_bin* .PHONY: default fetch clean luacheck-0.25.0/build/bin/000077500000000000000000000000001410451437000152235ustar00rootroot00000000000000luacheck-0.25.0/build/bin/luacheck.lua000066400000000000000000000003071410451437000175050ustar00rootroot00000000000000-- Version of bin/luacheck.lua for use in Luacheck binaries. -- Do not load modules from filesystem in case a bundled module is broken. package.path = "" package.cpath = "" require "luacheck.main" luacheck-0.25.0/docsrc/000077500000000000000000000000001410451437000146315ustar00rootroot00000000000000luacheck-0.25.0/docsrc/cli.rst000066400000000000000000000402511410451437000161340ustar00rootroot00000000000000Command line interface ====================== ``luacheck`` program accepts files, directories and `rockspecs `_ as arguments. They can be filtered using ``--include-files`` and ``--exclude-files`` options, see below. * Given a file, ``luacheck`` will check it. * Given ``-``, ``luacheck`` will check stdin. * Given a directory, ``luacheck`` will check all files within it, selecting only files with ``.lua`` extension unless ``--include-files`` option is used. This feature requires `LuaFileSystem `_ (installed automatically if LuaRocks was used to install Luacheck). * Given a rockspec (a file with ``.rockspec`` extension), ``luacheck`` will check all files with ``.lua`` extension mentioned in the rockspec in ``build.install.lua``, ``build.install.bin`` and ``build.modules`` tables. The output of ``luacheck`` consists of separate reports for each checked file and ends with a summary:: $ luacheck src Checking src/bad_code.lua 5 warnings src/bad_code.lua:3:16: unused variable helper src/bad_code.lua:3:23: unused variable length argument src/bad_code.lua:7:10: setting non-standard global variable embrace src/bad_code.lua:8:10: variable opt was previously defined as an argument on line 7 src/bad_code.lua:9:11: accessing undefined variable hepler Checking src/good_code.lua OK Checking src/python_code.lua 1 error src/python_code.lua:1:6: expected '=' near '__future__' Checking src/unused_code.lua 9 warnings src/unused_code.lua:3:18: unused argument baz src/unused_code.lua:4:8: unused loop variable i src/unused_code.lua:5:13: unused variable q src/unused_code.lua:7:11: unused loop variable a src/unused_code.lua:7:14: unused loop variable b src/unused_code.lua:7:17: unused loop variable c src/unused_code.lua:13:7: value assigned to variable x is unused src/unused_code.lua:14:1: value assigned to variable x is unused src/unused_code.lua:22:1: value assigned to variable z is unused Total: 14 warnings / 1 error in 4 files ``luacheck`` chooses exit code as follows: * Exit code is ``0`` if no warnings or errors occurred. * Exit code is ``1`` if some warnings occurred but there were no syntax errors or invalid inline options. * Exit code is ``2`` if there were some syntax errors or invalid inline options. * Exit code is ``3`` if some files couldn't be checked, typically due to an incorrect file name. * Exit code is ``4`` if there was a critical error (invalid CLI arguments, config, or cache file). .. _cliopts: Command line options -------------------- Short options that do not take an argument can be combined into one, so that ``-qqu`` is equivalent to ``-q -q -u``. For long options, both ``--option value`` or ``--option=value`` can be used. Options taking several arguments can be used several times; ``--ignore foo --ignore bar`` is equivalent to ``--ignore foo bar``. Note that options that may take several arguments, such as ``--globals``, should not be used immediately before positional arguments; given ``--globals foo bar file.lua``, ``luacheck`` will consider all ``foo``, ``bar`` and ``file.lua`` global and then panic as there are no file names left. ======================================= ================================================================================ Option Meaning ======================================= ================================================================================ ``-g | --no-global`` Filter out warnings related to global variables. ``-u | --no-unused`` Filter out warnings related to unused variables and values. ``-r | --no-redefined`` Filter out warnings related to redefined variables. ``-a | --no-unused-args`` Filter out warnings related to unused arguments and loop variables. ``-s | --no-unused-secondaries`` Filter out warnings related to unused variables set together with used ones. See :ref:`secondaryvaluesandvariables` ``--no-self`` Filter out warnings related to implicit ``self`` argument. ``--std `` Set standard globals, default is ``max``. ```` can be one of: * ``max`` - union of globals of Lua 5.1, Lua 5.2, Lua 5.3 and LuaJIT 2.x; * ``min`` - intersection of globals of Lua 5.1, Lua 5.2, Lua 5.3 and LuaJIT 2.x; * ``lua51`` - globals of Lua 5.1 without deprecated ones; * ``lua51c`` - globals of Lua 5.1; * ``lua52`` - globals of Lua 5.2; * ``lua52c`` - globals of Lua 5.2 compiled with LUA_COMPAT_ALL; * ``lua53`` - globals of Lua 5.3; * ``lua53c`` - globals of Lua 5.3 compiled with LUA_COMPAT_5_2; * ``lua54`` - globals of Lua 5.4; * ``lua54c`` - globals of Lua 5.4 compiled with LUA_COMPAT_5_3; * ``luajit`` - globals of LuaJIT 2.x; * ``ngx_lua`` - globals of Openresty `lua-nginx-module `_ 0.10.10, including standard LuaJIT 2.x globals; * ``love`` - globals added by `LÖVE `_; * ``busted`` - globals added by Busted 2.0, by default added for files ending with ``_spec.lua`` within ``spec``, ``test``, and ``tests`` subdirectories; * ``rockspec`` - globals allowed in rockspecs, by default added for files ending with ``.rockspec``; * ``luacheckrc`` - globals allowed in Luacheck configs, by default added for files ending with ``.luacheckrc``; * ``none`` - no standard globals. See :ref:`stds` ``--globals [] ...`` Add custom global variables or fields on top of standard ones. See :ref:`fields` ``--read-globals [] ...`` Add read-only global variables or fields. ``--new-globals [] ...`` Set custom global variables or fields. Removes custom globals added previously. ``--new-read-globals [] ...`` Set read-only global variables or fields. Removes read-only globals added previously. ``--not-globals [] ...`` Remove custom and standard global variables or fields. ``-c | --compat`` Equivalent to ``--std max``. ``-d | --allow-defined`` Allow defining globals implicitly by setting them. See :ref:`implicitlydefinedglobals` ``-t | --allow-defined-top`` Allow defining globals implicitly by setting them in the top level scope. See :ref:`implicitlydefinedglobals` ``-m | --module`` Limit visibility of implicitly defined globals to their files. See :ref:`modules` ``--max-line-length `` Set maximum allowed line length (default: 120). ``--no-max-line-length`` Do not limit line length. ``--max-code-line-length `` Set maximum allowed length for lines ending with code (default: 120). ``--no-max-code-line-length`` Do not limit code line length. ``--max-string-line-length `` Set maximum allowed length for lines within a string (default: 120). ``--no-max-string-line-length`` Do not limit string line length. ``--max-comment-line-length `` Set maximum allowed length for comment lines (default: 120). ``--no-max-comment-line-length`` Do not limit comment line length. ``--max-cyclomatic-complexity `` Set maximum cyclomatic complexity for functions. ``--no-max-cyclomatic-complexity`` Do not limit function cyclomatic complexity (default). ``--ignore | -i [] ...`` Filter out warnings matching patterns. ``--enable | -e [] ...`` Do not filter out warnings matching patterns. ``--only | -o [] ...`` Filter out warnings not matching patterns. ``--config `` Path to custom configuration file (default: ``.luacheckrc``). ``--no-config`` Do not look up custom configuration file. ``--default-config `` Default path to custom configuration file, to be used if ``--[no-]config`` is not used and ``.luacheckrc`` is not found. Default global location is: * ``%LOCALAPPDATA%\Luacheck\.luacheckrc`` on Windows; * ``~/Library/Application Support/Luacheck/.luacheckrc`` on OS X/macOS; * ``$XDG_CONFIG_HOME/luacheck/.luacheckrc`` or ``~/.config/luacheck/.luacheckrc`` on other systems. ``--no-default-config`` Do not use fallback configuration file. ``--filename `` Use another filename in output, for selecting configuration overrides and for file filtering. ``--exclude-files [] ...`` Do not check files matching these globbing patterns. Recursive globs such as ``**/*.lua`` are supported. ``--include-files [] ...`` Do not check files not matching these globbing patterns. ``--cache []`` Path to cache file. (default: ``.luacheckcache``). See :ref:`cache` ``--no-cache`` Do not use cache. ``-j | --jobs`` Check ```` files in parallel. Requires `LuaLanes `_. Default number of jobs is set to number of available processing units. ``--formatter `` Use custom formatter. ```` must be a module name or one of: * ``TAP`` - Test Anything Protocol formatter; * ``JUnit`` - JUnit XML formatter; * ``visual_studio`` - MSBuild/Visual Studio aware formatter; * ``plain`` - simple warning-per-line formatter; * ``default`` - standard formatter. ``-q | --quiet`` Suppress report output for files without warnings. * ``-qq`` - Suppress output of warnings. * ``-qqq`` - Only output summary. ``--codes`` Show warning codes. ``--ranges`` Show ranges of columns related to warnings. ``--no-color`` Do not colorize output. ``-v | --version`` Show version of Luacheck and its dependencies and exit. ``-h | --help`` Show help and exit. ======================================= ================================================================================ .. _patterns: Patterns -------- CLI options ``--ignore``, ``--enable`` and ``--only`` and corresponding config options allow filtering warnings using pattern matching on warning codes, variable names or both. If a pattern contains a slash, the part before slash matches warning code and the part after matches variable name. Otherwise, if a pattern contains a letter or underscore, it matches variable name. Otherwise, it matches warning code. E.g.: ======= ========================================================================= Pattern Matching warnings ======= ========================================================================= 4.2 Shadowing declarations of arguments or redefining them. .*_ Warnings related to variables with ``_`` suffix. 4.2/.*_ Shadowing declarations of arguments with ``_`` suffix or redefining them. ======= ========================================================================= Unless already anchored, patterns matching variable names are anchored at both sides and patterns matching warning codes are anchored at their beginnings. This allows one to filter warnings by category (e.g. ``--only 1`` focuses ``luacheck`` on global-related warnings). .. _fields: Defining extra globals and fields --------------------------------- CLI options ``--globals``, ``--new-globals``, ``--read-globals``, ``--new-read-globals``, and corresponding config options add new allowed globals or fields. E.g. ``--read-globals foo --globals foo.bar`` allows accessing ``foo`` global and mutating its ``bar`` field. ``--not-globals`` also operates on globals and fields and removes definitions of both standard and custom globals. .. _stds: Sets of standard globals ------------------------ CLI option ``--stds`` allows combining built-in sets described above using ``+``. For example, ``--std max`` is equivalent to ``--std=lua51c+lua52c+lua53c+luajit``. Leading plus sign adds new sets to current one instead of replacing it. For instance, ``--std +love`` is suitable for checking files using `LÖVE `_ framework. Custom sets of globals can be defined by mutating global variable ``stds`` in config. See :ref:`custom_stds` Formatters ---------- CLI option ``--formatter`` allows selecting a custom formatter for ``luacheck`` output. A custom formatter is a Lua module returning a function with three arguments: report as returned by ``luacheck`` module (see :ref:`report`), array of file names and table of options. Options contain values assigned to ``quiet``, ``color``, ``limit``, ``codes``, ``ranges`` and ``formatter`` options in CLI or config. Formatter function must return a string. .. _cache: Caching ------- If LuaFileSystem is available, Luacheck can cache results of checking files. On subsequent checks, only files which have changed since the last check will be rechecked, improving run time significantly. Changing options (e.g. defining additional globals) does not invalidate cache. Caching can be enabled by using ``--cache `` option or ``cache`` config option. Using ``--cache`` without an argument or setting ``cache`` config option to ``true`` sets ``.luacheckcache`` as the cache file. Note that ``--cache`` must be used every time ``luacheck`` is run, not on the first run only. Stable interface for editor plugins and tools --------------------------------------------- Command-line interface of Luacheck can change between minor releases. Starting from 0.11.0 version, the following interface is guaranteed at least till 1.0.0 version, and should be used by tools using Luacheck output, e.g. editor plugins. * Luacheck should be started from the directory containing the checked file. * File can be passed through stdin using ``-`` as argument or using a temporary file. Real filename should be passed using ``--filename`` option. * Plain formatter should be used. It outputs one issue (warning or error) per line. * To get precise error location, ``--ranges`` option can be used. Each line starts with real filename (passed using ``--filename``), followed by ``::-:``, where ```` is line number on which issue occurred and ``-`` is inclusive range of columns of token related to issue. Numbering starts from 1. If ``--ranges`` is not used, end column and dash is not printed. * To get warning and error codes, ``--codes`` option can be used. For each line, substring between parentheses contains three digit issue code, prefixed with ``E`` for errors and ``W`` for warnings. Lack of such substring indicates a fatal error (e.g. I/O error). * The rest of the line is warning message. If compatibility with older Luacheck version is desired, output of ``luacheck --help`` can be used to get its version. If it contains string ``0..``, where ```` is at least 11 and ``patch`` is any number, interface described above should be used. luacheck-0.25.0/docsrc/conf.py000066400000000000000000000202751410451437000161360ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # luacheck documentation build configuration file, created by # sphinx-quickstart on Thu Sep 4 16:25:37 2014. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'luacheck' copyright = u'2014 - 2018, Peter Melnichenko' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0.25.0' # The full version, including alpha/beta/rc tags. release = '0.25.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. import os if os.environ.get('READTHEDOCS', None) != 'True': try: import sphinx_rtd_theme html_theme = 'sphinx_rtd_theme' html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] except ImportError: pass # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = [] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'luacheckdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'luacheck.tex', u'luacheck Documentation', u'Peter Melnichenko', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'luacheck', u'luacheck Documentation', [u'Peter Melnichenko'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'luacheck', u'luacheck Documentation', u'Peter Melnichenko', 'luacheck', 'A simple Lua static analyzer.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False luacheck-0.25.0/docsrc/config.rst000066400000000000000000000212031410451437000166260ustar00rootroot00000000000000Configuration file ================== ``luacheck`` tries to load configuration from ``.luacheckrc`` file in the current directory. If not found, it will look for it in the parent directory and so on, going up until it reaches file system root. Path to config can be set using ``--config`` option, in which case it will be used during recursive loading. Paths within config are interpreted relatively to the directory from which it was loaded. Config loading can be disabled using ``--no-config`` flag. If neither of ``--config``, ``--no-config``, and ``--no-default-config`` options are used, ``luacheck`` will attempt to load configuration from value of ``--default-config`` option, or ``%LOCALAPPDATA%\Luacheck\.luacheckrc`` on Windows, ``~/Library/Application Support/Luacheck/.luacheckrc`` on OS X/macOS, and ``$XDG_CONFIG_HOME/luacheck/.luacheckrc`` or ``~/.config/luacheck/.luacheckrc`` on other systems by default. Paths within default config are interpreted relatively to the current directory. Config is simply a Lua script executed by ``luacheck``. It may set various options by assigning to globals or by returning a table with option names as keys. Options loaded from config have the lowest priority: it's possible to overwrite them with CLI options or inline options. .. _options: Config options -------------- ============================= ======================================== =================== Option Type Default value ============================= ======================================== =================== ``quiet`` Integer in range 0..3 ``0`` ``color`` Boolean ``true`` ``codes`` Boolean ``false`` ``ranges`` Boolean ``false`` ``formatter`` String or function ``"default"`` ``cache`` Boolean or string ``false`` ``jobs`` Positive integer ``1`` ``exclude_files`` Array of strings ``{}`` ``include_files`` Array of strings (Include all files) ``global`` Boolean ``true`` ``unused`` Boolean ``true`` ``redefined`` Boolean ``true`` ``unused_args`` Boolean ``true`` ``unused_secondaries`` Boolean ``true`` ``self`` Boolean ``true`` ``std`` String or set of standard globals ``"max"`` ``globals`` Array of strings or field definition map ``{}`` ``new_globals`` Array of strings or field definition map (Do not overwrite) ``read_globals`` Array of strings or field definition map ``{}`` ``new_read_globals`` Array of strings or field definition map (Do not overwrite) ``not_globals`` Array of strings ``{}`` ``compat`` Boolean ``false`` ``allow_defined`` Boolean ``false`` ``allow_defined_top`` Boolean ``false`` ``module`` Boolean ``false`` ``max_line_length`` Number or ``false`` ``120`` ``max_code_line_length`` Number or ``false`` ``120`` ``max_string_line_length`` Number or ``false`` ``120`` ``max_comment_line_length`` Number or ``false`` ``120`` ``max_cyclomatic_complexity`` Number or ``false`` ``false`` ``ignore`` Array of patterns (see :ref:`patterns`) ``{}`` ``enable`` Array of patterns ``{}`` ``only`` Array of patterns (Do not filter) ============================= ======================================== =================== An example of a config which makes ``luacheck`` ensure that only globals from the portable intersection of Lua 5.1, Lua 5.2, Lua 5.3 and LuaJIT 2.0 are used, as well as disables detection of unused arguments: .. code-block:: lua :linenos: std = "min" ignore = {"212"} .. _custom_stds: Custom sets of globals ---------------------- ``std`` option allows setting a custom standard set of globals using a table. This table can have two fields: ``globals`` and ``read_globals``. Both of them should contain a field definition map defining some globals. The simplest way to define globals is to list their names: .. code-block:: lua :linenos: std = { globals = {"foo", "bar"}, -- these globals can be set and accessed. read_globals = {"baz", "quux"} -- these globals can only be accessed. } For globals defined like this Luacheck will additionally consider any fields within them defined. To define a global with a restricted set of fields, use global name as key and a table as value. In that table, ``fields`` subtable can contain the fields in the same format: .. code-block:: lua :linenos: std = { read_globals = { foo = { -- Defining read-only global `foo`... fields = { field1 = { -- `foo.field1` is now defined... fields = { nested_field = {} -- `foo.field1.nested_field` is now defined... } }, field2 = {} -- `foo.field2` is defined. } } } } Globals and fields can be marked read-only or not using ``read_only`` property with a boolean value. Property ``other_fields`` controls whether the global or field can also contain other unspecified fields: .. code-block:: lua :linenos: std = { read_globals = { foo = { -- `foo` and its fields are read-only by default (because they are within `read_globals` table). fields = { bar = { read_only = false, -- `foo.bar` is not read-only, can be set. other_fields = true, -- `foo.bar[anything]` is defined and can be set or mutated (inherited from `foo.bar`). fields = { baz = {read_only = true}, -- `foo.bar.baz` is read-only as an exception. } } } } } } Custom sets can be given names by mutating global ``stds`` variable, so that they can then be used in ``--std`` CLI option and ``std`` inline and config option. .. code-block:: lua :linenos: stds.some_lib = {...} std = "min+some_lib" In config, ``globals``, ``new_globals``, ``read_globals``, and ``new_read_globals`` can also contain definitions in same format: .. code-block:: lua :linenos: read_globals = { server = { fields = { -- Allow mutating `server.sessions` with any keys... sessions = {read_only = false, other_fields = true}, -- other fields... } }, --- other globals... } Per-file and per-path overrides ------------------------------- The environment in which ``luacheck`` loads the config contains a special global ``files``. When checking a file ````, ``luacheck`` will override options from the main config with entries from ``files[]`` if ```` matches ````, applying entries for more general globs first. For example, the following config re-enables detection of unused arguments only for files in ``src/dir``, but not for files ending with ``_special.lua``: .. code-block:: lua :linenos: std = "min" ignore = {"212"} files["src/dir"] = {enable = {"212"}} files["src/dir/**/*_special.lua"] = {ignore = {"212"}} Note that ``files`` table supports autovivification, so that .. code-block:: lua files["src/dir"].enable = {"212"} and .. code-block:: lua files["src/dir"] = {enable = {"212"}} are equivalent. Default per-path std overrides ------------------------------ ``luacheck`` uses a set of default per-path overrides: .. code-block:: lua :linenos: files["**/spec/**/*_spec.lua"].std = "+busted" files["**/test/**/*_spec.lua"].std = "+busted" files["**/tests/**/*_spec.lua"].std = "+busted" files["**/*.rockspec"].std = "+rockspec" files["**/*.luacheckrc"].std = "+luacheckrc" Each of these can be overriden by setting a different ``std`` value for the corresponding key in ``files``. luacheck-0.25.0/docsrc/index.rst000066400000000000000000000004071410451437000164730ustar00rootroot00000000000000Luacheck documentation ====================== Contents: .. toctree:: warnings cli config inline module This is documentation for 0.25.0 version of `Luacheck `_, a linter for `Lua `_. luacheck-0.25.0/docsrc/inline.rst000066400000000000000000000055401410451437000166450ustar00rootroot00000000000000Inline options ============== 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. 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: ======================= ==================================================================== Option Number of arguments ======================= ==================================================================== global 0 unused 0 redefined 0 unused args 0 unused secondaries 0 self 0 compat 0 module 0 allow defined 0 allow defined top 0 max line length 1 (with ``no`` and no arguments disables line length checks) max code line length 1 (with ``no`` and no arguments disables code line length checks) max string line length 1 (with ``no`` and no arguments disables string line length checks) max comment line length 1 (with ``no`` and no arguments disables comment line length checks) std 1 globals 0+ new globals 0+ read globals 0+ new read globals 0+ not globals 0+ ignore 0+ (without arguments everything is ignored) enable 1+ only 1+ ======================= ==================================================================== Options that take no arguments can be prefixed with ``no`` to invert their meaning. E.g. ``--luacheck: no unused args`` disables unused argument warnings. 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: .. code-block:: lua :linenos: -- luacheck: globals g1 g2, ignore foo local foo = g1(g2) -- No warnings emitted. -- The following unused function is not reported. local function f() -- luacheck: ignore -- luacheck: globals g3 g3() -- No warning. end g3() -- Warning is emitted as the inline option defining g3 only affected function f. For fine-grained control over inline option visibility use ``luacheck: push`` and ``luacheck: pop`` directives: .. code-block:: lua :linenos: -- luacheck: push ignore foo foo() -- No warning. -- luacheck: pop foo() -- Warning is emitted. luacheck-0.25.0/docsrc/module.rst000066400000000000000000000072001410451437000166470ustar00rootroot00000000000000Luacheck module =============== Use ``local luacheck = require "luacheck"`` to import ``luacheck`` module. It contains the following functions: * ``luacheck.get_report(source)``: Given source string, returns analysis data (a table). * ``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`. * ``luacheck.check_strings(sources, options)``: Checks array of sources using options, returns final report. Tables with field ``fatal`` within ``sources`` array are ignored. * ``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. * ``luacheck.get_message(issue)``: Returns a string message for an issue, see :ref:`report`. ``luacheck._VERSION`` contains Luacheck version as a string in ``MAJOR.MINOR.PATCH`` format. Using ``luacheck`` as a function is equivalent to calling ``luacheck.check_files``. .. _report: Report format ------------- 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. 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. 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: ============= ============================================================================================================== Codes Additional fields ============= ============================================================================================================== 011 ``msg`` field contains syntax error message. 111 ``module`` field indicates that assignment is to a non-module global variable. 122, 142, 143 ``indirect`` field indicates that the global field was accessed using a local alias. 122, 142, 143 ``field`` field contains string representation of related global field. 211 ``func`` field indicates that unused variable is a function. 211 ``recursive`` field indicates that unused function is recursive. 211 ``mutually_recursive`` field is set for unused mutually recursive functions. 314 ``field`` field contains string representation of ununsed field or index. 011 ``prev_line``, ``prev_column``, and ``prev_end_column`` fields may point to an extra relevant location, such as the opening unpaired bracket. 4.. ``prev_line``, ``prev_column``, and ``prev_end_column`` fields contain location of the overwritten definition. 521 ``label`` field contains label name. 631 ``line_ending`` field contains ``"comment"`` or ``"string"`` if line ending is within a comment or a string. 631 ``max_length`` field contains maximum allowed line length. ============= ============================================================================================================== Other fields may be present for internal reasons. luacheck-0.25.0/docsrc/warnings.rst000066400000000000000000000253161410451437000172220ustar00rootroot00000000000000List of warnings ================ Warnings produced by Luacheck are categorized using three-digit warning codes. Warning codes can be displayed in CLI output using ``--codes`` CLI option or ``codes`` config option. Errors also have codes starting with zero; unlike warnings, they can not be ignored. ==== ============================================================================= Code Description ==== ============================================================================= 011 A syntax error. 021 An invalid inline option. 022 An unpaired inline push directive. 023 An unpaired inline pop directive. 111 Setting an undefined global variable. 112 Mutating an undefined global variable. 113 Accessing an undefined global variable. 121 Setting a read-only global variable. 122 Setting a read-only field of a global variable. 131 Unused implicitly defined global variable. 142 Setting an undefined field of a global variable. 143 Accessing an undefined field of a global variable. 211 Unused local variable. 212 Unused argument. 213 Unused loop variable. 221 Local variable is accessed but never set. 231 Local variable is set but never accessed. 232 An argument is set but never accessed. 233 Loop variable is set but never accessed. 241 Local variable is mutated but never accessed. 311 Value assigned to a local variable is unused. 312 Value of an argument is unused. 313 Value of a loop variable is unused. 314 Value of a field in a table literal is unused. 321 Accessing uninitialized local variable. 331 Value assigned to a local variable is mutated but never accessed. 341 Mutating uninitialized local variable. 411 Redefining a local variable. 412 Redefining an argument. 413 Redefining a loop variable. 421 Shadowing a local variable. 422 Shadowing an argument. 423 Shadowing a loop variable. 431 Shadowing an upvalue. 432 Shadowing an upvalue argument. 433 Shadowing an upvalue loop variable. 511 Unreachable code. 512 Loop can be executed at most once. 521 Unused label. 531 Left-hand side of an assignment is too short. 532 Left-hand side of an assignment is too long. 541 An empty ``do`` ``end`` block. 542 An empty ``if`` branch. 551 An empty statement. 561 Cyclomatic complexity of a function is too high. 571 A numeric for loop goes from #(expr) down to 1 or less without negative step. 611 A line consists of nothing but whitespace. 612 A line contains trailing whitespace. 613 Trailing whitespace in a string. 614 Trailing whitespace in a comment. 621 Inconsistent indentation (``SPACE`` followed by ``TAB``). 631 Line is too long. ==== ============================================================================= Global variables (1xx) ---------------------- For each file, Luacheck builds list of defined globals and fields which can be used there. By default only globals from Lua standard library are defined; custom globals can be added using ``--globals`` CLI option or ``globals`` config option, and version of standard library can be selected using ``--std`` CLI option or ``std`` config option. When an undefined global or field is set, mutated or accessed, Luacheck produces a warning. Read-only globals ^^^^^^^^^^^^^^^^^ By default, most standard globals and fields are marked as read-only, so that setting them produces a warning. Custom read-only globals and fields can be added using ``--read-globals`` CLI option or ``read_globals`` config option, or using a custom set of globals. See :ref:`custom_stds` Globals and fields that are not read-only by default: * ``_G`` * ``_ENV`` (treated as a global by Luacheck) * ``package.path`` * ``package.cpath`` * ``package.loaded`` * ``package.preload`` * ``package.loaders`` * ``package.searchers`` .. _implicitlydefinedglobals: Implicitly defined globals ^^^^^^^^^^^^^^^^^^^^^^^^^^ Luacheck can be configured to consider globals assigned under some conditions to be defined implicitly. When ``-d``/``--allow_defined`` CLI option or ``allow_defined`` config option is used, all assignments to globals define them; when ``-t``/``--allow_defined_top`` CLI option or ``allow_defined_top`` config option is used, assignments to globals in the top level function scope (also known as main chunk) define them. A warning is produced when an implicitly defined global is not accessed anywhere. .. _modules: Modules ^^^^^^^ Files can be marked as modules using ``-m``/``--module`` CLI option or ``module`` config option to simulate semantics of the deprecated `module `_ function. Globals implicitly defined inside a module are considired part of its interface, are not visible outside and are not reported as unused. Assignments to other globals are not allowed, even to defined ones. Unused variables (2xx) and values (3xx) --------------------------------------- Luacheck generates warnings for all unused local variables except one named ``_``. It also detects variables which are set but never accessed or accessed but never set. Unused values and uninitialized variables ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ For each value assigned to a local variable, Luacheck computes set of expressions where it could be used. Warnings are produced for unused values (when a value can't be used anywhere) and for accessing uninitialized variables (when no values can reach an expression). E.g. in the following snippet value assigned to ``foo`` on line 1 is unused, and variable ``bar`` is uninitialized on line 9: .. code-block:: lua :linenos: local foo = expr1() local bar if condition() then foo = expr2() bar = expr3() else foo = expr4() print(bar) end return foo, bar .. _secondaryvaluesandvariables: Secondary values and variables ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unused value assigned to a local variable is secondary if its origin is the last item on the RHS of assignment, and another value from that item is used. Secondary values typically appear when result of a function call is put into locals, and only some of them are later used. For example, here value assigned to ``b`` is secondary, value assigned to ``c`` is used, and value assigned to ``a`` is simply unused: .. code-block:: lua :linenos: local a, b, c = f(), g() return c A variable is secondary if all values assigned to it are secondary. In the snippet above, ``b`` is a secondary variable. Warnings related to unused secondary values and variables can be removed using ``-s``/``--no-unused-secondaries`` CLI option or ``unused_secondaries`` config option. Shadowing declarations (4xx) ---------------------------- Luacheck detects declarations of local variables shadowing previous declarations, unless the variable is named ``_``. If the previous declaration is in the same scope as the new one, it is called redefining. Note that it is **not** necessary to define a new local variable when overwriting an argument: .. code-block:: lua :linenos: local function f(x) local x = x or "default" -- bad end local function f(x) x = x or "default" -- good end Control flow and data flow issues (5xx) --------------------------------------- Unreachable code ^^^^^^^^^^^^^^^^ Luacheck detects unreachable code. It also detects it if end of a loop block is unreachable, which means that the loop can be executed at most once: .. code-block:: lua :linenos: for i = 1, 100 do -- Break statement is outside the `if` block, -- so that the loop always stops after the first iteration. if cond(i) then f() end break end Unused labels ^^^^^^^^^^^^^ Labels that are not used by any ``goto`` statements are reported as unused. Unbalanced assignments ^^^^^^^^^^^^^^^^^^^^^^ If an assignment has left side and right side with different lengths, the assignment is unbalanced and Luacheck warns about it. An exception is initializing several local variables in a single statement while leaving some uninitialized: .. code-block:: lua :linenos: local a, b, c = nil -- Effectively sets `a`, `b`, and `c` to nil, no warning. Empty blocks ^^^^^^^^^^^^ Luacheck warns about empty ``do`` ``end`` blocks and empty ``if`` branches (``then`` ``else``, ``then`` ``elseif``, and ``then`` ``end``). Empty statements ^^^^^^^^^^^^^^^^ In Lua 5.2+ semicolons are considered statements and can appear even when not following normal statements. Such semicolons produce Luacheck warnings as they are completely useless. Cyclomatic complexity ^^^^^^^^^^^^^^^^^^^^^ If a limit is set using ``--max-cyclomatic-complexity`` CLI option or corresponding config or inline options, Luacheck warns about functions with too high cyclomatic complexity. Reversed numeric for loops ^^^^^^^^^^^^^^^^^^^^^^^^^^ Iterating a table in reverse using a numeric for loop going from ``#t`` to ``1`` requires a negative loop step. Luacheck warns about loops going from ``#(some expression)`` to ``1`` or a smaller constant when the loop step is not negative: .. code-block:: lua :linenos: -- Warning for this loop: -- numeric for loop goes from #(expr) down to 1 but loop step is not negative for i = #t, 1 do print(t[i]) end -- This loop is okay. for i = #t, 1, -1 do print(t[i]) end Formatting issues (6xx) ----------------------- Whitespace issues ^^^^^^^^^^^^^^^^^ Luacheck warns about trailing whitespace and inconsistent indentation (``SPACE`` followed by ``TAB``). Some examples of trailing whitespace Luacheck finds: .. code-block:: lua :linenos: -- Whitespace example. print("Hello") print("World") Here: * Any tabs or spaces after either ``)`` would be considered trailing. * Any tabs or spaces after the ``.`` in the comment would be considered trailing * Any tabs or spaces on the empty line between the two ``print`` statements would also be considered a form of trailing whitespace. Trailing whitespace in any of these forms is useless, can be a nuisance to developers navigating around a file, and is forbidden in many formatting styles. Line length limits ^^^^^^^^^^^^^^^^^^ Luacheck warns about lines that are longer then some limit. Default limit is ``120`` characters. It's possible to change this limit using ``--max-line-length`` CLI option or disable the check completely with ``--no-max-line-length``; there are similar config and inline options. Additionally, separate limits can be set for three different type of lines: * "String" lines have their line ending inside a string, typically a long string using ``[[...]]`` syntax. * "Comment" lines have their line ending inside a long comment (``--[[...]]``), or end with a short comment using normal ``--...`` syntax. * "Code" lines are all other lines. These types of lines are limited using CLI options named ``--[no-]max-string-line-length``, ``--[no-]max-comment-line-length``, and ``--[no-]max-code-line-length``, with similar config and inline options. luacheck-0.25.0/luacheck-dev-1.rockspec000066400000000000000000000107671410451437000176130ustar00rootroot00000000000000package = "luacheck" version = "dev-1" source = { url = "git+https://github.com/luarocks/luacheck.git" } description = { summary = "A static analyzer and a linter for Lua", detailed = [[ Luacheck is a command-line tool for linting and static analysis of Lua code. It is able to spot usage of undefined global variables, unused local variables and a few other typical problems within Lua programs. ]], homepage = "https://github.com/luarocks/luacheck", license = "MIT" } dependencies = { "lua >= 5.1", "argparse >= 0.6.0", "luafilesystem >= 1.6.3" } build = { type = "builtin", modules = { luacheck = "src/luacheck/init.lua", ["luacheck.builtin_standards"] = "src/luacheck/builtin_standards/init.lua", ["luacheck.builtin_standards.love"] = "src/luacheck/builtin_standards/love.lua", ["luacheck.builtin_standards.ngx"] = "src/luacheck/builtin_standards/ngx.lua", ["luacheck.cache"] = "src/luacheck/cache.lua", ["luacheck.check"] = "src/luacheck/check.lua", ["luacheck.check_state"] = "src/luacheck/check_state.lua", ["luacheck.config"] = "src/luacheck/config.lua", ["luacheck.core_utils"] = "src/luacheck/core_utils.lua", ["luacheck.decoder"] = "src/luacheck/decoder.lua", ["luacheck.expand_rockspec"] = "src/luacheck/expand_rockspec.lua", ["luacheck.filter"] = "src/luacheck/filter.lua", ["luacheck.format"] = "src/luacheck/format.lua", ["luacheck.fs"] = "src/luacheck/fs.lua", ["luacheck.globbing"] = "src/luacheck/globbing.lua", ["luacheck.lexer"] = "src/luacheck/lexer.lua", ["luacheck.main"] = "src/luacheck/main.lua", ["luacheck.multithreading"] = "src/luacheck/multithreading.lua", ["luacheck.options"] = "src/luacheck/options.lua", ["luacheck.parser"] = "src/luacheck/parser.lua", ["luacheck.profiler"] = "src/luacheck/profiler.lua", ["luacheck.runner"] = "src/luacheck/runner.lua", ["luacheck.serializer"] = "src/luacheck/serializer.lua", ["luacheck.stages"] = "src/luacheck/stages/init.lua", ["luacheck.stages.detect_bad_whitespace"] = "src/luacheck/stages/detect_bad_whitespace.lua", ["luacheck.stages.detect_cyclomatic_complexity"] = "src/luacheck/stages/detect_cyclomatic_complexity.lua", ["luacheck.stages.detect_empty_blocks"] = "src/luacheck/stages/detect_empty_blocks.lua", ["luacheck.stages.detect_empty_statements"] = "src/luacheck/stages/detect_empty_statements.lua", ["luacheck.stages.detect_globals"] = "src/luacheck/stages/detect_globals.lua", ["luacheck.stages.detect_reversed_fornum_loops"] = "src/luacheck/stages/detect_reversed_fornum_loops.lua", ["luacheck.stages.detect_unbalanced_assignments"] = "src/luacheck/stages/detect_unbalanced_assignments.lua", ["luacheck.stages.detect_uninit_accesses"] = "src/luacheck/stages/detect_uninit_accesses.lua", ["luacheck.stages.detect_unreachable_code"] = "src/luacheck/stages/detect_unreachable_code.lua", ["luacheck.stages.detect_unused_fields"] = "src/luacheck/stages/detect_unused_fields.lua", ["luacheck.stages.detect_unused_locals"] = "src/luacheck/stages/detect_unused_locals.lua", ["luacheck.stages.linearize"] = "src/luacheck/stages/linearize.lua", ["luacheck.stages.name_functions"] = "src/luacheck/stages/name_functions.lua", ["luacheck.stages.parse"] = "src/luacheck/stages/parse.lua", ["luacheck.stages.parse_inline_options"] = "src/luacheck/stages/parse_inline_options.lua", ["luacheck.stages.resolve_locals"] = "src/luacheck/stages/resolve_locals.lua", ["luacheck.stages.unwrap_parens"] = "src/luacheck/stages/unwrap_parens.lua", ["luacheck.standards"] = "src/luacheck/standards.lua", ["luacheck.unicode"] = "src/luacheck/unicode.lua", ["luacheck.unicode_printability_boundaries"] = "src/luacheck/unicode_printability_boundaries.lua", ["luacheck.utils"] = "src/luacheck/utils.lua", ["luacheck.vendor.sha1"] = "src/luacheck/vendor/sha1/init.lua", ["luacheck.vendor.sha1.bit_ops"] = "src/luacheck/vendor/sha1/bit_ops.lua", ["luacheck.vendor.sha1.bit32_ops"] = "src/luacheck/vendor/sha1/bit32_ops.lua", ["luacheck.vendor.sha1.common"] = "src/luacheck/vendor/sha1/common.lua", ["luacheck.vendor.sha1.lua53_ops"] = "src/luacheck/vendor/sha1/lua53_ops.lua", ["luacheck.vendor.sha1.pure_lua_ops"] = "src/luacheck/vendor/sha1/pure_lua_ops.lua", ["luacheck.version"] = "src/luacheck/version.lua" }, install = { bin = { luacheck = "bin/luacheck.lua" } } } luacheck-0.25.0/scripts/000077500000000000000000000000001410451437000150435ustar00rootroot00000000000000luacheck-0.25.0/scripts/build-binaries.sh000077500000000000000000000012521410451437000202730ustar00rootroot00000000000000#!/usr/bin/env bash set -eu set -o pipefail # Builds the following binaries: # * luacheck (Linux x86-64) # * luacheck32 (Linux x86) # * luacheck.exe (Windows x86-64) # * luacheck32.exe (Windows x86) # Should be executed from root Luacheck directory. # Resulting binaries will be in `build/bin/`. cd build make fetch function build { label="$1" shift echo echo "=== Building Luacheck ($label) ===" echo make clean "$@" make "-j$(nproc)" "$@" } build "Linux x86-64" LINUX=1 #build "Linux x86" LINUX=1 "BASE_CC=gcc -m32" SUFFIX=32 build "Windows x86-64" CROSS=x86_64-w64-mingw32- SUFFIX=.exe build "Windows x86" CROSS=i686-w64-mingw32- SUFFIX=32.exe luacheck-0.25.0/scripts/dedicated_coverage.sh000077500000000000000000000061161410451437000211670ustar00rootroot00000000000000#!/usr/bin/env bash set -eu set -o pipefail # Collects test coverage for luacheck modules with associated spec files. # Runs spec files from the arguments or all spec files. # Each module can be covered only from its own spec file. # Should be executed from root Luacheck directory. declare -A spec_to_module spec_to_module[spec/bad_whitespace_spec.lua]=src/luacheck/stages/detect_bad_whitespace.lua spec_to_module[spec/cache_spec.lua]=src/luacheck/cache.lua spec_to_module[spec/check_spec.lua]=src/luacheck/check.lua spec_to_module[spec/config_spec.lua]=src/luacheck/config.lua spec_to_module[spec/decoder_spec.lua]=src/luacheck/decoder.lua spec_to_module[spec/empty_blocks_spec.lua]="src/luacheck/stages/detect_empty_blocks.lua" spec_to_module[spec/expand_rockspec_spec.lua]=src/luacheck/expand_rockspec.lua spec_to_module[spec/filter_spec.lua]=src/luacheck/filter.lua spec_to_module[spec/format_spec.lua]=src/luacheck/format.lua spec_to_module[spec/fs_spec.lua]=src/luacheck/fs.lua spec_to_module[spec/globbing_spec.lua]=src/luacheck/globbing.lua spec_to_module[spec/luacheck_spec.lua]=src/luacheck/init.lua spec_to_module[spec/lexer_spec.lua]=src/luacheck/lexer.lua spec_to_module[spec/cli_spec.lua]=src/luacheck/main.lua spec_to_module[spec/options_spec.lua]=src/luacheck/options.lua spec_to_module[spec/parser_spec.lua]=src/luacheck/parser.lua spec_to_module[spec/serializer_spec.lua]=src/luacheck/serializer.lua spec_to_module[spec/cyclomatic_complexity_spec.lua]=src/luacheck/stages/detect_cyclomatic_complexity.lua spec_to_module[spec/globals_spec.lua]=src/luacheck/stages/detect_globals.lua spec_to_module[spec/reversed_fornum_loops_spec.lua]=src/luacheck/stages/detect_reversed_fornum_loops.lua spec_to_module[spec/unbalanced_assignments_spec.lua]=src/luacheck/stages/detect_unbalanced_assignments.lua spec_to_module[spec/uninit_accesses_spec.lua]=src/luacheck/stages/detect_uninit_accesses.lua spec_to_module[spec/unreachable_code_spec.lua]=src/luacheck/stages/detect_unreachable_code.lua spec_to_module[spec/unused_fields_spec.lua]=src/luacheck/stages/detect_unused_fields.lua spec_to_module[spec/unused_locals_spec.lua]=src/luacheck/stages/detect_unused_locals.lua spec_to_module[spec/linearize_spec.lua]=src/luacheck/stages/linearize.lua spec_to_module[spec/resolve_locals_spec.lua]=src/luacheck/stages/resolve_locals.lua spec_to_module[spec/standards_spec.lua]=src/luacheck/standards.lua spec_to_module[spec/utils_spec.lua]=src/luacheck/utils.lua if [ $# -eq 0 ]; then specs="$(sort <<< "${!spec_to_module[@]}")" else specs="$@" fi { echo Spec Module Hits Missed Coverage for spec in $specs; do if [ -v spec_to_module[$spec] ]; then module="${spec_to_module[$spec]}" rm -f luacov.stats.out rm -f luacov.report.out echo "busted -c $spec" >&2 busted -c "$spec" >&2 || true luacov echo -n "$spec " grep -P "$module +[^ ]+ +[^ ]+ +[^ ]+" luacov.report.out || echo "$module 0 0 0.00%" echo >&2 else echo "No associated module for spec $spec" >&2 fi done } | column -t luacheck-0.25.0/scripts/gen_unicode_printability_module.sh000077500000000000000000000007731410451437000240270ustar00rootroot00000000000000#!/usr/bin/env bash set -eu set -o pipefail # Generates luacheck.unicode_printability_boundaries module given Unicode version. # Should be executed from root Luacheck directory. url="https://www.unicode.org/Public/$1/ucd/UnicodeData.txt" cache="scripts/UnicodeData-$1.txt" if [ ! -e "$cache" ]; then wget -O "$cache" "$url" fi ( echo "-- Autogenerated using data from $url"; lua scripts/unicode_data_to_printability_module.lua < "$cache" ) > src/luacheck/unicode_printability_boundaries.lua luacheck-0.25.0/scripts/package.sh000077500000000000000000000014271410451437000170010ustar00rootroot00000000000000#!/usr/bin/env bash set -eu set -o pipefail # Creates rockspec and source rock for a new Luacheck release given version number. # Should be executed from root Luacheck directory. # Resulting rockspec and rock will be in `package/`. version="$1" rm -rf package mkdir package cd package echo echo "=== Creating rockspec for Luacheck $version ===" echo luarocks new-version ../luacheck-dev-1.rockspec --tag="$version" echo echo "=== Copying Luacheck files ===" echo mkdir luacheck cp -r ../src luacheck mkdir luacheck/bin cp ../bin/luacheck.lua luacheck/bin cp -r ../doc luacheck cp ../README.md ../CHANGELOG.md ../LICENSE luacheck echo echo "=== Packing source rock for Luacheck $version ===" echo zip -r luacheck-"$version"-1.src.rock luacheck luacheck-"$version"-1.rockspec cd .. luacheck-0.25.0/scripts/unicode_data_to_printability_module.lua000066400000000000000000000041151410451437000250270ustar00rootroot00000000000000-- Reads Unicode character data in UnicodeData.txt format from stdin. -- Prints a Lua module retuning an array of first codepoints of -- each continuous block of codepoints that are all printable or all not printable. -- See https://unicode.org/reports/tr44/ local category_printabilities = { Lu = true, Ll = true, Lt = true, Lm = true, Lo = true, Mn = true, Mc = true, Me = true, Nd = true, Nl = true, No = true, Pc = true, Pd = true, Ps = true, Pe = true, Pi = true, Pf = true, Po = true, Sm = true, Sc = true, Sk = true, So = true, Zs = true, Zl = false, Zp = false, Cc = false, Cf = false, Cs = false, Co = false, Cn = false } local codepoint_printabilities = {} local max_codepoint = 0 local range_start_codepoint for line in io.lines() do local codepoint_hex, name, category = assert(line:match("^([^;]+);([^;]+);([^;]+)")) local codepoint = assert(tonumber("0x" .. codepoint_hex)) local printability = category_printabilities[category] assert(printability ~= nil) if name:find(", First>$") then assert(not range_start_codepoint) range_start_codepoint = codepoint elseif name:find(", Last>$") then assert(range_start_codepoint and range_start_codepoint >= range_start_codepoint) for range_codepoint = range_start_codepoint, codepoint do codepoint_printabilities[range_codepoint] = printability end range_start_codepoint = nil else codepoint_printabilities[codepoint] = printability end max_codepoint = math.max(max_codepoint, codepoint) end assert(not range_start_codepoint) local parts = {"return {"} local prev_printability = true -- Iterate up to a non-existent codepoint to ensure that the last required codepoint is printed. for codepoint = 0, max_codepoint + 1 do local printability = codepoint_printabilities[codepoint] or false if printability ~= prev_printability then table.insert(parts, ("%d,"):format(codepoint)) end prev_printability = printability end table.insert(parts, "}") print(table.concat(parts)) luacheck-0.25.0/spec/000077500000000000000000000000001410451437000143065ustar00rootroot00000000000000luacheck-0.25.0/spec/bad_whitespace_spec.lua000066400000000000000000000056031410451437000207710ustar00rootroot00000000000000local helper = require "spec.helper" local function assert_warnings(warnings, src) assert.same(warnings, helper.get_stage_warnings("detect_bad_whitespace", src)) end describe("bad whitespace detection", function() it("detects lines with only whitespace", function() assert_warnings({ {code = "611", line = 1, column = 1, end_column = 4}, {code = "611", line = 3, column = 1, end_column = 1} }, " \n--[[\n \n]]\n") end) it("detects trailing whitespace with different warnings code depending on line ending type", function() assert_warnings({ {code = "612", line = 1, column = 8, end_column = 9}, {code = "613", line = 2, column = 13, end_column = 13}, {code = "612", line = 3, column = 8, end_column = 8}, {code = "614", line = 4, column = 11, end_column = 14} }, "local a \nlocal b = [[ \nthing]] \nlocal c --\t\t\t\t\nlocal d\n") end) it("detects spaces followed by tabs", function() assert_warnings({ {code = "621", line = 1, column = 1, end_column = 5} }, " \t \tlocal foo\n\t\t local bar\n") end) it("does not warn on spaces followed by tabs if the line has only whitespace", function() assert_warnings({ {code = "611", line = 1, column = 1, end_column = 7} }, " \t \t \n") end) it("can detect both trailing whitespace and inconsistent indentation on the same line", function() assert_warnings({ {code = "621", line = 1, column = 1, end_column = 2}, {code = "612", line = 1, column = 10, end_column = 10} }, " \tlocal a \n") end) it("handles lack of trailing newline", function() assert_warnings({ {code = "611", line = 2, column = 1, end_column = 5} }, "local a\n ") assert_warnings({ {code = "612", line = 2, column = 8, end_column = 12} }, "local a\nlocal b ") assert_warnings({ {code = "621", line = 1, column = 1, end_column = 2}, {code = "614", line = 1, column = 13, end_column = 16} }, " \tlocal a -- ") end) it("provides correct column ranges in presence of two-byte line endings", function() assert_warnings({ {code = "612", line = 1, column = 10, end_column = 13}, {code = "621", line = 2, column = 1, end_column = 4}, {code = "611", line = 3, column = 1, end_column = 3} }, "local foo \r\n \tlocal bar\n\r ") end) it("provides correct column ranges in presence of utf8", function() assert_warnings({ {code = "612", line = 1, column = 17, end_column = 20}, {code = "611", line = 2, column = 1, end_column = 4}, {code = "621", line = 3, column = 1, end_column = 4}, {code = "614", line = 3, column = 20, end_column = 24}, }, "local foo = '\204\128\204\130' \n \n \tlocal bar -- \240\144\128\128\224\166\152 \n") end) end) luacheck-0.25.0/spec/cache_spec.lua000066400000000000000000000056371410451437000171010ustar00rootroot00000000000000local cache = require "luacheck.cache" local fs = require "luacheck.fs" local lfs = require "lfs" local sha1 = require "luacheck.vendor.sha1" setup(function() end) describe("cache", function() describe("get_default_dir", function() it("returns a string", function() assert.is_string(cache.get_default_dir()) end) end) describe("new", function() it("returns nil, error message on failure to init cache", function() local c, err = cache.new("LICENSE") assert.is_nil(c) assert.is_string(err) end) it("returns Cache object on success", function() local c = cache.new("src") assert.is_table(c) end) end) describe("Cache", function() local filename = "spec/caches/file.lua" local normalized_filename = fs.normalize(fs.join(fs.get_current_dir(), filename)) local cache_dir = "spec/caches" local cache_filename = fs.join(cache_dir, sha1.sha1(normalized_filename)) local c before_each(function() c = cache.new(cache_dir) assert.is_table(c) end) after_each(function() os.remove(filename) if lfs.attributes(cache_filename, "mode") == "directory" then lfs.rmdir(cache_filename) else os.remove(cache_filename) end end) local function make_report(code) return { warnings = { code and {code = code} }, inline_options = {}, line_lengths = {} } end describe("put", function() it("returns nil on failure to store cache", function() lfs.mkdir(cache_filename) local ok = c:put(filename, make_report()) assert.is_nil(ok) end) it("returns true on successfull cache store", function() local ok = c:put(filename, make_report()) assert.is_true(ok) end) end) describe("get", function() it("returns nil on cache miss", function() local report, err = c:get(filename) assert.is_nil(report) assert.is_nil(err) end) it("returns nil on outdated cache", function() assert.is_true(c:put(filename, make_report())) io.open(filename, "w"):close() assert.is_true(lfs.touch(filename, os.time() + 100000)) local report, err = c:get(filename) assert.is_nil(report) assert.is_nil(err) end) it("returns report on success", function() local original_report = make_report("111") assert.is_true(c:put(filename, original_report)) io.open(filename, "w"):close() assert.is_true(lfs.touch(filename, os.time() - 100000)) local cached_report = c:get(filename) assert.same(original_report, cached_report) end) end) end) end) luacheck-0.25.0/spec/check_spec.lua000066400000000000000000000246351410451437000171120ustar00rootroot00000000000000local raw_check = require "luacheck.check" local function remove_cyclomatic_complexity_warnings(warnings) for i = #warnings, 1, -1 do if warnings[i].code == "561" then table.remove(warnings, i) end end end local function check_full(src) local report = raw_check(src) remove_cyclomatic_complexity_warnings(report.warnings) return report end local function check(src) return check_full(src).warnings end describe("check", function() it("does not find anything wrong in an empty block", function() assert.same({}, check("")) end) it("considers a variable assigned even if it can't get a value due to short rhs (it still gets nil)", function() assert.same({ {code = "311", name = "a", line = 1, column = 7, end_column = 7, overwritten_line = 2, overwritten_column = 1, overwritten_end_column = 1}, {code = "311", name = "b", line = 1, column = 10, end_column = 10, overwritten_line = 2, overwritten_column = 4, overwritten_end_column = 4}, {code = "532", line = 2, column = 1, end_column = 12} }, check[[ local a, b = "foo", "bar" a, b = "bar" return a, b ]]) end) it("reports vartype == var when the unused value is not the initial", function() assert.same({ {code = "312", name = "b", line = 1, column = 23, end_column = 23, overwritten_line = 4, overwritten_column = 4, overwritten_end_column = 4}, {code = "311", name = "a", line = 2, column = 4, end_column = 4, overwritten_line = 3, overwritten_column = 4, overwritten_end_column = 4} }, check[[ local function foo(a, b) a = a or "default" a = 42 b = 7 return a, b end return foo ]]) end) it("does not detect unused values in loops", function() assert.same({ {code = "113", name = "print", line = 3, column = 4, end_column = 8}, {code = "113", name = "math", indexing = {"floor"}, line = 4, column = 8, end_column = 11} }, check[[ local a = 10 while a > 0 do print(a) a = math.floor(a/2) end ]]) end) it("detects unused local value referred to from closure in incompatible branch", function() assert.same({ {code = "311", name = "a", line = 4, column = 4, end_column = 4}, {code = "321", name = "a", line = 6, column = 28, end_column = 28} }, check[[ local a if (...)() then a = 1 else (...)(function() return a end) end ]]) end) it("detects unused upvalue value referred to from closure in incompatible branch", function() assert.same({ {code = "311", name = "a", line = 4, column = 21, end_column = 21}, {code = "321", name = "a", line = 6, column = 28, end_column = 28} }, check[[ local a if (...)() then (...)(function() a = 1 end) else (...)(function() return a end) end ]]) end) it("handles upvalues before infinite loops", function() assert.same({ {code = "221", name = "x", line = 1, column = 7, end_column = 7}, {code = "211", name = "f", func = true, line = 2, column = 16, end_column = 16} }, check[[ local x local function f() return x end ::loop:: goto loop ]]) end) it("detects redefinition in the same scope", function() assert.same({ {code = "211", name = "foo", line = 1, column = 7, end_column = 9}, {code = "411", name = "foo", line = 2, column = 7, end_column = 9, prev_line = 1, prev_column = 7, prev_end_column = 9}, {code = "113", name = "print", line = 3, column = 1, end_column = 5} }, check[[ local foo local foo = "bar" print(foo) ]]) end) it("detects redefinition of function arguments", function() assert.same({ {code = "212", name = "foo", line = 1, column = 17, end_column = 19}, {code = "212", name = "...", line = 1, column = 22, end_column = 24}, {code = "412", name = "foo", line = 2, column = 10, end_column = 12, prev_line = 1, prev_column = 17, prev_end_column = 19} }, check[[ return function(foo, ...) local foo = 1 return foo end ]]) end) it("marks redefinition of implicit self", function() assert.same({ {code = "212", name = "self", line = 2, column = 11, end_column = 11, self = true}, {code = "212", name = "self", line = 4, column = 14, end_column = 14, self = true}, {code = "432", name = "self", line = 4, column = 14, end_column = 14, self = true, prev_line = 2, prev_column = 11, prev_end_column = 11} }, check[[ local t = {} function t:f() local o = {} function o:g() end return o end return t ]]) assert.same({ {code = "212", name = "self", line = 2, column = 14, end_column = 17}, {code = "212", name = "self", line = 4, column = 14, end_column = 14, self = true}, {code = "432", name = "self", line = 4, column = 14, end_column = 14, prev_line = 2, prev_column = 14, prev_end_column = 17} }, check[[ local t = {} function t.f(self) local o = {} function o:g() end return o end return t ]]) assert.same({ {code = "212", name = "self", line = 2, column = 11, end_column = 11, self = true}, {code = "212", name = "self", line = 4, column = 17, end_column = 20}, {code = "432", name = "self", line = 4, column = 17, end_column = 20, prev_line = 2, prev_column = 11, prev_end_column = 11} }, check[[ local t = {} function t:f() local o = {} function o.g(self) end return o end return t ]]) end) it("detects shadowing definitions", function() assert.same({ {code = "431", name = "a", line = 4, column = 10, end_column = 10, prev_line = 1, prev_column = 7, prev_end_column = 7}, {code = "421", name = "a", line = 7, column = 13, end_column = 13, prev_line = 4, prev_column = 10, prev_end_column = 10} }, check[[ local a = 46 return a, function(foo, ...) local a = 1 do local a = 6 foo(a, ...) end return a end ]]) end) it("detects unused labels", function() assert.same({ {code = "521", label = "fail", line = 2, column = 4, end_column = 11} }, check[[ ::fail:: do ::fail:: end goto fail ]]) end) it("detects empty statements", function() assert.same({ {code = "551", line = 1, column = 1, end_column = 1}, {code = "541", line = 2, column = 1, end_column = 6}, {code = "551", line = 2, column = 8, end_column = 8}, {code = "551", line = 4, column = 20, end_column = 20}, {code = "551", line = 7, column = 17, end_column = 17} }, check[[ ; do end;; local foo = "bar"; foo = foo .. "baz";; while true do if foo() then; goto fail; elseif foo() then break; end end ::fail:: return foo; ]]) end) it("provides correct locations in presence of utf8", function() assert.same({ {code = "211", name = "a", line = 2, column = 15, end_column = 15}, {code = "113", name = "math", line = 2, column = 17, end_column = 20, indexing = {"\204\130"}} }, check("-- \240\144\128\128\224\166\152\nlocal --[[\204\128]] a;math['\204\130']()\n")) end) it("provides inline options, line lengths, and line endings", function() assert.same({ warnings = { {code = "211", name = "foo", line = 2, column = 7, end_column = 9}, {code = "211", name = "bar", line = 2, column = 12, end_column = 14}, {code = "512", line = 7, column = 1, end_column = 32}, {code = "213", name = "_", line = 7, column = 5, end_column = 5}, {code = "113", name = "pairs", line = 7, column = 10, end_column = 14}, {code = "211", name = "f", func = true, line = 11, column = 16, end_column = 16} }, inline_options = { {options = {ignore = {"bar"}}, line = 1, column = 1, end_column = 28}, {options = {ignore = {"foo"}}, line = 2, column = 16, end_column = 38}, {pop_count = 1, line = 3}, {pop_count = 1, line = 4}, {options = {ignore = {".*"}}, line = 5, column = 1, end_column = 19}, {options = {ignore = {"f"}}, line = 11, column = 24, end_column = 44}, {pop_count = 1, options = {std = "max"}, line = 12, column = 1, end_column = 20}, {options = {std = "none"}, line = 13, column = 1, end_column = 21}, {pop_count = 2, line = 15}, {pop_count = 1, line = 16} }, line_lengths = {28, 38, 16, 17, 19, 17, 32, 16, 0, 17, 44, 20, 21, 16, 3, 0}, line_endings = {"comment", "comment", "comment", nil, "comment", "comment", nil, "comment", nil, "comment", "comment", "comment", "comment", "comment"} }, check_full[[ -- luacheck: push ignore bar local foo, bar -- luacheck: ignore foo -- luacheck: pop return function() -- luacheck: ignore -- luacheck: push for _ in pairs({}) do return end -- luacheck: pop -- luacheck: push local function f() end -- luacheck: ignore f -- luacheck: std max -- luacheck: std none -- luacheck: pop end ]]) end) it("emits correct inline option error messages", function() assert.same({ {code = "023", line = 1, column = 1, end_column = 16}, {code = "022", line = 2, column = 1, end_column = 17}, {code = "021", msg = "unknown inline option 'something strange'", line = 3, column = 1, end_column = 30}, {code = "021", msg = "inline option 'std' expects 1 argument, 0 given", line = 4, column = 1, end_column = 16}, {code = "021", msg = "inline option 'std' expects 1 argument, 3 given", line = 5, column = 1, end_column = 30}, {code = "021", msg = "inline option 'no unused' expects 0 arguments, 2 given", line = 6, column = 1, end_column = 43}, {code = "021", msg = "unknown inline option 'no ignore anything please'", line = 7, column = 1, end_column = 38}, {code = "021", msg = "empty inline option", line = 8, column = 1, end_column = 12}, {code = "021", msg = "empty inline option invocation", line = 9, column = 1, end_column = 38} }, check_full[[ -- luacheck: pop -- luacheck: push -- luacheck: something strange -- luacheck: std -- luacheck: std lua51 + lua52 -- luacheck: no unused, no unused very much -- luacheck: no ignore anything please -- luacheck: -- luacheck: no unused, , no redefined ]].warnings) end) it("handles argparse sample", function() assert.table(check(io.open("spec/samples/argparse-0.2.0.lua", "rb"):read("*a"))) end) end) luacheck-0.25.0/spec/cli_spec.lua000066400000000000000000001773731410451437000166140ustar00rootroot00000000000000local utils = require "luacheck.utils" local multithreading = require "luacheck.multithreading" local helper = require "spec.helper" local luacheck_cmd = helper.luacheck_command() local function quote(argument) -- Do not worry about special characters too much, just quote. local mark = utils.is_windows and '"' or "'" return mark .. argument .. mark end local function norm_output(output) -- Replace "/" with native slashes, except when it's used to separate -- warning and error numbers on the last line. return output:gsub("(%w)/(%w)", "%1" .. utils.dir_sep .. "%2") end local function get_output(command, wd, color) if color then if utils.is_windows and not os.getenv("ANSICON") then pending("uses terminal colors") end else command = "--no-color " .. command end command = ("%s %s 2>&1"):format(helper.luacheck_command(wd), command) local handler = io.popen(command) local output = handler:read("*a") handler:close() if color then return (output:gsub("\27%[%d+m", "\27"):gsub("\27+", "#")) else return output end end local function get_exitcode(command) local nosql_db = package.config:sub(1, 1) == "/" and "/dev/null" or "NUL" local code51, _, code52plus = os.execute(luacheck_cmd.." "..command.." > "..nosql_db.." 2>&1") if type(code51) == "number" then return code51 >= 256 and math.floor(code51 / 256) or code51 else return code52plus end end -- luacheck: max line length 180 describe("cli", function() it("exists", function() assert.equal(0, get_exitcode "--help") end) it("handles invalid options", function() assert.equal(4, get_exitcode "--invalid-option") end) it("works for correct files", function() assert.equal([[ Checking spec/samples/good_code.lua OK Total: 0 warnings / 0 errors in 1 file ]], get_output "spec/samples/good_code.lua --no-config") assert.equal(0, get_exitcode "spec/samples/good_code.lua --no-config") end) it("allows measuring performance", function() assert.equal(0, get_exitcode "spec/samples/good_code.lua --no-config --profile") end) it("removes ./ in the beginnings of file names", function() assert.equal([[ Checking spec/samples/good_code.lua OK Total: 0 warnings / 0 errors in 1 file ]], get_output "./spec/samples/good_code.lua --no-config") end) it("allows setting new filename", function() assert.equal([[ Checking new.lua OK Total: 0 warnings / 0 errors in 1 file ]], get_output "spec/samples/good_code.lua --no-config --filename new.lua") end) it("filters files using --exclude-files", function() assert.equal([[ Checking spec/samples/good_code.lua OK Total: 0 warnings / 0 errors in 1 file ]], get_output("spec/samples/good_code.lua spec/samples/bad_code.lua --no-config --exclude-files " .. quote("**/??d_code.lua"))) end) it("filters files using --include-files", function() assert.equal([[ Checking spec/samples/bad_code.lua 5 warnings Total: 5 warnings / 0 errors in 1 file ]], get_output("spec/samples/good_code.lua spec/samples/bad_code.lua --no-config -qq --include-files " .. quote("**/??d_code.lua"))) end) it("--exclude-files has priority over --include-files", function() assert.equal([[ Checking spec/samples/good_code.lua OK Total: 0 warnings / 0 errors in 1 file ]], get_output("spec/samples/good_code.lua spec/samples/bad_code.lua --no-config --include-files " .. quote("**/*.lua") .. " --exclude-files " .. quote("**/??d_code.lua"))) end) it("works for incorrect files", function() assert.equal([[ Checking spec/samples/bad_code.lua 5 warnings spec/samples/bad_code.lua:3:16: unused function 'helper' spec/samples/bad_code.lua:3:23: unused variable length argument spec/samples/bad_code.lua:7:10: setting non-standard global variable 'embrace' spec/samples/bad_code.lua:8:10: variable 'opt' was previously defined as an argument on line 7 spec/samples/bad_code.lua:9:11: accessing undefined variable 'hepler' Total: 5 warnings / 0 errors in 1 file ]], get_output "spec/samples/bad_code.lua --no-config") assert.equal(1, get_exitcode "spec/samples/bad_code.lua --no-config") end) it("detects whitespace issues", function() assert.equal([[ Checking spec/samples/bad_whitespace.lua 10 warnings spec/samples/bad_whitespace.lua:4:26: line contains trailing whitespace spec/samples/bad_whitespace.lua:8:25: trailing whitespace in a comment spec/samples/bad_whitespace.lua:14:20: trailing whitespace in a string spec/samples/bad_whitespace.lua:17:30: trailing whitespace in a comment spec/samples/bad_whitespace.lua:22:40: trailing whitespace in a comment spec/samples/bad_whitespace.lua:26:1: line contains only whitespace spec/samples/bad_whitespace.lua:27:1: line contains only whitespace spec/samples/bad_whitespace.lua:28:1: line contains only whitespace spec/samples/bad_whitespace.lua:29:1: line contains only whitespace spec/samples/bad_whitespace.lua:34:1: inconsistent indentation (SPACE followed by TAB) Total: 10 warnings / 0 errors in 1 file ]], get_output "spec/samples/bad_whitespace.lua --no-config") assert.equal(1, get_exitcode "spec/samples/bad_whitespace.lua --no-config") end) it("works for incorrect patterns in options", function() assert.equal([[ Critical error: Invalid pattern '^%1foo$' ]], get_output "spec/samples/bad_code.lua --ignore %1foo --no-config") end) it("checks stdin when given -", function() assert.equal([[ Checking stdin 5 warnings stdin:3:16: unused function 'helper' stdin:3:23: unused variable length argument stdin:7:10: setting non-standard global variable 'embrace' stdin:8:10: variable 'opt' was previously defined as an argument on line 7 stdin:9:11: accessing undefined variable 'hepler' Total: 5 warnings / 0 errors in 1 file ]], get_output "- --config=spec/configs/override_config.luacheckrc < spec/samples/bad_code.lua") end) it("colors output by default", function() assert.equal([[ Checking spec/samples/good_code.lua #OK# Checking spec/samples/bad_code.lua #5 warnings# spec/samples/bad_code.lua:3:16: unused function #helper# spec/samples/bad_code.lua:3:23: unused variable length argument spec/samples/bad_code.lua:7:10: setting non-standard global variable #embrace# spec/samples/bad_code.lua:8:10: variable #opt# was previously defined as an argument on line 7 spec/samples/bad_code.lua:9:11: accessing undefined variable #hepler# Total: #5# warnings / #0# errors in 2 files ]], get_output("spec/samples/good_code.lua spec/samples/bad_code.lua --no-config", nil, true)) end) it("suppresses OK output with -q", function() assert.equal([[ Checking spec/samples/bad_code.lua 5 warnings spec/samples/bad_code.lua:3:16: unused function 'helper' spec/samples/bad_code.lua:3:23: unused variable length argument spec/samples/bad_code.lua:7:10: setting non-standard global variable 'embrace' spec/samples/bad_code.lua:8:10: variable 'opt' was previously defined as an argument on line 7 spec/samples/bad_code.lua:9:11: accessing undefined variable 'hepler' Checking spec/samples/unused_code.lua 9 warnings spec/samples/unused_code.lua:3:18: unused argument 'baz' spec/samples/unused_code.lua:4:8: unused loop variable 'i' spec/samples/unused_code.lua:5:13: unused variable 'q' spec/samples/unused_code.lua:7:11: unused loop variable 'a' spec/samples/unused_code.lua:7:14: unused loop variable 'b' spec/samples/unused_code.lua:7:17: unused loop variable 'c' spec/samples/unused_code.lua:13:7: value assigned to variable 'x' is overwritten on line 14 before use spec/samples/unused_code.lua:14:1: value assigned to variable 'x' is overwritten on line 15 before use spec/samples/unused_code.lua:21:7: variable 'z' is never accessed Total: 14 warnings / 0 errors in 3 files ]], get_output "-q spec/samples/bad_code.lua spec/samples/good_code.lua spec/samples/unused_code.lua --no-config") assert.equal([[ Total: 0 warnings / 0 errors in 1 file ]], get_output "-q spec/samples/good_code.lua --no-config") end) it("suppresses warnings output with -qq", function() assert.equal([[ Checking spec/samples/bad_code.lua 5 warnings Checking spec/samples/unused_code.lua 9 warnings Total: 14 warnings / 0 errors in 3 files ]], get_output "-qq spec/samples/bad_code.lua spec/samples/good_code.lua spec/samples/unused_code.lua --no-config") end) it("suppresses file info output with -qqq", function() assert.equal([[Total: 14 warnings / 0 errors in 3 files ]], get_output "-qqq spec/samples/bad_code.lua spec/samples/good_code.lua spec/samples/unused_code.lua --no-config") end) it("allows to ignore some types of warnings", function() assert.equal([[ Checking spec/samples/bad_code.lua 3 warnings spec/samples/bad_code.lua:7:10: setting non-standard global variable 'embrace' spec/samples/bad_code.lua:8:10: variable 'opt' was previously defined as an argument on line 7 spec/samples/bad_code.lua:9:11: accessing undefined variable 'hepler' Total: 3 warnings / 0 errors in 1 file ]], get_output "-u spec/samples/bad_code.lua --no-config") assert.equal([[ Checking spec/samples/bad_code.lua 3 warnings spec/samples/bad_code.lua:3:16: unused function 'helper' spec/samples/bad_code.lua:3:23: unused variable length argument spec/samples/bad_code.lua:8:10: variable 'opt' was previously defined as an argument on line 7 Total: 3 warnings / 0 errors in 1 file ]], get_output "-g spec/samples/bad_code.lua --no-config") assert.equal([[ Checking spec/samples/bad_code.lua 4 warnings spec/samples/bad_code.lua:3:16: unused function 'helper' spec/samples/bad_code.lua:3:23: unused variable length argument spec/samples/bad_code.lua:7:10: setting non-standard global variable 'embrace' spec/samples/bad_code.lua:9:11: accessing undefined variable 'hepler' Total: 4 warnings / 0 errors in 1 file ]], get_output "-r spec/samples/bad_code.lua --no-config") end) it("allows to define additional globals", function() assert.equal([[ Checking spec/samples/bad_code.lua 4 warnings spec/samples/bad_code.lua:3:16: unused function 'helper' spec/samples/bad_code.lua:3:23: unused variable length argument spec/samples/bad_code.lua:8:10: variable 'opt' was previously defined as an argument on line 7 spec/samples/bad_code.lua:9:11: accessing undefined variable 'hepler' Total: 4 warnings / 0 errors in 1 file ]], get_output "spec/samples/bad_code.lua --globals embrace --no-config") end) it("allows to set standard globals", function() assert.equal([[ Checking spec/samples/bad_code.lua 6 warnings spec/samples/bad_code.lua:1:1: mutating non-standard global variable 'package' spec/samples/bad_code.lua:3:16: unused function 'helper' spec/samples/bad_code.lua:3:23: unused variable length argument spec/samples/bad_code.lua:7:10: setting non-standard global variable 'embrace' spec/samples/bad_code.lua:8:10: variable 'opt' was previously defined as an argument on line 7 spec/samples/bad_code.lua:9:11: accessing undefined variable 'hepler' Total: 6 warnings / 0 errors in 1 file ]], get_output "--std none spec/samples/bad_code.lua --no-config") assert.equal([[ Checking spec/samples/bad_code.lua 5 warnings spec/samples/bad_code.lua:3:16: unused function 'helper' spec/samples/bad_code.lua:3:23: unused variable length argument spec/samples/bad_code.lua:7:10: setting non-standard global variable 'embrace' spec/samples/bad_code.lua:8:10: variable 'opt' was previously defined as an argument on line 7 spec/samples/bad_code.lua:9:11: accessing undefined variable 'hepler' Total: 5 warnings / 0 errors in 1 file ]], get_output "--std lua51+lua52+lua53 spec/samples/bad_code.lua --no-config") end) it("allows to ignore some variables", function() assert.equal([[ Checking spec/samples/bad_code.lua 3 warnings spec/samples/bad_code.lua:3:16: unused function 'helper' spec/samples/bad_code.lua:3:23: unused variable length argument spec/samples/bad_code.lua:9:11: accessing undefined variable 'hepler' Total: 3 warnings / 0 errors in 1 file ]], get_output "spec/samples/bad_code.lua --ignore embrace opt --no-config") end) it("allows to only watch some variables", function() assert.equal([[ Checking spec/samples/bad_code.lua 1 warning spec/samples/bad_code.lua:3:16: unused function 'helper' Total: 1 warning / 0 errors in 1 file ]], get_output "spec/samples/bad_code.lua --only helper --no-config") end) it("recognizes different types of variables", function() assert.equal([[ Checking spec/samples/unused_code.lua 9 warnings spec/samples/unused_code.lua:3:18: unused argument 'baz' spec/samples/unused_code.lua:4:8: unused loop variable 'i' spec/samples/unused_code.lua:5:13: unused variable 'q' spec/samples/unused_code.lua:7:11: unused loop variable 'a' spec/samples/unused_code.lua:7:14: unused loop variable 'b' spec/samples/unused_code.lua:7:17: unused loop variable 'c' spec/samples/unused_code.lua:13:7: value assigned to variable 'x' is overwritten on line 14 before use spec/samples/unused_code.lua:14:1: value assigned to variable 'x' is overwritten on line 15 before use spec/samples/unused_code.lua:21:7: variable 'z' is never accessed Total: 9 warnings / 0 errors in 1 file ]], get_output "spec/samples/unused_code.lua --no-config") end) it("allows to ignore unused arguments", function() assert.equal([[ Checking spec/samples/unused_code.lua 4 warnings spec/samples/unused_code.lua:5:13: unused variable 'q' spec/samples/unused_code.lua:13:7: value assigned to variable 'x' is overwritten on line 14 before use spec/samples/unused_code.lua:14:1: value assigned to variable 'x' is overwritten on line 15 before use spec/samples/unused_code.lua:21:7: variable 'z' is never accessed Total: 4 warnings / 0 errors in 1 file ]], get_output "spec/samples/unused_code.lua --no-unused-args --no-config") end) it("allows to ignore unused secondary values and variables", function() assert.equal([[ Checking spec/samples/unused_secondaries.lua 4 warnings spec/samples/unused_secondaries.lua:3:7: unused variable 'a' spec/samples/unused_secondaries.lua:6:7: unused variable 'x' spec/samples/unused_secondaries.lua:6:13: unused variable 'z' spec/samples/unused_secondaries.lua:12:1: value assigned to variable 'o' is unused Total: 4 warnings / 0 errors in 1 file ]], get_output "spec/samples/unused_secondaries.lua --no-config") assert.equal([[ Checking spec/samples/unused_secondaries.lua 1 warning spec/samples/unused_secondaries.lua:6:7: unused variable 'x' Total: 1 warning / 0 errors in 1 file ]], get_output "spec/samples/unused_secondaries.lua -s --no-config") end) it("allows to ignore warnings related to implicit self", function() assert.equal([[ Checking spec/samples/redefined.lua 5 warnings spec/samples/redefined.lua:4:10: shadowing upvalue 'a' on line 1 spec/samples/redefined.lua:4:13: variable 'self' is never set spec/samples/redefined.lua:4:13: variable 'self' was previously defined as an argument on line 3 spec/samples/redefined.lua:7:13: shadowing definition of variable 'a' on line 4 spec/samples/redefined.lua:8:32: shadowing upvalue 'self' on line 4 Total: 5 warnings / 0 errors in 1 file ]], get_output "spec/samples/redefined.lua --no-self --globals each --no-config") end) it("handles errors gracefully", function() assert.equal([[ Checking spec/samples/python_code.lua 1 error spec/samples/python_code.lua:1:6: expected '=' near '__future__' Checking s/samples/absent_code1.lua I/O error s/samples/absent_code1.lua: couldn't read: No such file or directory Checking s/samples/absent_code2.lua I/O error s/samples/absent_code2.lua: couldn't read: No such file or directory Total: 0 warnings / 1 error in 1 file, couldn't check 2 files ]], get_output "spec/samples/python_code.lua s/samples/absent_code1.lua s/samples/absent_code2.lua --no-config") assert.equal(3, get_exitcode "spec/samples/python_code.lua spec/samples/absent_code.lua s/samples/absent_code2.lua --no-config") end) it("expands rockspecs", function() assert.equal([[ Checking spec/samples/bad_code.lua 5 warnings spec/samples/bad_code.lua:3:16: unused function 'helper' spec/samples/bad_code.lua:3:23: unused variable length argument spec/samples/bad_code.lua:7:10: setting non-standard global variable 'embrace' spec/samples/bad_code.lua:8:10: variable 'opt' was previously defined as an argument on line 7 spec/samples/bad_code.lua:9:11: accessing undefined variable 'hepler' Checking spec/samples/good_code.lua OK Total: 5 warnings / 0 errors in 2 files ]], get_output "spec/samples/sample.rockspec --no-config") end) it("handles bad rockspecs", function() assert.matches([[ Checking spec/samples/bad.rockspec Runtime error spec/samples/bad%.rockspec: line 1: attempt to call .+ Total: 0 warnings / 0 errors in 0 files, couldn't check 1 file ]], get_output "spec/samples/bad.rockspec --no-config") end) it("allows ignoring defined globals", function() assert.equal([[ Checking spec/samples/defined.lua 1 warning spec/samples/defined.lua:4:4: accessing undefined variable 'baz' Checking spec/samples/defined2.lua OK Total: 1 warning / 0 errors in 2 files ]], get_output "spec/samples/defined.lua spec/samples/defined2.lua -d --no-config") assert.equal([[ Checking spec/samples/defined2.lua OK Checking spec/samples/defined.lua 1 warning spec/samples/defined.lua:4:4: accessing undefined variable 'baz' Total: 1 warning / 0 errors in 2 files ]], get_output "spec/samples/defined2.lua spec/samples/defined.lua -d --no-config") end) it("allows restricting scope of defined globals to the file with their definition", function() assert.equal([[ Checking spec/samples/defined2.lua 1 warning spec/samples/defined2.lua:1:1: accessing undefined variable 'foo' Checking spec/samples/defined3.lua OK Total: 1 warning / 0 errors in 2 files ]], get_output "spec/samples/defined2.lua spec/samples/defined3.lua -d -m --no-config") end) it("allows ignoring globals defined in top level scope", function() assert.equal([[ Checking spec/samples/defined4.lua 2 warnings spec/samples/defined4.lua:1:10: unused global variable 'foo' spec/samples/defined4.lua:3:4: setting non-standard global variable 'bar' Total: 2 warnings / 0 errors in 1 file ]], get_output "spec/samples/defined4.lua -t --no-config") end) it("detects unused defined globals", function() assert.equal([[ Checking spec/samples/defined3.lua 3 warnings spec/samples/defined3.lua:1:1: unused global variable 'foo' spec/samples/defined3.lua:2:1: unused global variable 'foo' spec/samples/defined3.lua:3:1: unused global variable 'bar' Total: 3 warnings / 0 errors in 1 file ]], get_output "spec/samples/defined3.lua -d --no-config") assert.equal([[ Checking spec/samples/defined3.lua 1 warning spec/samples/defined3.lua:3:1: unused global variable 'bar' Checking spec/samples/defined2.lua OK Total: 1 warning / 0 errors in 2 files ]], get_output "spec/samples/defined3.lua spec/samples/defined2.lua -d --no-config") end) it("treats `unused global` warnings as `global` type warnings", function() assert.equal([[ Checking spec/samples/defined3.lua OK Total: 0 warnings / 0 errors in 1 file ]], get_output "spec/samples/defined3.lua -gd --no-config") assert.equal([[ Checking spec/samples/defined3.lua 1 warning spec/samples/defined3.lua:3:1: unused global variable 'bar' Checking spec/samples/defined2.lua OK Total: 1 warning / 0 errors in 2 files ]], get_output "spec/samples/defined3.lua spec/samples/defined2.lua -ud --no-config") end) it("allows ignoring unused defined globals", function() assert.equal([[ Checking spec/samples/defined3.lua OK Total: 0 warnings / 0 errors in 1 file ]], get_output "spec/samples/defined3.lua -d --ignore 13 --no-config") assert.equal([[ Checking spec/samples/defined3.lua OK Checking spec/samples/defined2.lua OK Total: 0 warnings / 0 errors in 2 files ]], get_output "spec/samples/defined3.lua spec/samples/defined2.lua -d --ignore 13 --no-config") end) it("detects flow issues", function() assert.equal([[ Checking spec/samples/bad_flow.lua 6 warnings spec/samples/bad_flow.lua:1:28: empty if branch spec/samples/bad_flow.lua:6:4: empty do..end block spec/samples/bad_flow.lua:12:10: right side of assignment has less values than left side expects spec/samples/bad_flow.lua:16:10: right side of assignment has more values than left side expects spec/samples/bad_flow.lua:21:7: unreachable code spec/samples/bad_flow.lua:25:1: loop is executed at most once Total: 6 warnings / 0 errors in 1 file ]], get_output "spec/samples/bad_flow.lua --no-config") end) it("detects redefinitions", function() assert.equal([[ Checking spec/samples/redefined.lua 6 warnings spec/samples/redefined.lua:3:11: unused argument 'self' spec/samples/redefined.lua:4:10: shadowing upvalue 'a' on line 1 spec/samples/redefined.lua:4:13: variable 'self' is never set spec/samples/redefined.lua:4:13: variable 'self' was previously defined as an argument on line 3 spec/samples/redefined.lua:7:13: shadowing definition of variable 'a' on line 4 spec/samples/redefined.lua:8:32: shadowing upvalue 'self' on line 4 Total: 6 warnings / 0 errors in 1 file ]], get_output "spec/samples/redefined.lua --globals each --no-config") end) it("detects lines that are too long", function() assert.equal([[ Checking spec/samples/line_length.lua 8 warnings spec/samples/line_length.lua:2:121: line is too long (123 > 120) spec/samples/line_length.lua:3:121: line is too long (164 > 120) spec/samples/line_length.lua:8:121: line is too long (134 > 120) spec/samples/line_length.lua:13:41: line is too long (47 > 40) spec/samples/line_length.lua:18:121: line is too long (132 > 120) spec/samples/line_length.lua:22:81: line is too long (85 > 80) spec/samples/line_length.lua:26:101: line is too long (104 > 100) spec/samples/line_length.lua:29:121: line is too long (125 > 120) Total: 8 warnings / 0 errors in 1 file ]], get_output "spec/samples/line_length.lua --no-config") assert.equal([[ Checking spec/samples/line_length.lua 7 warnings spec/samples/line_length.lua:3:131: line is too long (164 > 130) spec/samples/line_length.lua:8:131: line is too long (134 > 130) spec/samples/line_length.lua:13:41: line is too long (47 > 40) spec/samples/line_length.lua:18:131: line is too long (132 > 130) spec/samples/line_length.lua:22:81: line is too long (85 > 80) spec/samples/line_length.lua:26:101: line is too long (104 > 100) spec/samples/line_length.lua:29:121: line is too long (125 > 120) Total: 7 warnings / 0 errors in 1 file ]], get_output "spec/samples/line_length.lua --no-config --max-line-length=130") assert.equal([[ Checking spec/samples/line_length.lua 4 warnings spec/samples/line_length.lua:13:41: line is too long (47 > 40) spec/samples/line_length.lua:22:81: line is too long (85 > 80) spec/samples/line_length.lua:26:101: line is too long (104 > 100) spec/samples/line_length.lua:29:121: line is too long (125 > 120) Total: 4 warnings / 0 errors in 1 file ]], get_output "spec/samples/line_length.lua --no-config --no-max-line-length") assert.equal([[ Checking spec/samples/line_length.lua 7 warnings spec/samples/line_length.lua:2:121: line is too long (123 > 120) spec/samples/line_length.lua:3:121: line is too long (164 > 120) spec/samples/line_length.lua:13:41: line is too long (47 > 40) spec/samples/line_length.lua:18:121: line is too long (132 > 120) spec/samples/line_length.lua:22:81: line is too long (85 > 80) spec/samples/line_length.lua:26:101: line is too long (104 > 100) spec/samples/line_length.lua:29:121: line is too long (125 > 120) Total: 7 warnings / 0 errors in 1 file ]], get_output "spec/samples/line_length.lua --no-config --no-max-string-line-length") end) it("detects issues related to read-only globals", function() assert.equal([[ Checking spec/samples/read_globals.lua 5 warnings spec/samples/read_globals.lua:1:1: setting read-only global variable 'string' spec/samples/read_globals.lua:2:1: setting undefined field 'append' of global 'table' spec/samples/read_globals.lua:5:1: setting read-only global variable 'bar' spec/samples/read_globals.lua:6:1: mutating non-standard global variable 'baz' spec/samples/read_globals.lua:6:21: accessing undefined variable 'baz' Total: 5 warnings / 0 errors in 1 file ]], get_output "spec/samples/read_globals.lua --std=lua52 --globals foo --read-globals bar --no-config") end) it("detects indirect global indexing", function() assert.equal([[ Checking spec/samples/indirect_globals.lua 3 warnings spec/samples/indirect_globals.lua:2:11-16: accessing undefined variable 'global' spec/samples/indirect_globals.lua:5:1-8: indirectly setting undefined field 'concat.foo.bar' of global 'table' spec/samples/indirect_globals.lua:5:32-37: accessing undefined variable 'global' Total: 3 warnings / 0 errors in 1 file ]], get_output "spec/samples/indirect_globals.lua --std=min --ranges --no-config") end) it("allows defining fields", function() assert.equal([[ Checking spec/samples/indirect_globals.lua 2 warnings spec/samples/indirect_globals.lua:2:11-16: accessing undefined variable 'global' spec/samples/indirect_globals.lua:5:32-37: accessing undefined variable 'global' Total: 2 warnings / 0 errors in 1 file ]], get_output "spec/samples/indirect_globals.lua --std=min --globals table.concat.foo --ranges --no-config") end) it("detects issues related to global fields", function() assert.equal([[ Checking spec/samples/global_fields.lua 13 warnings / 1 error spec/samples/global_fields.lua:2:16: indirectly accessing undefined field 'upsert' of global 'table' spec/samples/global_fields.lua:8:1: indirectly setting undefined field 'insert.foo' of global 'table' spec/samples/global_fields.lua:23:7: accessing undefined field 'gfind' of global 'string' spec/samples/global_fields.lua:27:7: accessing undefined field 'find' of global 'string' spec/samples/global_fields.lua:32:7: accessing undefined variable 'server' spec/samples/global_fields.lua:33:7: accessing undefined variable 'server' spec/samples/global_fields.lua:34:1: mutating non-standard global variable 'server' spec/samples/global_fields.lua:35:1: mutating non-standard global variable 'server' spec/samples/global_fields.lua:36:1: mutating non-standard global variable 'server' spec/samples/global_fields.lua:37:7: accessing undefined variable 'server' spec/samples/global_fields.lua:38:1: mutating non-standard global variable 'server' spec/samples/global_fields.lua:40:1: invalid value of option 'std': unknown std 'my_server' spec/samples/global_fields.lua:41:1: mutating non-standard global variable 'server' spec/samples/global_fields.lua:42:1: mutating non-standard global variable 'server' Total: 13 warnings / 1 error in 1 file ]], get_output "spec/samples/global_fields.lua --no-config") assert.equal([[ Checking spec/samples/global_fields.lua 7 warnings spec/samples/global_fields.lua:2:16: indirectly accessing undefined field 'upsert' of global 'table' spec/samples/global_fields.lua:8:1: indirectly setting undefined field 'insert.foo' of global 'table' spec/samples/global_fields.lua:23:7: accessing undefined field 'gfind' of global 'string' spec/samples/global_fields.lua:27:7: accessing undefined field 'find' of global 'string' spec/samples/global_fields.lua:34:1: setting undefined field 'foo' of global 'server' spec/samples/global_fields.lua:35:1: setting undefined field 'bar.?' of global 'server' spec/samples/global_fields.lua:37:7: accessing undefined field 'baz.abcd' of global 'server' Total: 7 warnings / 0 errors in 1 file ]], get_output "spec/samples/global_fields.lua --config=spec/configs/custom_fields_config.luacheckrc") end) it("detects fornums going from #(expr) down to 1 with positive step", function() assert.equal([[ Checking spec/samples/reversed_fornum.lua 1 warning spec/samples/reversed_fornum.lua:1:1: numeric for loop goes from #(expr) down to -1.5 but loop step is not negative Total: 1 warning / 0 errors in 1 file ]], get_output "spec/samples/reversed_fornum.lua --no-config") end) it("allows showing warning codes", function() assert.equal([[ Checking spec/samples/read_globals.lua 5 warnings spec/samples/read_globals.lua:1:1: (W121) setting read-only global variable 'string' spec/samples/read_globals.lua:2:1: (W142) setting undefined field 'append' of global 'table' spec/samples/read_globals.lua:5:1: (W121) setting read-only global variable 'bar' spec/samples/read_globals.lua:6:1: (W112) mutating non-standard global variable 'baz' spec/samples/read_globals.lua:6:21: (W113) accessing undefined variable 'baz' Total: 5 warnings / 0 errors in 1 file ]], get_output "spec/samples/read_globals.lua --std=lua52 --globals foo --read-globals bar --codes --no-config") end) it("allows showing token ranges", function() assert.equal([[ Checking spec/samples/inline_options.lua 8 warnings / 2 errors spec/samples/inline_options.lua:6:16-16: unused function 'f' spec/samples/inline_options.lua:12:4-5: accessing undefined variable 'qu' spec/samples/inline_options.lua:15:1-3: accessing undefined variable 'baz' spec/samples/inline_options.lua:22:10-10: unused variable 'g' spec/samples/inline_options.lua:24:7-7: unused variable 'f' spec/samples/inline_options.lua:24:10-10: unused variable 'g' spec/samples/inline_options.lua:26:1-17: unpaired push directive spec/samples/inline_options.lua:28:4-19: unpaired pop directive spec/samples/inline_options.lua:34:1-6: empty do..end block spec/samples/inline_options.lua:35:10-13: empty if branch Checking spec/samples/python_code.lua 1 error spec/samples/python_code.lua:1:6-15: expected '=' near '__future__' Total: 8 warnings / 3 errors in 2 files ]], get_output "spec/samples/inline_options.lua spec/samples/python_code.lua --ranges --no-config") end) it("shows correct ranges for files with utf8", function() assert.equal([[ Checking spec/samples/utf8.lua 4 warnings spec/samples/utf8.lua:2:1-4: setting undefined field '분야 명' of global 'math' spec/samples/utf8.lua:2:16-19: accessing undefined field '値' of global 'math' spec/samples/utf8.lua:3:25-25: unused variable 't' spec/samples/utf8.lua:4:5-28: value assigned to field 'päällekkäinen nimi a\u{200B}b' is overwritten on line 5 before use Checking spec/samples/utf8_error.lua 1 error spec/samples/utf8_error.lua:2:11-11: expected statement near 'о' Total: 4 warnings / 1 error in 2 files ]], get_output "spec/samples/utf8.lua spec/samples/utf8_error.lua --ranges --no-config") end) it("applies inline options", function() assert.equal([[ Checking spec/samples/inline_options.lua 8 warnings / 2 errors spec/samples/inline_options.lua:6:16: unused function 'f' spec/samples/inline_options.lua:12:4: accessing undefined variable 'qu' spec/samples/inline_options.lua:15:1: accessing undefined variable 'baz' spec/samples/inline_options.lua:22:10: unused variable 'g' spec/samples/inline_options.lua:24:7: unused variable 'f' spec/samples/inline_options.lua:24:10: unused variable 'g' spec/samples/inline_options.lua:26:1: unpaired push directive spec/samples/inline_options.lua:28:4: unpaired pop directive spec/samples/inline_options.lua:34:1: empty do..end block spec/samples/inline_options.lua:35:10: empty if branch Total: 8 warnings / 2 errors in 1 file ]], get_output "spec/samples/inline_options.lua --std=none --no-config") -- Inline `enable` option overrides CLI `ignore`. assert.equal([[ Checking spec/samples/inline_options.lua 8 warnings / 2 errors spec/samples/inline_options.lua:6:16: unused function 'f' spec/samples/inline_options.lua:12:4: accessing undefined variable 'qu' spec/samples/inline_options.lua:15:1: accessing undefined variable 'baz' spec/samples/inline_options.lua:22:10: unused variable 'g' spec/samples/inline_options.lua:24:7: unused variable 'f' spec/samples/inline_options.lua:24:10: unused variable 'g' spec/samples/inline_options.lua:26:1: unpaired push directive spec/samples/inline_options.lua:28:4: unpaired pop directive spec/samples/inline_options.lua:34:1: empty do..end block spec/samples/inline_options.lua:35:10: empty if branch Total: 8 warnings / 2 errors in 1 file ]], get_output "spec/samples/inline_options.lua --std=none --ignore=542 --no-config") assert.equal([[ Checking spec/samples/global_inline_options.lua 3 warnings spec/samples/global_inline_options.lua:6:10: unused global variable 'f' spec/samples/global_inline_options.lua:7:4: setting non-standard global variable 'baz' spec/samples/global_inline_options.lua:18:4: setting non-module global variable 'external' Total: 3 warnings / 0 errors in 1 file ]], get_output "spec/samples/global_inline_options.lua --std=lua52 --no-config") assert.equal([[ Checking spec/samples/read_globals_inline_options.lua 5 warnings spec/samples/read_globals_inline_options.lua:2:10: accessing undefined variable 'baz' spec/samples/read_globals_inline_options.lua:3:1: setting read-only global variable 'foo' spec/samples/read_globals_inline_options.lua:3:11: setting non-standard global variable 'baz' spec/samples/read_globals_inline_options.lua:3:16: mutating non-standard global variable 'baz' spec/samples/read_globals_inline_options.lua:5:1: setting read-only global variable 'foo' Total: 5 warnings / 0 errors in 1 file ]], get_output "spec/samples/read_globals_inline_options.lua --std=lua52 --no-config") assert.equal([[ Checking spec/samples/read_globals_inline_options.lua 3 warnings spec/samples/read_globals_inline_options.lua:3:1: setting read-only global variable 'foo' spec/samples/read_globals_inline_options.lua:3:16: setting read-only field '?' of global 'baz' spec/samples/read_globals_inline_options.lua:5:1: setting read-only global variable 'foo' Total: 3 warnings / 0 errors in 1 file ]], get_output "spec/samples/read_globals_inline_options.lua --std=lua52 --read-globals baz --globals foo --no-config") end) it("inline options can use extended stds", function() assert.equal([[ Checking spec/samples/custom_std_inline_options.lua 3 warnings spec/samples/custom_std_inline_options.lua:3:1: accessing undefined variable 'tostring' spec/samples/custom_std_inline_options.lua:6:19: accessing undefined variable 'print' spec/samples/custom_std_inline_options.lua:6:25: accessing undefined variable 'it' Total: 3 warnings / 0 errors in 1 file ]], get_output "spec/samples/custom_std_inline_options.lua --config=spec/configs/custom_stds_config.luacheckrc") end) if not multithreading.has_lanes then pending("uses multithreading") else it("uses multithreading", function() assert.equal([[ Checking spec/samples/good_code.lua OK Checking spec/samples/bad_code.lua 5 warnings spec/samples/bad_code.lua:3:16: unused function 'helper' spec/samples/bad_code.lua:3:23: unused variable length argument spec/samples/bad_code.lua:7:10: setting non-standard global variable 'embrace' spec/samples/bad_code.lua:8:10: variable 'opt' was previously defined as an argument on line 7 spec/samples/bad_code.lua:9:11: accessing undefined variable 'hepler' Checking spec/samples/python_code.lua 1 error spec/samples/python_code.lua:1:6: expected '=' near '__future__' Total: 5 warnings / 1 error in 3 files ]], get_output "spec/samples/good_code.lua spec/samples/bad_code.lua spec/samples/python_code.lua --std=lua52 -j2 --no-config") end) end it("allows using custom formatter", function() assert.equal([[Files: 2 Formatter: spec.formatters.custom_formatter Quiet: 1 Color: false Codes: true ]], get_output "spec/samples/good_code.lua spec/samples/bad_code.lua --formatter spec.formatters.custom_formatter -q --codes --no-color --no-config") end) it("loads custom formatters relatively to project root", function() assert.equal([[Files: 2 Formatter: spec.formatters.custom_formatter Quiet: 1 Color: false Codes: true ]], get_output("samples/good_code.lua samples/bad_code.lua --formatter spec.formatters.custom_formatter -q --codes --no-color --no-config", "spec/")) end) it("allows using format options in config", function() assert.equal([[Checking spec/samples/bad_code.lua 5 warnings spec/samples/bad_code.lua:3:16-21: (W211) unused function 'helper' spec/samples/bad_code.lua:3:23-25: (W212) unused variable length argument spec/samples/bad_code.lua:7:10-16: (W111) setting non-standard global variable 'embrace' spec/samples/bad_code.lua:8:10-12: (W412) variable 'opt' was previously defined as an argument on line 7 spec/samples/bad_code.lua:9:11-16: (W113) accessing undefined variable 'hepler' Total: 5 warnings / 0 errors in 2 files ]], get_output "spec/samples/good_code.lua spec/samples/bad_code.lua --config=spec/configs/format_opts_config.luacheckrc") assert.equal([[Checking spec/samples/bad_code.lua 5 warnings Total: 5 warnings / 0 errors in 2 files ]], get_output "spec/samples/good_code.lua spec/samples/bad_code.lua -qq --config=spec/configs/format_opts_config.luacheckrc") end) it("has built-in TAP formatter", function() assert.equal([[ 1..8 not ok 1 bad_file: I/O error ok 2 spec/samples/good_code.lua not ok 3 spec/samples/bad_code.lua:3:16: unused function 'helper' not ok 4 spec/samples/bad_code.lua:3:23: unused variable length argument not ok 5 spec/samples/bad_code.lua:7:10: setting non-standard global variable 'embrace' not ok 6 spec/samples/bad_code.lua:8:10: variable 'opt' was previously defined as an argument on line 7 not ok 7 spec/samples/bad_code.lua:9:11: accessing undefined variable 'hepler' not ok 8 spec/samples/python_code.lua:1:6: expected '=' near '__future__' ]], get_output "bad_file spec/samples/good_code.lua spec/samples/bad_code.lua spec/samples/python_code.lua --std=lua52 --formatter TAP --no-config") assert.equal([[ 1..8 not ok 1 bad_file: I/O error ok 2 spec/samples/good_code.lua not ok 3 spec/samples/bad_code.lua:3:16: (W211) unused function 'helper' not ok 4 spec/samples/bad_code.lua:3:23: (W212) unused variable length argument not ok 5 spec/samples/bad_code.lua:7:10: (W111) setting non-standard global variable 'embrace' not ok 6 spec/samples/bad_code.lua:8:10: (W412) variable 'opt' was previously defined as an argument on line 7 not ok 7 spec/samples/bad_code.lua:9:11: (W113) accessing undefined variable 'hepler' not ok 8 spec/samples/python_code.lua:1:6: (E011) expected '=' near '__future__' ]], get_output "bad_file spec/samples/good_code.lua spec/samples/bad_code.lua spec/samples/python_code.lua --std=lua52 --formatter TAP --codes --no-config") end) it("has built-in JUnit formatter", function() assert.equal([[ ]], get_output "bad_file spec/samples/good_code.lua spec/samples/bad_code.lua spec/samples/python_code.lua --std=lua52 --formatter JUnit --no-config") end) it("has built-in Visual Studio aware formatter", function() assert.equal([[ luacheck : fatal error F1: couldn't check bad_file: couldn't read: No such file or directory spec/samples/bad_code.lua(3,16) : warning W211: unused function 'helper' spec/samples/bad_code.lua(3,23) : warning W212: unused variable length argument spec/samples/bad_code.lua(7,10) : warning W111: setting non-standard global variable 'embrace' spec/samples/bad_code.lua(8,10) : warning W412: variable 'opt' was previously defined as an argument on line 7 spec/samples/bad_code.lua(9,11) : warning W113: accessing undefined variable 'hepler' spec/samples/python_code.lua(1,6) : error E011: expected '=' near '__future__' ]], get_output "bad_file spec/samples/good_code.lua spec/samples/bad_code.lua spec/samples/python_code.lua --std=lua52 --formatter visual_studio --no-config") end) it("has built-in simple warning-per-line formatter", function() assert.equal("", get_output "spec/samples/good_code.lua --std=lua52 --formatter plain --no-config") assert.equal([[ spec/samples/bad_code.lua:3:16: unused function 'helper' spec/samples/bad_code.lua:3:23: unused variable length argument spec/samples/bad_code.lua:7:10: setting non-standard global variable 'embrace' spec/samples/bad_code.lua:8:10: variable 'opt' was previously defined as an argument on line 7 spec/samples/bad_code.lua:9:11: accessing undefined variable 'hepler' spec/samples/python_code.lua:1:6: expected '=' near '__future__' ]], get_output "spec/samples/good_code.lua spec/samples/bad_code.lua spec/samples/python_code.lua --std=lua52 --formatter plain --no-config") assert.equal([[ spec/samples/404.lua: I/O error (couldn't read: No such file or directory) ]], get_output "spec/samples/404.lua --formatter plain --no-config") assert.equal([[ spec/samples/bad_code.lua:3:16: (W211) unused function 'helper' spec/samples/bad_code.lua:3:23: (W212) unused variable length argument spec/samples/bad_code.lua:7:10: (W111) setting non-standard global variable 'embrace' spec/samples/bad_code.lua:8:10: (W412) variable 'opt' was previously defined as an argument on line 7 spec/samples/bad_code.lua:9:11: (W113) accessing undefined variable 'hepler' spec/samples/python_code.lua:1:6: (E011) expected '=' near '__future__' ]], get_output "spec/samples/good_code.lua spec/samples/bad_code.lua spec/samples/python_code.lua --std=lua52 --formatter plain --codes --no-config") end) it("provides version info", function() local output = get_output "--version" assert.truthy(output:match("^Luacheck: [%w%p ]+\nLua: [%w%p ]+\nArgparse: [%w%p ]+\nLuaFileSystem: [%w%p ]+\nLuaLanes: [%w%p ]+\n$")) end) it("expands folders", function() assert.matches("^Total: %d+ warnings / %d+ errors in 26 files\n$", get_output "spec/samples -qqq --no-config --exclude-files spec/samples/global_fields.lua") end) it("uses --include-files when expanding folders", function() assert.matches("^Total: %d+ warnings / %d+ errors in 2 files\n$", get_output("spec/samples -qqq --no-config --include-files " .. quote("**/*.rockspec"))) end) describe("config", function() describe("loading", function() it("uses .luacheckrc in current directory if possible", function() assert.equal(norm_output [[ Checking nested/ab.lua 1 warning nested/ab.lua:1:10: accessing undefined variable 'b' Checking nested/nested/abc.lua 2 warnings nested/nested/abc.lua:1:7: accessing undefined variable 'a' nested/nested/abc.lua:1:13: accessing undefined variable 'c' Total: 3 warnings / 0 errors in 2 files ]], get_output("nested", "spec/configs/project/")) end) it("does not use .luacheckrc in current directory with --no-config", function() assert.equal(norm_output [[ Checking nested/ab.lua 2 warnings nested/ab.lua:1:7: accessing undefined variable 'a' nested/ab.lua:1:10: accessing undefined variable 'b' Checking nested/nested/abc.lua 3 warnings nested/nested/abc.lua:1:7: accessing undefined variable 'a' nested/nested/abc.lua:1:10: accessing undefined variable 'b' nested/nested/abc.lua:1:13: accessing undefined variable 'c' Total: 5 warnings / 0 errors in 2 files ]], get_output("nested --no-config", "spec/configs/project/")) end) it("uses .luacheckrc in upper directory", function() assert.equal(norm_output [[ Checking ab.lua 1 warning ab.lua:1:10: accessing undefined variable 'b' Checking nested/abc.lua 2 warnings nested/abc.lua:1:7: accessing undefined variable 'a' nested/abc.lua:1:13: accessing undefined variable 'c' Total: 3 warnings / 0 errors in 2 files ]], get_output("ab.lua nested", "spec/configs/project/nested/")) end) it("uses config provided with --config=path", function() assert.equal([[ Checking spec/samples/compat.lua OK Total: 0 warnings / 0 errors in 1 file ]], get_output "spec/samples/compat.lua --config=spec/configs/global_config.luacheckrc") end) it("uses config when checking stdin", function() assert.equal([[ Checking stdin OK Total: 0 warnings / 0 errors in 1 file ]], get_output "- --config=spec/configs/global_config.luacheckrc < spec/samples/compat.lua") end) it("uses per-file overrides", function() assert.equal([[ Checking spec/samples/bad_code.lua 4 warnings spec/samples/bad_code.lua:3:16: unused function 'helper' spec/samples/bad_code.lua:3:23: unused variable length argument spec/samples/bad_code.lua:7:10: setting non-standard global variable 'embrace' spec/samples/bad_code.lua:9:11: accessing undefined variable 'hepler' Checking spec/samples/unused_code.lua OK Total: 4 warnings / 0 errors in 2 files ]], get_output "spec/samples/bad_code.lua spec/samples/unused_code.lua --config=spec/configs/override_config.luacheckrc") end) it("adds per-file overrides with default stds", function() assert.equal(([[ Checking .luacheckrc 3 warnings .luacheckrc:14:6: accessing undefined variable 'it' .luacheckrc:14:10: accessing undefined variable 'version' .luacheckrc:14:25: accessing undefined variable 'newproxy' Checking default_stds-scm-1.rockspec 3 warnings default_stds-scm-1.rockspec:13:1: accessing undefined variable 'it' default_stds-scm-1.rockspec:13:21: accessing undefined variable 'newproxy' default_stds-scm-1.rockspec:13:37: accessing undefined variable 'new_globals' Checking nested/spec/sample_spec.lua 3 warnings nested/spec/sample_spec.lua:1:39: accessing undefined variable 'newproxy' nested/spec/sample_spec.lua:1:55: accessing undefined variable 'version' nested/spec/sample_spec.lua:1:64: accessing undefined variable 'read_globals' Checking normal_file.lua 4 warnings normal_file.lua:1:1: accessing undefined variable 'it' normal_file.lua:1:29: accessing undefined variable 'newproxy' normal_file.lua:1:45: accessing undefined variable 'version' normal_file.lua:1:54: accessing undefined variable 'read_globals' Checking sample_spec.lua 4 warnings sample_spec.lua:1:1: accessing undefined variable 'it' sample_spec.lua:1:28: accessing undefined variable 'newproxy' sample_spec.lua:1:44: accessing undefined variable 'version' sample_spec.lua:1:53: accessing undefined variable 'read_globals' Checking test/nested_normal_file.lua 4 warnings test/nested_normal_file.lua:1:1: accessing undefined variable 'it' test/nested_normal_file.lua:1:47: accessing undefined variable 'newproxy' test/nested_normal_file.lua:1:63: accessing undefined variable 'version' test/nested_normal_file.lua:1:72: accessing undefined variable 'read_globals' Checking test/sample_spec.lua 5 warnings test/sample_spec.lua:1:1: accessing undefined variable 'it' test/sample_spec.lua:1:37: accessing undefined variable 'newproxy' test/sample_spec.lua:1:47: accessing undefined variable 'math' test/sample_spec.lua:1:53: accessing undefined variable 'version' test/sample_spec.lua:1:62: accessing undefined variable 'read_globals' Checking tests/nested/sample_spec.lua 3 warnings tests/nested/sample_spec.lua:1:44: accessing undefined variable 'newproxy' tests/nested/sample_spec.lua:1:60: accessing undefined variable 'version' tests/nested/sample_spec.lua:1:69: accessing undefined variable 'read_globals' Checking tests/sample_spec.lua 3 warnings tests/sample_spec.lua:1:17: accessing undefined variable 'newproxy' tests/sample_spec.lua:1:33: accessing undefined variable 'version' tests/sample_spec.lua:1:42: accessing undefined variable 'read_globals' Total: 32 warnings / 0 errors in 9 files ]]):gsub("([a-z])/", "%1" .. package.config:sub(1, 1)), get_output(". --include-files .", "spec/projects/default_stds/")) end) it("uses new filename when selecting per-file overrides", function() assert.equal([[ Checking spec/samples/unused_code.lua OK Total: 0 warnings / 0 errors in 1 file ]], get_output "- --config=spec/configs/override_config.luacheckrc --filename spec/samples/unused_code.lua < spec/samples/unused_code.lua") end) it("uses all overrides prefixing file name", function() assert.equal([[ Checking spec/samples/unused_secondaries.lua 1 warning spec/samples/unused_secondaries.lua:12:1: value assigned to variable 'o' is unused Checking spec/samples/unused_code.lua 7 warnings spec/samples/unused_code.lua:3:18: unused argument 'baz' spec/samples/unused_code.lua:4:8: unused loop variable 'i' spec/samples/unused_code.lua:7:11: unused loop variable 'a' spec/samples/unused_code.lua:7:14: unused loop variable 'b' spec/samples/unused_code.lua:7:17: unused loop variable 'c' spec/samples/unused_code.lua:13:7: value assigned to variable 'x' is overwritten on line 14 before use spec/samples/unused_code.lua:14:1: value assigned to variable 'x' is overwritten on line 15 before use Total: 8 warnings / 0 errors in 2 files ]], get_output "spec/samples/unused_secondaries.lua spec/samples/unused_code.lua --config=spec/configs/multioverride_config.luacheckrc") end) it("allows reenabling warnings ignored in config using --enable", function() assert.equal([[ Checking spec/samples/bad_code.lua 4 warnings spec/samples/bad_code.lua:3:16: unused function 'helper' spec/samples/bad_code.lua:3:23: unused variable length argument spec/samples/bad_code.lua:7:10: setting non-standard global variable 'embrace' spec/samples/bad_code.lua:9:11: accessing undefined variable 'hepler' Checking spec/samples/unused_code.lua 1 warning spec/samples/unused_code.lua:5:13: unused variable 'q' Total: 5 warnings / 0 errors in 2 files ]], get_output "spec/samples/bad_code.lua spec/samples/unused_code.lua --config=spec/configs/override_config.luacheckrc --enable=211") end) it("allows using cli-specific options in top level config", function() assert.equal([[Files: 2 Warnings: 14 Errors: 0 Quiet: 0 Color: false Codes: true ]], get_output "spec/samples/bad_code.lua spec/samples/unused_code.lua --config=spec/configs/cli_specific_config.luacheckrc --std=lua52") end) it("uses exclude_files option", function() assert.equal(([[ Checking spec/samples/argparse-0.2.0.lua 9 warnings Checking spec/samples/compat.lua 4 warnings Checking spec/samples/custom_std_inline_options.lua 3 warnings / 1 error Checking spec/samples/global_inline_options.lua 3 warnings Checking spec/samples/globals.lua 2 warnings Checking spec/samples/indirect_globals.lua 3 warnings Checking spec/samples/inline_options.lua 7 warnings / 2 errors Checking spec/samples/line_length.lua 8 warnings Checking spec/samples/python_code.lua 1 error Checking spec/samples/read_globals.lua 5 warnings Checking spec/samples/read_globals_inline_options.lua 3 warnings Checking spec/samples/redefined.lua 7 warnings Checking spec/samples/reversed_fornum.lua 1 warning Checking spec/samples/unused_code.lua 9 warnings Checking spec/samples/unused_secondaries.lua 4 warnings Checking spec/samples/utf8.lua 4 warnings Checking spec/samples/utf8_error.lua 1 error Total: 72 warnings / 5 errors in 19 files ]]):gsub("(spec/samples)/", "%1"..package.config:sub(1, 1)), get_output "spec/samples --config=spec/configs/exclude_files_config.luacheckrc -qq --exclude-files spec/samples/global_fields.lua") end) it("loads exclude_files option correctly from upper directory", function() assert.equal([[ Checking argparse-0.2.0.lua 9 warnings Checking compat.lua 4 warnings Checking custom_std_inline_options.lua 3 warnings / 1 error Checking global_inline_options.lua 3 warnings Checking globals.lua 2 warnings Checking indirect_globals.lua 3 warnings Checking inline_options.lua 7 warnings / 2 errors Checking line_length.lua 8 warnings Checking python_code.lua 1 error Checking read_globals.lua 5 warnings Checking read_globals_inline_options.lua 3 warnings Checking redefined.lua 7 warnings Checking reversed_fornum.lua 1 warning Checking unused_code.lua 9 warnings Checking unused_secondaries.lua 4 warnings Checking utf8.lua 4 warnings Checking utf8_error.lua 1 error Total: 72 warnings / 5 errors in 19 files ]], get_output(". --config=spec/configs/exclude_files_config.luacheckrc -qq --exclude-files global_fields.lua", "spec/samples/")) end) it("combines excluded files from config and cli", function() assert.equal([[ Checking argparse-0.2.0.lua 9 warnings Checking compat.lua 4 warnings Checking custom_std_inline_options.lua 3 warnings / 1 error Checking global_inline_options.lua 3 warnings Checking globals.lua 2 warnings Checking indirect_globals.lua 3 warnings Checking inline_options.lua 7 warnings / 2 errors Checking line_length.lua 8 warnings Checking python_code.lua 1 error Checking redefined.lua 7 warnings Checking reversed_fornum.lua 1 warning Checking unused_code.lua 9 warnings Checking unused_secondaries.lua 4 warnings Checking utf8.lua 4 warnings Checking utf8_error.lua 1 error Total: 64 warnings / 5 errors in 17 files ]], get_output(". --config=spec/configs/exclude_files_config.luacheckrc -qq --exclude-files global_fields.lua --exclude-files " .. quote("./read*"), "spec/samples/")) end) it("allows defining custom stds", function() assert.equal([[ Checking spec/samples/globals.lua 2 warnings spec/samples/globals.lua:1:15: accessing undefined variable 'rawlen' spec/samples/globals.lua:1:22: accessing undefined variable 'tostring' Total: 2 warnings / 0 errors in 1 file ]], get_output "spec/samples/globals.lua --config=spec/configs/custom_stds_config.luacheckrc") assert.equal([[ Checking spec/samples/globals.lua 2 warnings spec/samples/globals.lua:1:1: accessing undefined variable 'print' spec/samples/globals.lua:1:15: accessing undefined variable 'rawlen' Total: 2 warnings / 0 errors in 1 file ]], get_output "spec/samples/globals.lua --config=spec/configs/custom_stds_config.luacheckrc --std=other_std") assert.equal([[ Checking spec/samples/globals.lua 1 warning spec/samples/globals.lua:1:15: accessing undefined variable 'rawlen' Total: 1 warning / 0 errors in 1 file ]], get_output "spec/samples/globals.lua --config=spec/configs/custom_stds_config.luacheckrc --std=+other_std") assert.equal([[ Checking spec/samples/globals.lua 1 warning spec/samples/globals.lua:1:7: accessing undefined variable 'setfenv' Total: 1 warning / 0 errors in 1 file ]], get_output "spec/samples/globals.lua --config=spec/configs/custom_stds_config.luacheckrc --std=lua52") end) it("allows importing options with require", function() assert.equal([[ Checking spec/samples/globals.lua 1 warning spec/samples/globals.lua:1:7: (W113) accessing undefined variable 'setfenv' Total: 1 warning / 0 errors in 1 file ]], get_output "spec/samples/globals.lua --config=spec/configs/import_config.luacheckrc") end) describe("global path", function() setup(function() os.rename(".luacheckrc", ".luacheckrc.bak") end) teardown(function() os.rename(".luacheckrc.bak", ".luacheckrc") end) it("uses global path as fallback if --[no-]config is not used", function() assert.equal([[ Checking spec/samples/compat.lua OK Total: 0 warnings / 0 errors in 1 file ]], get_output "spec/samples/compat.lua --default-config=spec/configs/global_config.luacheckrc") end) it("detects errors in global path config", function() assert.matches([[ Critical error: Couldn't load configuration from spec/configs/bad_config.luacheckrc: syntax error %(line 2: .* near 'method_missing'%) ]], get_output "spec/samples/compat.lua --default-config=spec/configs/bad_config.luacheckrc") end) it("does not use global path config if it is missing", function() assert.equal([[ Checking spec/samples/compat.lua 2 warnings spec/samples/compat.lua:1:2: accessing undefined variable 'setfenv' spec/samples/compat.lua:1:22: accessing undefined variable 'setfenv' Total: 2 warnings / 0 errors in 1 file ]], get_output "spec/samples/compat.lua --std=lua52 --default-config=spec/configs/config_404.luacheckrc") end) it("does not use global path as fallback if --no-default-config is not used", function() assert.equal([[ Checking spec/samples/compat.lua 2 warnings spec/samples/compat.lua:1:2: accessing undefined variable 'setfenv' spec/samples/compat.lua:1:22: accessing undefined variable 'setfenv' Total: 2 warnings / 0 errors in 1 file ]], get_output "spec/samples/compat.lua --std=lua52 --no-default-config") end) it("does not use global path as fallback if --config is used", function() assert.equal([[ Files: 1 Warnings: 4 Errors: 0 Quiet: 0 Color: false Codes: true ]], get_output "spec/samples/compat.lua --std=min --default-config=spec/configs/global_config.luacheckrc --config=spec/configs/cli_specific_config.luacheckrc") end) it("does not use global path as fallback if --no-config is used", function() assert.equal([[ Checking spec/samples/compat.lua 2 warnings spec/samples/compat.lua:1:2: accessing undefined variable 'setfenv' spec/samples/compat.lua:1:22: accessing undefined variable 'setfenv' Total: 2 warnings / 0 errors in 1 file ]], get_output "spec/samples/compat.lua --std=lua52 --default-config=spec/configs/global_config.luacheckrc --no-config") end) end) end) describe("error handling", function() it("raises critical error on config with syntax errors", function() assert.matches([[ Critical error: Couldn't load configuration from spec/configs/bad_config.luacheckrc: syntax error %(line 2: .*%) ]], get_output "spec/samples/empty.lua --config=spec/configs/bad_config.luacheckrc") assert.equal(4, get_exitcode "spec/samples/empty.lua --config=spec/configs/bad_config.luacheckrc") end) it("raises critical error on non-existent config", function() assert.equal([[ Critical error: Couldn't find configuration file spec/configs/config_404.luacheckrc ]], get_output "spec/samples/empty.lua --config=spec/configs/config_404.luacheckrc") assert.equal(4, get_exitcode "spec/samples/empty.lua --config=spec/configs/config_404.luacheckrc") end) it("raises critical error on config with invalid options", function() assert.equal([[ Critical error: in config loaded from spec/configs/invalid_config.luacheckrc: invalid value of option 'ignore': array of strings expected, got string ]], get_output "spec/samples/empty.lua --config=spec/configs/invalid_config.luacheckrc") assert.equal(4, get_exitcode "spec/samples/empty.lua --config=spec/configs/invalid_config.luacheckrc") end) end) describe("overwriting", function() it("prioritizes CLI options over config", function() assert.equal(1, get_exitcode "spec/samples/compat.lua --config=spec/configs/cli_override_config.luacheckrc --std=min --new-globals foo") end) it("prioritizes CLI options over config overrides", function() assert.equal(1, get_exitcode "spec/samples/compat.lua --config=spec/configs/cli_override_file_config.luacheckrc --std=min --new-globals foo") end) it("concats array-like options from config and CLI", function() assert.equal([[ Checking spec/samples/globals.lua OK Total: 0 warnings / 0 errors in 1 file ]], get_output "spec/samples/globals.lua --config=spec/configs/global_config.luacheckrc --globals tostring") end) end) end) end) luacheck-0.25.0/spec/config_spec.lua000066400000000000000000000212531410451437000172730ustar00rootroot00000000000000local lfs = require "lfs" local config = require "luacheck.config" local fs = require "luacheck.fs" local cur_dir = lfs.currentdir() local P = fs.normalize local function AP(p) return P(fs.join(cur_dir, p)) end local function nest(dir, func) local backed = false local function back() if not backed then lfs.chdir(cur_dir) backed = true end end finally(back) lfs.chdir(dir) func() back() end describe("config", function() it("has default path", function() assert.is_string(config.default_path) end) it("loads default config", function() local conf = assert.is_table(config.load_config()) local config_stack = assert.is_table(config.stack_configs({conf})) nest("spec/configs", function() local nested_conf = assert.is_table(config.load_config()) local nested_config_stack = assert.is_table(config.stack_configs({nested_conf})) assert.same(config_stack:get_top_options(), nested_config_stack:get_top_options()) assert.same(config_stack:get_options(P"spec/foo.lua"), nested_config_stack:get_options(P"../foo.lua")) end) assert.not_same(config_stack:get_options(P"spec/foo_spec.lua"), config_stack:get_options("foo_spec.lua")) end) it("works with empty config", function() local empty_config = assert.is_table(config.table_to_config({})) local config_stack = assert.is_table(config.stack_configs({empty_config})) assert.is_table(config_stack) assert.same({ quiet = 0, color = true, codes = false, ranges = false, formatter = "default", jobs = false, cache = false, include_files = {}, exclude_files = {} }, config_stack:get_top_options()) assert.same({{}}, config_stack:get_options("bar.lua")) end) it("loads config from path", function() local conf = assert.is_table(config.load_config(P"spec/configs/override_config.luacheckrc")) local config_stack = assert.is_table(config.stack_configs({conf})) local bad_code_options = config_stack:get_options(P"spec/samples/bad_code.lua") nest("spec/configs/project", function() local nested_conf = assert.is_table(config.load_config(P"spec/configs/override_config.luacheckrc")) local nested_config_stack = assert.is_table(config.stack_configs({nested_conf})) assert.same(config_stack:get_top_options(), nested_config_stack:get_top_options()) assert.same( bad_code_options, nested_config_stack:get_options(P"../../samples/bad_code.lua") ) end) assert.not_same( config_stack:get_options(P"spec/samples/bad_code.lua"), config_stack:get_options(P"spec/samples/unused_code.lua") ) end) it("returns nil, error on missing config", function() local conf, err = config.load_config(P"spec/configs/config_404.luacheckrc") assert.is_nil(conf) assert.equal("Couldn't find configuration file "..P"spec/configs/config_404.luacheckrc", err) end) it("returns nil, error on config with bad syntax", function() local conf, err = config.load_config(P"spec/configs/bad_config.luacheckrc") assert.is_nil(conf) assert.matches("Couldn't load configuration from "..P"spec/configs/bad_config.luacheckrc".. ": syntax error %(line 2: .*%)", err) nest("spec/configs/project", function() local nested_conf, nested_err = config.load_config(P"spec/configs/bad_config.luacheckrc") assert.is_nil(nested_conf) assert.matches("Couldn't load configuration from "..P"../../../spec/configs/bad_config.luacheckrc".. ": syntax error %(line 2: .*%)", nested_err) end) end) it("returns nil, error on config with runtime issues", function() local conf, err = config.load_config(P"spec/configs/runtime_bad_config.luacheckrc") assert.is_nil(conf) assert.equal("Couldn't load configuration from "..P"spec/configs/runtime_bad_config.luacheckrc".. ": runtime error (line 1: attempt to call a nil value)", err) nest("spec/configs/project", function() local nested_conf, nested_err = config.load_config(P"spec/configs/runtime_bad_config.luacheckrc") assert.is_nil(nested_conf) assert.equal("Couldn't load configuration from "..P"../../../spec/configs/runtime_bad_config.luacheckrc".. ": runtime error (line 1: attempt to call a nil value)", nested_err) end) end) it("stack_configs returns nil, error on invalid config", function() local conf = assert.is_table(config.load_config(P"spec/configs/invalid_config.luacheckrc")) local config_stack, err = config.stack_configs({conf}) assert.is_nil(config_stack) assert.equal("in config loaded from "..P"spec/configs/invalid_config.luacheckrc".. ": invalid value of option 'ignore': array of strings expected, got string", err) nest("spec/configs/project", function() local nested_conf = assert.is_table(config.load_config(P"spec/configs/invalid_config.luacheckrc")) local nested_config_stack, nested_err = config.stack_configs({nested_conf}) assert.is_nil(nested_config_stack) assert.equal("in config loaded from "..P"../../../spec/configs/invalid_config.luacheckrc".. ": invalid value of option 'ignore': array of strings expected, got string", nested_err) end) end) it("stack_configs returns nil, error on invalid custom std within config", function() local conf = assert.is_table(config.load_config(P"spec/configs/bad_custom_std_config.luacheckrc")) local config_stack, err = config.stack_configs({conf}) assert.is_nil(config_stack) -- luacheck: no max line length assert.equal("in config loaded from "..P"spec/configs/bad_custom_std_config.luacheckrc".. ": invalid custom std 'my_std': in field .read_globals.foo.fields[1]: string expected as field name, got number", err) end) it("stack_configs returns nil, error on config with invalid override", function() local conf = assert.is_table(config.load_config(P"spec/configs/invalid_override_config.luacheckrc")) local config_stack, err = config.stack_configs({conf}) assert.is_nil(config_stack) -- luacheck: no max line length assert.equal("in config loaded from "..P"spec/configs/invalid_override_config.luacheckrc".. ": invalid options for path 'spec/foo.lua': invalid value of option 'enable': array of strings expected, got string", err) nest("spec/configs/project", function() local nested_conf = assert.is_table(config.load_config(P"spec/configs/invalid_override_config.luacheckrc")) local nested_config_stack, nested_err = config.stack_configs({nested_conf}) assert.is_nil(nested_config_stack) assert.equal( "in config loaded from "..P"../../../spec/configs/invalid_override_config.luacheckrc".. ": invalid options for path 'spec/foo.lua': invalid value of option 'enable': array of strings expected, got string", nested_err ) end) end) it("stack_configs handles paths in options from configs loaded relatively", function() nest("spec/configs/project", function() local conf = assert.is_table(config.load_config(P"spec/configs/paths_config.luacheckrc")) local config_stack = assert.is_table(config.stack_configs({conf})) assert.same({ quiet = 0, color = true, codes = false, ranges = false, formatter = "helper.fmt", formatter_anchor_dir = P(cur_dir), jobs = false, cache = AP("something.luacheckcache"), include_files = { AP("foo"), AP("bar") }, exclude_files = { AP("foo/thing") } }, config_stack:get_top_options()) local extra_conf = assert.is_table(config.table_to_config({ cache = true, formatter = "helper.fmt2", include_files = {"baz"} })) local combined_stack = assert.is_table(config.stack_configs({conf, extra_conf})) assert.same({ quiet = 0, color = true, codes = false, ranges = false, formatter = "helper.fmt2", formatter_anchor_dir = P(cur_dir), jobs = false, cache = AP("something.luacheckcache"), include_files = { AP("foo"), AP("bar"), AP("spec/configs/project/baz") }, exclude_files = { AP("foo/thing") } }, combined_stack:get_top_options()) end) end) end) luacheck-0.25.0/spec/configs/000077500000000000000000000000001410451437000157365ustar00rootroot00000000000000luacheck-0.25.0/spec/configs/bad_config.luacheckrc000066400000000000000000000001171410451437000220360ustar00rootroot00000000000000-- let's talk about ruby def method_missing(*args); args.join(" "); end -- wat luacheck-0.25.0/spec/configs/bad_custom_std_config.luacheckrc000066400000000000000000000001641410451437000243040ustar00rootroot00000000000000stds.my_std = { read_globals = { foo = { fields = { 1, 2, 3 } } } } luacheck-0.25.0/spec/configs/cli_override_config.luacheckrc000066400000000000000000000000671410451437000237620ustar00rootroot00000000000000global = true globals = {"print", "setfenv", "rawlen"} luacheck-0.25.0/spec/configs/cli_override_file_config.luacheckrc000066400000000000000000000001451410451437000247560ustar00rootroot00000000000000files["spec/samples/compat.lua"] = { global = true, globals = {"print", "setfenv", "rawlen"} } luacheck-0.25.0/spec/configs/cli_specific_config.luacheckrc000066400000000000000000000004621410451437000237270ustar00rootroot00000000000000color = false codes = true formatter = function(report, file_names, options) return ([[ Files: %d Warnings: %d Errors: %d Quiet: %d Color: %s Codes: %s]]):format(#file_names, report.warnings, report.errors, options.quiet, options.color and "true" or "false", options.codes and "true" or "false") end luacheck-0.25.0/spec/configs/config.lua000066400000000000000000000000341410451437000177030ustar00rootroot00000000000000return { std = "lua52" } luacheck-0.25.0/spec/configs/custom_fields_config.luacheckrc000066400000000000000000000006761410451437000241620ustar00rootroot00000000000000stds = { my_server = { read_globals = { server = { fields = { bar = {read_only = false}, baz = {}, sessions = {read_only = false, other_fields = true} } } } } } read_globals = { server = { fields = { bar = {}, baz = {read_only = false}, sessions = {read_only = false, other_fields = true} } } } luacheck-0.25.0/spec/configs/custom_stds_config.luacheckrc000066400000000000000000000002061410451437000236560ustar00rootroot00000000000000stds = { my_std = {read_globals = {"print", "setfenv"}}, other_std = {read_globals = {"tostring", "setfenv"}} } std = "my_std" luacheck-0.25.0/spec/configs/exclude_files_config.luacheckrc000066400000000000000000000001111410451437000241150ustar00rootroot00000000000000exclude_files = {"spec/samples/defined?.lua", "**/bad*.lua"} std = "min" luacheck-0.25.0/spec/configs/format_opts_config.luacheckrc000066400000000000000000000000451410451437000236450ustar00rootroot00000000000000quiet = 1 ranges = true codes = true luacheck-0.25.0/spec/configs/global_config.luacheckrc000066400000000000000000000000511410451437000225450ustar00rootroot00000000000000globals = {"print", "setfenv", "rawlen"} luacheck-0.25.0/spec/configs/import_config.luacheckrc000066400000000000000000000001011410451437000226130ustar00rootroot00000000000000codes = true std = "lua51" return require "spec.configs.config" luacheck-0.25.0/spec/configs/invalid_config.luacheckrc000066400000000000000000000000171410451437000227350ustar00rootroot00000000000000ignore = "211" luacheck-0.25.0/spec/configs/invalid_override_config.luacheckrc000066400000000000000000000000671410451437000246410ustar00rootroot00000000000000ignore = {"211"} files["spec/foo.lua"].enable = "211" luacheck-0.25.0/spec/configs/multioverride_config.luacheckrc000066400000000000000000000002461410451437000242050ustar00rootroot00000000000000unused = false files["spec/samples/"] = { unused_args = true, enable = {"31"}, ignore = {"213"} } files["spec/samples/*_code.lua"] = { enable = {"213"} } luacheck-0.25.0/spec/configs/override_config.luacheckrc000066400000000000000000000002061410451437000231260ustar00rootroot00000000000000files["spec/samples/bad_code.lua"] = { redefined = false, compat = true } files["spec/samples/unused_code.lua"].unused = false luacheck-0.25.0/spec/configs/paths_config.luacheckrc000066400000000000000000000001701410451437000224260ustar00rootroot00000000000000cache = "something.luacheckcache" formatter = "helper.fmt" include_files = {"foo", "bar"} exclude_files = {"foo/thing"} luacheck-0.25.0/spec/configs/project/000077500000000000000000000000001410451437000174045ustar00rootroot00000000000000luacheck-0.25.0/spec/configs/project/.luacheckrc000066400000000000000000000001641410451437000215120ustar00rootroot00000000000000files["nested/ab.lua"].ignore = {"a"} files["nested/nested/"].enable = {"a"} files["nested/nested/"].ignore = {"b"} luacheck-0.25.0/spec/configs/project/nested/000077500000000000000000000000001410451437000206665ustar00rootroot00000000000000luacheck-0.25.0/spec/configs/project/nested/ab.lua000066400000000000000000000000141410451437000217460ustar00rootroot00000000000000print(a, b) luacheck-0.25.0/spec/configs/project/nested/nested/000077500000000000000000000000001410451437000221505ustar00rootroot00000000000000luacheck-0.25.0/spec/configs/project/nested/nested/abc.lua000066400000000000000000000000171410451437000233760ustar00rootroot00000000000000print(a, b, c) luacheck-0.25.0/spec/configs/runtime_bad_config.luacheckrc000066400000000000000000000000101410451437000235710ustar00rootroot00000000000000(nil)() luacheck-0.25.0/spec/cyclomatic_complexity_spec.lua000066400000000000000000000135461410451437000224400ustar00rootroot00000000000000local helper = require "spec.helper" local function assert_warnings(warnings, src) assert.same(warnings, helper.get_stage_warnings("detect_cyclomatic_complexity", src)) end describe("cyclomatic complexity detection", function() it("reports 1 for empty main chunk", function() assert_warnings({ {code = "561", line = 1, column = 1, end_column = 1, complexity = 1, function_type = "main_chunk"} }, "") end) it("reports 1 for functions with no branches", function() assert_warnings({ {code = "561", line = 1, column = 1, end_column = 1, complexity = 1, function_type = "main_chunk"} }, [[ print(1) do print(2) end return 3 ]]) end) it("reports 2 for functions with a single if branch", function() assert_warnings({ {code = "561", line = 1, column = 1, end_column = 1, complexity = 2, function_type = "main_chunk"} }, [[ print(1) if ... then print(2) end print(3) ]]) assert_warnings({ {code = "561", line = 1, column = 1, end_column = 1, complexity = 2, function_type = "main_chunk"} }, [[ print(1) if ... then print(2) else print(3) end ]]) end) it("reports 2 for functions with a single loop", function() assert_warnings({ {code = "561", line = 1, column = 1, end_column = 1, complexity = 2, function_type = "main_chunk"} }, [[ print(1) for i = 1, 10 do print(2) end print(3) ]]) assert_warnings({ {code = "561", line = 1, column = 1, end_column = 1, complexity = 2, function_type = "main_chunk"} }, [[ print(1) for k, v in pairs(t) do print(2) end print(3) ]]) assert_warnings({ {code = "561", line = 1, column = 1, end_column = 1, complexity = 2, function_type = "main_chunk"} }, [[ print(1) while cond() do print(2) end print(3) ]]) assert_warnings({ {code = "561", line = 1, column = 1, end_column = 1, complexity = 2, function_type = "main_chunk"} }, [[ print(1) repeat print(2) until cond() print(3) ]]) end) it("reports 2 for functions with a single boolean operator", function() assert_warnings({ {code = "561", line = 1, column = 1, end_column = 1, complexity = 2, function_type = "main_chunk"} }, [[ print(a and b) ]]) assert_warnings({ {code = "561", line = 1, column = 1, end_column = 1, complexity = 2, function_type = "main_chunk"} }, [[ print(a or b) ]]) end) it("provides appropriate names and types for functions", function() assert_warnings({ {code = "561", line = 1, column = 1, end_column = 1, complexity = 1, function_type = "main_chunk"}, {code = "561", line = 1, column = 8, end_column = 17, complexity = 1,function_type = "function"}, {code = "561", line = 2, column = 14, end_column = 27, complexity = 1, function_type = "function", function_name = "f"}, {code = "561", line = 3, column = 8, end_column = 21, complexity = 1, function_type = "function", function_name = "g"}, {code = "561", line = 4, column = 10, end_column = 25, complexity = 1, function_type = "function", function_name = "h"}, {code = "561", line = 5, column = 25, end_column = 38, complexity = 1, function_type = "function", function_name = "t.k"}, {code = "561", line = 6, column = 26, end_column = 39, complexity = 1, function_type = "function", function_name = "t.k1.k2.k3.k4"}, {code = "561", line = 7, column = 11, end_column = 24, complexity = 1, function_type = "function"}, {code = "561", line = 8, column = 6, end_column = 19, complexity = 1, function_type = "function"}, {code = "561", line = 9, column = 4, end_column = 27, complexity = 1, function_type = "method", function_name = "t.foo.bar"} }, [[ return function() local f = function() end g = function() end local function h() end local a, t = 1, {k = function() end} t.k1.k2 = {k3 = {k4 = function() end}} t[1] = function() end t[function() end] = 1 function t.foo:bar() end end ]]) end) it("reports correct complexity in complex cases", function() assert_warnings({ {code = "561", line = 1, column = 1, end_column = 1, complexity = 8, function_type = "main_chunk"} }, [[ if month == 1 then return 31 elseif month == 2 then if year % 4 == 0 then return 29 end return 28 elseif (month <= 7 and month % 2 == 1) or (month >= 8 and month % 2 == 0) then return 31 else return 30 end ]]) assert_warnings({ {code = "561", line = 1, column = 1, end_column = 1, complexity = 4, function_type = "main_chunk"} }, [[ local i, j = 0, 0 local total = 0 while to > 0 and i < to do while j < to do j = j + 1 total = total + 1 end i = i + 1 end return total ]]) assert_warnings({ {code = "561", line = 1, column = 1, end_column = 1, complexity = 4, function_type = "main_chunk"} }, [[ local i, j = 0, 0 local total = 0 repeat repeat j = j + 1 total = total + 1 until j >= to i = i + 1 until i >= to or to <= 0 return total ]]) assert_warnings({ {code = "561", line = 1, column = 1, end_column = 1, complexity = 7, function_type = "main_chunk"} }, [[ for k1 in t and pairs(t) or pairs({}) do for k2 in pairs(t) do if k1 and k2 then return k1 + k2 end end end ]]) assert_warnings({ {code = "561", line = 1, column = 1, end_column = 1, complexity = 6, function_type = "main_chunk"} }, [[ for i = 1, t > 10 and 10 or t do for j = 1, t do if i + j == i * j then return i end end end ]]) assert_warnings({ {code = "561", line = 1, column = 1, end_column = 1, complexity = 5, function_type = "main_chunk"} }, [[ local v1 = v and v*3 or 4 local t = {v1 == 3 and v*v or v/3} return t ]]) end) end) luacheck-0.25.0/spec/decoder_spec.lua000066400000000000000000000061501410451437000174320ustar00rootroot00000000000000local decoder = require "luacheck.decoder" local lua_utf8 = require "lua-utf8" local function assert_encoding(encoding, ...) local lib = encoding == "utf8" and lua_utf8 or string local length = select("#", ...) local bytes = lib.char(...) local chars = decoder.decode(bytes) local label_parts = {"("} for index = 1, length do table.insert(label_parts, ("\\u{%X}"):format((select(index, ...)))) end table.insert(label_parts, ")") local label = table.concat(label_parts) assert.equals(length, chars:get_length(), ":get_length" .. label) for from = 1, length do for to = from, length do assert.equals(lib.sub(bytes, from, to), chars:get_substring(from, to), ":get_substring" .. label) end end local iter, state, var if encoding == "utf8" then iter, state = lua_utf8.next, bytes else iter, state, var = ipairs({...}) end local index = 1 for offset, codepoint in iter, state, var do assert.equals(codepoint, chars:get_codepoint(index), ":get_codepoint" .. label) local from, to, match = chars:find("(.)", index) assert.equals(offset, from, ":find" .. label) assert.equals(offset, to, ":find" .. label) assert.equals(bytes:sub(offset, offset), match, ":find" .. label) index = index + 1 end end describe("decoder", function() it("decodes valid codepoints correctly", function() -- Checking literally all codepoints is very slow with coverage enabled, pick only a few. for base = 0, 0x10FFFF, 0x800 do for offset = 0, 0x100, 41 do local codepoint1 = base + offset local codepoint2 = codepoint1 + 9 assert_encoding("utf8", codepoint1, codepoint2) end end end) it("falls back to latin1 on invalid utf8", function() -- Bad first byte. assert_encoding("latin1", 0xC0, 0x80, 0x80, 0x80) assert_encoding("latin1", 0x00, 0xF8, 0x80, 0x80, 0x80) -- Two bytes, bad continuation byte. assert_encoding("latin1", 0x00, 0xC0, 0x00, 0xC0, 0x80) assert_encoding("latin1", 0x00, 0xC0, 0xFF, 0xC0, 0x80) -- Three bytes, bad first continuation byte. assert_encoding("latin1", 0x00, 0xE0, 0x00, 0xC0, 0x80) assert_encoding("latin1", 0x00, 0xE0, 0xFF, 0xC0, 0x80) -- Three bytes, bad second continuation byte. assert_encoding("latin1", 0x00, 0xE0, 0x80, 0x00, 0xC0, 0x80) assert_encoding("latin1", 0x00, 0xE0, 0x80, 0xFF, 0xC0, 0x80) -- Four bytes, bad first continuation byte. assert_encoding("latin1", 0x00, 0xF0, 0x00, 0xC0, 0x80) assert_encoding("latin1", 0x00, 0xF0, 0xFF, 0xC0, 0x80) -- Four bytes, bad second continuation byte. assert_encoding("latin1", 0x00, 0xF0, 0x80, 0x00, 0xC0, 0x80) assert_encoding("latin1", 0x00, 0xF0, 0x80, 0xFF, 0xC0, 0x80) -- Four bytes, bad third continuation byte. assert_encoding("latin1", 0x00, 0xF0, 0x80, 0x80, 0x00, 0xC0, 0x80) assert_encoding("latin1", 0x00, 0xF0, 0x80, 0x80, 0xFF, 0xC0, 0x80) -- Codepoint too large. assert_encoding("latin1", 0xF7, 0x80, 0x80, 0x80, 0x00) end) end) luacheck-0.25.0/spec/empty_blocks_spec.lua000066400000000000000000000026641410451437000205260ustar00rootroot00000000000000local helper = require "spec.helper" local function assert_warnings(warnings, src) assert.same(warnings, helper.get_stage_warnings("detect_empty_blocks", src)) end describe("empty block detection", function() it("detects empty blocks", function() assert_warnings({ {code = "541", line = 1, column = 1, end_column = 6}, {code = "542", line = 3, column = 8, end_column = 11}, {code = "542", line = 5, column = 12, end_column = 15}, {code = "542", line = 7, column = 1, end_column = 4} }, [[ do end if ... then elseif ... then else end if ... then somehing() else something_else() end do something() end while ... do end repeat until ... ]]) end) it("detects empty blocks in nested blocks and functions", function() assert_warnings({ {code = "541", line = 4, column = 10, end_column = 15}, {code = "541", line = 7, column = 13, end_column = 18}, {code = "541", line = 12, column = 22, end_column = 27}, {code = "542", line = 14, column = 27, end_column = 30} }, [[ do while x do if y then do end else repeat do end function t() for i = 1, 10 do for _, v in ipairs(tab) do do end if c then end end end end until z end end end ]]) end) end) luacheck-0.25.0/spec/expand_rockspec_spec.lua000066400000000000000000000027111410451437000211740ustar00rootroot00000000000000local expand_rockspec = require "luacheck.expand_rockspec" local fs = require "luacheck.fs" local P = fs.normalize describe("expand_rockspec", function() it("returns sorted array of lua files related to a rock", function() assert.same({ "bar.lua", "baz.lua", "bin.lua", "foo.lua" }, expand_rockspec("spec/folder/rockspec")) end) it("autodetects modules for rockspecs without build table", function() assert.same({ P"spec/rock/src/rock.lua", P"spec/rock/src/rock/mod.lua", P"spec/rock/bin/rock.lua" }, expand_rockspec("spec/rock/rock-dev-1.rockspec")) end) it("autodetects modules for rockspecs without build.modules table", function() assert.same({ P"spec/rock2/mod.lua" }, expand_rockspec("spec/rock2/rock2-dev-1.rockspec")) end) it("returns nil, \"I/O\" for non-existent paths", function() local ok, err = expand_rockspec("spec/folder/non-existent") assert.is_nil(ok) assert.equal("I/O", err) end) it("returns nil, \"syntax\" for rockspecs with syntax errors", function() local ok, err = expand_rockspec("spec/folder/bad_config") assert.is_nil(ok) assert.equal("syntax", err) end) it("returns nil, \"runtime\" for rockspecs with run-time errors", function() local ok, err = expand_rockspec("spec/folder/bad_rockspec") assert.is_nil(ok) assert.equal("runtime", err) end) end) luacheck-0.25.0/spec/filter_spec.lua000066400000000000000000000325141410451437000173150ustar00rootroot00000000000000local filter_full = require "luacheck.filter".filter local function filter(issue_arrays, opts) local report = {} for i, issues in ipairs(issue_arrays) do local line_lengths = {} for issue_index, issue in ipairs(issues) do issue.line = issue_index issue.column = 1 line_lengths[issue_index] = 0 end report[i] = {warnings = issues, inline_options = {}, line_lengths = line_lengths, line_endings = {}} end local result = filter_full(report, opts) for _, file_report in ipairs(result) do for _, issue in ipairs(file_report) do issue.line = nil issue.column = nil end end return result end describe("filter", function() it("filters warnings by name", function() assert.same({ { { code = "211", name = "baz" } } }, filter({ { { code = "211", name = "foo" }, { code = "211", name = "bar" }, { code = "211", name = "baz" } } }, { ignore = {"bar"}, only = {"bar", "baz"} })) end) it("removes unused var/value and redefined warnings related to _, unless it's useless", function() assert.same({ { { code = "211", name = "foo" }, { code = "211", name = "_", useless = true } } }, filter({ { { code = "211", name = "foo" }, { code = "211", name = "_", useless = true }, { code = "412", name = "_" }, { code = "221", name = "_" } } })) end) it("filters warnings by type", function() assert.same({ { { code = "211", name = "foo" } } }, filter({ { { code = "211", name = "foo" }, { code = "111", name = "bar" }, { code = "413", name = "baz" } } }, { global = false, redefined = false })) assert.same({ { { code = "221", name = "foo" }, { code = "111", name = "bar" }, { code = "413", name = "baz" } } }, filter({ { { code = "221", name = "foo" }, { code = "111", name = "bar" }, { code = "321", name = "qu" }, { code = "413", name = "baz" } } }, { ignore = {"32"} })) end) it("filters warnings by code and name using patterns", function() assert.same({ { { code = "212", name = "bar" }, { code = "413", name = "_baz" } } }, filter({ { { code = "212", name = "bar" }, { code = "212", name = "_qu" }, { code = "321", name = "foo" }, { code = "413", name = "_baz" } } }, { ignore = {"foo", "212/_.*"} })) end) it("filters unused warnings by subtype", function() assert.same({ { { code = "211", name = "foo" } } }, filter({ { { code = "211", name = "foo" }, { code = "311", name = "bar" }, { code = "212", name = "baz" }, { code = "221", name = "qu" } } }, { ignore = {"22", "31"}, unused_args = false })) end) it("filters unused warnings related to secondary variables", function() assert.same({ { { code = "212", name = "baz" } } }, filter({ { { code = "211", name = "foo", secondary = true }, { code = "311", name = "bar", secondary = true }, { code = "212", name = "baz" } } }, { unused_secondaries = false })) end) it("filters unused and redefined warnings related to implicit self", function() assert.same({ { { code = "212", name = "self" } } }, filter({ { { code = "212", name = "self", self = true }, { code = "432", name = "self", self = true }, { code = "212", name = "self" } } }, { self = false })) end) it("filters defined globals", function() assert.same({ { { code = "111", name = "module" } } }, filter({ { { code = "113", name = "foo" }, { code = "111", name = "module" } } }, { std = {}, globals = {"foo"} })) end) it("filters standard globals", function() assert.same({ { { code = "111", name = "module" } } }, filter({ { { code = "113", name = "package" }, { code = "111", name = "module" } } }, { std = "min" })) end) it("allows defined globals with allow_defined = true", function() assert.same({ { { code = "131", name = "bar" }, { code = "113", name = "baz" } } }, filter({ { { code = "113", name = "foo" }, { code = "111", name = "foo" }, { code = "111", name = "bar" }, { code = "113", name = "baz" } } }, { allow_defined = true })) end) it("allows globals defined in top level function scope with allow_defined_top = true", function() assert.same({ { { code = "111", name = "bar" }, { code = "113", name = "baz" } } }, filter({ { { code = "113", name = "foo" }, { code = "111", name = "foo", top = true }, { code = "111", name = "bar" }, { code = "113", name = "baz" } } }, { allow_defined_top = true })) end) it("allows globals defined in the same file with module = true", function() assert.same({ {}, { { code = "113", name = "foo" } } }, filter({ { { code = "113", name = "foo" }, { code = "111", name = "foo" } }, { { code = "113", name = "foo" } } }, { allow_defined = true, module = true })) end) it("only allows setting globals defined in the same file with module = true", function() assert.same({ {}, { { code = "111", name = "string", module = true }, { code = "111", name = "bar", module = true } } }, filter({ { { code = "111", name = "bar" } }, { { code = "111", name = "foo", top = true }, { code = "111", name = "foo" }, { code = "111", name = "string" }, { code = "111", name = "bar" } } }, { { allow_defined = true, ignore = {"13"} }, { allow_defined_top = true, module = true } })) end) it("using an implicitly defined global from a module marks it as used", function() assert.same({ {}, {} }, filter({ { { code = "111", name = "foo" } }, { { code = "113", name = "foo" }, { code = "111", name = "bar" } } }, { { allow_defined = true }, { allow_defined = true, module = true } })) end) it("applies inline option events and per-line options", function() assert.same({ { {code = "111", name = "not_print", line = 1, column = 1}, {code = "111", name = "print", line = 5, column = 1}, {code = "121", name = "print", line = 7, column = 1}, {code = "021", msg = "invalid value of option 'std': unknown std 'bad_std'", line = 8, column = 1}, {code = "021", msg = "invalid value of option 'std': unknown std 'another_bad_std'", line = 11, column = 20}, {code = "211", name = "not_print", line = 14, column = 1} } }, filter_full({ { warnings = { {code = "111", name = "not_print", line = 1, column = 1}, {code = "111", name = "not_print", line = 4, column = 1}, {code = "111", name = "print", line = 5, column = 1}, {code = "111", name = "print", line = 7, column = 1}, {code = "111", name = "not_print", line = 12, column = 1}, {code = "211", name = "not_print", line = 14, column = 1}, {code = "311", name = "c", line = 14, column = 2} }, inline_options = { {options = {std = "none"}, line = 3, column = 1}, {options = {ignore = {".*"}}, line = 4, column = 10}, {pop_count = 1, line = 5}, {pop_count = 1, line = 7}, {options = {std = "bad_std"}, line = 8, column = 1}, {options = {std = "max"}, line = 9, column = 1}, {options = {std = "another_bad_std"}, line = 11, column = 20}, {options = {ignore = {"not_print"}}, line = 12, column = 1}, {options = {ignore = {"211"}}, line = 13, column = 1}, {pop_count = 2, options = {ignore = {"c"}}, line = 14, column = 1} }, line_lengths = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, line_endings = {} } }, { { std = "max" } })) end) it("adds line length warnings", function() assert.same({ { {code = "631", line = 2, column = 121, end_column = 121, max_length = 120}, {code = "631", line = 5, column = 16, end_column = 18, line_ending = "string", max_length = 15} } }, filter_full({ { warnings = {}, inline_options = { {options = {max_line_length = 20}, line = 3, column = 1}, {options = {max_string_line_length = 15}, line = 4, column = 1}, {options = {max_line_length = false}, line = 6, column = 1} }, line_lengths = {120, 121, 15, 20, 18, 15, 200}, line_endings = {[5] = "string"} } }, {})) end) end) luacheck-0.25.0/spec/folder/000077500000000000000000000000001410451437000155615ustar00rootroot00000000000000luacheck-0.25.0/spec/folder/bad_config000066400000000000000000000000141410451437000175520ustar00rootroot00000000000000foo = `bar` luacheck-0.25.0/spec/folder/bad_rockspec000066400000000000000000000000201410451437000201130ustar00rootroot00000000000000build "builtin" luacheck-0.25.0/spec/folder/bom000066400000000000000000000000131410451437000162530ustar00rootroot00000000000000foo bar luacheck-0.25.0/spec/folder/config000066400000000000000000000000141410451437000167440ustar00rootroot00000000000000foo = "bar" luacheck-0.25.0/spec/folder/env_config000066400000000000000000000000141410451437000176140ustar00rootroot00000000000000foo = bar() luacheck-0.25.0/spec/folder/folder1/000077500000000000000000000000001410451437000171155ustar00rootroot00000000000000luacheck-0.25.0/spec/folder/folder1/another000066400000000000000000000000001410451437000204660ustar00rootroot00000000000000luacheck-0.25.0/spec/folder/folder1/fail000066400000000000000000000000001410451437000177410ustar00rootroot00000000000000luacheck-0.25.0/spec/folder/folder1/file000066400000000000000000000000001410451437000177450ustar00rootroot00000000000000luacheck-0.25.0/spec/folder/folder2/000077500000000000000000000000001410451437000171165ustar00rootroot00000000000000luacheck-0.25.0/spec/folder/folder2/garbage000066400000000000000000000000001410451437000204170ustar00rootroot00000000000000luacheck-0.25.0/spec/folder/foo000066400000000000000000000000111410451437000162570ustar00rootroot00000000000000contents luacheck-0.25.0/spec/folder/rockspec000066400000000000000000000003511410451437000173140ustar00rootroot00000000000000build = { type = "builtin", modules = { foo = "foo.lua", bar = "bar.lua", qu = "qu.c" }, install = { lua = { baz = "baz.lua" }, bin = { bin = "bin.lua" } } } luacheck-0.25.0/spec/format_spec.lua000066400000000000000000000106611410451437000173170ustar00rootroot00000000000000local format = require "luacheck.format".format local function mark_colors(s) return (s:gsub("\27%[%d+m", "\27"):gsub("\27+", "#")) end describe("format", function() it("returns formatted report", function() assert.equal([[Checking stdin 1 warning stdin:2:7: unused global variable 'foo' Checking foo.lua 1 warning foo.lua:2:7: empty statement Checking bar.lua OK Checking baz.lua 1 error baz.lua:4:3: something went wrong Total: 2 warnings / 1 error in 4 files]], format({ warnings = 2, errors = 1, fatals = 0, { { code = "131", name = "foo", line = 2, column = 7 } }, { { code = "551", line = 2, column = 7 } }, {}, { { code = "011", line = 4, column = 3, msg = "something went wrong" } } }, {"stdin", "foo.lua", "bar.lua", "baz.lua"}, {color = false})) end) it("does not output OK messages with options.quiet >= 1", function() assert.equal([[Checking stdin 1 warning stdin:2:7: unused global variable 'foo' Checking foo.lua 1 warning / 1 error foo.lua:2:7: unused global variable 'foo' foo.lua:3:10: bad, bad inline option Checking baz.lua Syntax error baz.lua: error message Total: 2 warnings / 1 error in 3 files, couldn't check 1 file]], format({ warnings = 2, errors = 1, fatals = 1, { { code = "131", name = "foo", line = 2, column = 7 } }, { { code = "131", name = "foo", line = 2, column = 7 }, { code = "021", msg = "bad, bad inline option", line = 3, column = 10 } }, {}, { fatal = "syntax", msg = "error message" } }, {"stdin", "foo.lua", "bar.lua", "baz.lua"}, {quiet = 1, color = false})) end) it("does not output warnings with options.quiet >= 2", function() assert.equal([[Checking stdin 1 warning Checking foo.lua 1 warning Checking baz.lua Syntax error Total: 2 warnings / 0 errors in 3 files, couldn't check 1 file]], format({ warnings = 2, errors = 0, fatals = 1, { { code = "131", name = "foo", line = 2, column = 7 } }, { { code = "131", name = "foo", line = 2, column = 7 } }, {}, { fatal = "syntax" } }, {"stdin", "foo.lua", "bar.lua", "baz.lua"}, {quiet = 2, color = false})) end) it("does not output file info with options.quiet == 3", function() assert.equal("Total: 2 warnings / 0 errors in 3 files, couldn't check 1 file", format({ warnings = 2, errors = 0, fatals = 1, { { code = "131", name = "foo", line = 2, column = 7 } }, { { code = "131", name = "foo", line = 2, column = 7 } }, {}, { fatal = "syntax" } }, {"stdin", "foo.lua", "bar.lua", "baz.lua"}, {quiet = 3, color = false})) end) it("colors output by default", function() if package.config:sub(1, 1) == "\\" and not os.getenv("ANSICON") then pending("uses terminal colors") end assert.equal([[Checking stdin #1 warning# stdin:2:7: unused global variable #foo# Checking foo.lua #1 warning# foo.lua:2:7: unused global variable #foo# Checking bar.lua #OK# Checking baz.lua #Syntax error# baz.lua: error message Total: #2# warnings / #0# errors in 3 files, couldn't check 1 file]], mark_colors(format({ warnings = 2, errors = 0, fatals = 1, { { code = "131", name = "foo", line = 2, column = 7 } }, { { code = "131", name = "foo", line = 2, column = 7 } }, {}, { fatal = "syntax", msg = "error message" } }, {"stdin", "foo.lua", "bar.lua", "baz.lua"}, {}))) end) end) luacheck-0.25.0/spec/formatters/000077500000000000000000000000001410451437000164745ustar00rootroot00000000000000luacheck-0.25.0/spec/formatters/custom_formatter.lua000066400000000000000000000003431410451437000225740ustar00rootroot00000000000000return function(report, file_names, options) return ([[ Files: %d Formatter: %s Quiet: %d Color: %s Codes: %s]]):format(#file_names, options.formatter, options.quiet, tostring(options.color), tostring(options.codes)) end luacheck-0.25.0/spec/fs_spec.lua000066400000000000000000000051361410451437000164400ustar00rootroot00000000000000local fs = require "luacheck.fs" local utils = require "luacheck.utils" local P = fs.normalize describe("fs", function() describe("is_dir", function() it("returns true for directories", function() assert.is_true(fs.is_dir("spec/folder")) end) it("returns false for files", function() assert.is_false(fs.is_dir("spec/folder/foo")) end) it("returns false for non-existent paths", function() assert.is_false(fs.is_dir("spec/folder/non-existent")) end) end) describe("is_file", function() it("returns true for files", function() assert.is_true(fs.is_file("spec/folder/foo")) end) it("returns false for directories", function() assert.is_false(fs.is_file("spec/folder")) end) it("returns false for non-existent paths", function() assert.is_false(fs.is_file("spec/folder/non-existent")) end) end) describe("extract_files", function() it("returns sorted list of files in a directory matching pattern", function() assert.same({ P"spec/folder/folder1/fail", P"spec/folder/folder1/file", P"spec/folder/foo" }, fs.extract_files(P"spec/folder", "^f")) end) end) describe("get_mtime", function() it("returns modification time as a number", function() assert.number(fs.get_mtime("spec/folder/foo")) end) it("returns nil for non-existent files", function() assert.is_nil(fs.get_mtime("spec/folder/non-existent")) end) end) describe("get_current_dir", function() it("returns absolute path to current directory with trailing directory separator", function() local current_dir = fs.get_current_dir() assert.string(current_dir) assert.matches(utils.dir_sep .. "$", current_dir) assert.not_equal("", (fs.split_base(current_dir))) assert.is_true(fs.is_file(current_dir .. "spec/folder/foo")) end) end) describe("find_file", function() it("finds file in a directory", function() local path = P(fs.get_current_dir() .. "spec/folder") assert.equal(path, fs.find_file(path, "foo")) end) it("finds file in a parent directory", function() local path = P(fs.get_current_dir() .. "spec/folder") assert.equal(path, fs.find_file(fs.join(path, "folder1"), "foo")) end) it("returns nil if can't find file", function() assert.is_nil( fs.find_file(fs.get_current_dir(), "this file shouldn't exist or it will make luacheck testsuite break")) end) end) end) luacheck-0.25.0/spec/globals_spec.lua000066400000000000000000000066401410451437000174540ustar00rootroot00000000000000local helper = require "spec.helper" local function assert_warnings(warnings, src) assert.same(warnings, helper.get_stage_warnings("detect_globals", src)) end describe("global detection", function() it("detects global set", function() assert_warnings({ {code = "111", name = "foo", line = 1, column = 1, end_column = 3, top = true} }, [[ foo = {} ]]) end) it("detects global set in nested functions", function() assert_warnings({ {code = "111", name = "foo", line = 2, column = 4, end_column = 6} }, [[ local function bar() foo = {} end bar() ]]) end) it("detects global access in multi-assignments", function() assert_warnings({ {code = "111", name = "y", line = 2, column = 4, end_column = 4, top = true}, {code = "113", name = "print", line = 3, column = 1, end_column = 5} }, [[ local x x, y = 1 print(x) ]]) end) it("detects global access in self swap", function() assert_warnings({ {code = "113", name = "a", line = 1, column = 11, end_column = 11}, {code = "113", name = "print", line = 2, column = 1, end_column = 5} }, [[ local a = a print(a) ]]) end) it("detects global mutation", function() assert_warnings({ {code = "112", name = "a", indexing = {false}, line = 1, column = 1, end_column = 1} }, [[ a[1] = 6 ]]) end) it("detects indirect global field access", function() assert_warnings({ { code = "113", name = "b", indexing = {false}, line = 2, column = 15, end_column = 15 }, { code = "113", name = "b", indexing = {false, false, "foo"}, previous_indexing_len = 2, line = 3, column = 8, end_column = 12, indirect = true } }, [[ local c = "foo" local alias = b[1] return alias[2][c] ]]) end) it("detects indirect global field mutation", function() assert_warnings({ { code = "113", name = "b", indexing = {false}, line = 2, column = 15, end_column = 15 }, { code = "112", name = "b", indexing = {false, false, "foo"}, previous_indexing_len = 2, line = 3, column = 1, end_column = 5, indirect = true } }, [[ local c = "foo" local alias = b[1] alias[2][c] = c ]]) end) it("provides indexing information for warnings related to global fields", function() assert_warnings({ { code = "113", name = "global", line = 2, column = 11, end_column = 16 }, { code = "113", name = "global", indexing = {"foo", "bar", false}, indirect = true, previous_indexing_len = 1, line = 3, column = 15, end_column = 15 }, { code = "113", name = "global", indexing = {"foo", "bar", false, true}, indirect = true, previous_indexing_len = 4, line = 5, column = 8, end_column = 13 } }, [[ local c = "foo" local g = global local alias = g[c].bar[1] local alias2 = alias return alias2[...] ]]) end) end) luacheck-0.25.0/spec/globbing_spec.lua000066400000000000000000000054521410451437000176140ustar00rootroot00000000000000local globbing = require "luacheck.globbing" local fs = require "luacheck.fs" local cur_dir = fs.get_current_dir() local function check_match(expected_result, glob, path) glob = fs.normalize(fs.join(cur_dir, glob)) path = fs.normalize(fs.join(cur_dir, path)) assert.equal(expected_result, globbing.match(glob, path)) end describe("globbing", function() describe("match", function() it("returns true on literal match", function() check_match(true, "foo/bar", "foo/bar") end) it("returns true on literal match after normalization", function() check_match(true, "foo//bar/baz/..", "./foo/bar/") end) it("returns false for on literal mismatch", function() check_match(false, "foo/bar", "foo/baz") end) it("accepts subdirectory matches", function() check_match(true, "foo/bar", "foo/bar/baz") end) it("understands wildcards", function() check_match(true, "*", "foo") check_match(true, "foo/*r", "foo/bar") check_match(true, "foo/*r", "foo/bar/baz") check_match(false, "foo/*r", "foo/baz") end) it("understands optional characters", function() check_match(false, "?", "foo") check_match(true, "???", "foo") check_match(true, "????", "foo") check_match(true, "f?o/?a?", "foo/bar") check_match(false, "f?o/?a?", "foo/abc") end) it("understands ranges and classes", function() check_match(true, "[d-h]o[something]", "foo") check_match(false, "[d-h]o[somewhere]", "bar") check_match(false, "[.-h]o[i-z]", "bar") end) it("accepts closing bracket as first class character", function() check_match(true, "[]]", "]") check_match(false, "[]]", "[") check_match(true, "[]foo][]foo][]foo]", "foo") end) it("accepts dash as first or last class character", function() check_match(true, "[-]", "-") check_match(false, "[-]", "+") check_match(true, "[---]", "-") end) it("understands negation", function() check_match(true, "[!foo][!bar][!baz]", "boo") check_match(false, "[!foo][!bar][!baz]", "far") check_match(false, "[!a-z]", "g") end) it("understands recursive globbing using **", function() check_match(true, "**/*.lua", "foo.lua") check_match(true, "**/*.lua", "foo/bar.lua") check_match(false, "foo/**/*.lua", "bar.lua") check_match(false, "foo/**/*.lua", "foo.lua") check_match(true, "foo/**/bar/*.lua", "foo/bar/baz.lua") check_match(true, "foo/**/bar/*.lua", "foo/foo2/foo3/bar/baz.lua") check_match(false, "foo/**/bar/*.lua", "foo/baz.lua") check_match(false, "foo/**/bar/*.lua", "bar/baz.lua") end) end) end) luacheck-0.25.0/spec/helper.lua000066400000000000000000000045521410451437000162760ustar00rootroot00000000000000local helper = {} local function get_lua() local index = -1 local res = "lua" while arg[index] do res = arg[index] index = index - 1 end return res end local dir_sep = package.config:sub(1, 1) -- Return path to root directory when run from `path`. local function antipath(path) local _, level = path:gsub("[/\\]", "") return (".."..dir_sep):rep(level) end function helper.luacov_config(prefix) return { statsfile = prefix.."luacov.stats.out", modules = { luacheck = "src/luacheck/init.lua", ["luacheck.*"] = "src", ["luacheck.*.*"] = "src", ["luacheck.*.*.*"] = "src" }, exclude = { "bin/luacheck$" } } end local luacov = package.loaded["luacov.runner"] local lua -- Returns command that runs `luacheck` executable from `loc_path`. function helper.luacheck_command(loc_path) lua = lua or get_lua() loc_path = loc_path or "." local prefix = antipath(loc_path) local cmd = ("cd %s && %s"):format(loc_path, lua) -- Extend package.path to allow loading this helper and luacheck modules. cmd = cmd..(' -e "package.path=[[%s?.lua;%ssrc%s?.lua;%ssrc%s?%sinit.lua;]]..package.path"'):format( prefix, prefix, dir_sep, prefix, dir_sep, dir_sep) if luacov then -- Launch luacov. cmd = cmd..(' -e "require[[luacov.runner]](require[[spec.helper]].luacov_config([[%s]]))"'):format(prefix) end return ("%s %sbin%sluacheck.lua"):format(cmd, prefix, dir_sep) end function helper.get_chstate_after_stage(target_stage_name, source) -- Luacov isn't yet started when helper is required, defer requiring luacheck -- modules so that their main chunks get covered. local check_state = require "luacheck.check_state" local stages = require "luacheck.stages" local chstate = check_state.new(source) for index, stage_name in ipairs(stages.names) do stages.modules[index].run(chstate) if stage_name == target_stage_name then return chstate end chstate.warnings = {} end error("no stage " .. target_stage_name, 0) end function helper.get_stage_warnings(target_stage_name, source) local core_utils = require "luacheck.core_utils" local chstate = helper.get_chstate_after_stage(target_stage_name, source) core_utils.sort_by_location(chstate.warnings) return chstate.warnings end return helper luacheck-0.25.0/spec/lexer_spec.lua000066400000000000000000000476701410451437000171600ustar00rootroot00000000000000local decoder = require "luacheck.decoder" local lexer = require "luacheck.lexer" local function new_state_from_source_bytes(bytes) return lexer.new_state(decoder.decode(bytes)) end local function get_tokens(source) local lexer_state = new_state_from_source_bytes(source) local tokens = {} repeat local token = {} token.token, token.token_value, token.line, token.offset = lexer.next_token(lexer_state) tokens[#tokens+1] = token until token.token == "eof" return tokens end local function get_token(source) local lexer_state = new_state_from_source_bytes(source) local token = {} token.token, token.token_value = lexer.next_token(lexer_state) return token end local function maybe_error(lexer_state) local ok, msg, line, offset, end_offset = lexer.next_token(lexer_state) return not ok and {msg = msg, line = line, offset = offset, end_offset = end_offset} end local function get_error(source) return maybe_error(new_state_from_source_bytes(source)) end local function get_last_error(source) local lexer_state = new_state_from_source_bytes(source) local err repeat err = maybe_error(lexer_state) until err return err end describe("lexer", function() it("parses EOS correctly", function() assert.same({token = "eof"}, get_token(" ")) end) it("parses names correctly", function() assert.same({token = "name", token_value = "foo"}, get_token("foo")) assert.same({token = "name", token_value = "_"}, get_token("_")) assert.same({token = "name", token_value = "foo1_2"}, get_token("foo1_2")) assert.same({token = "name", token_value = "foo"}, get_token("foo!")) end) it("parses keywords correctly", function() assert.same({token = "do"}, get_token("do")) assert.same({token = "goto"}, get_token("goto fail;")) end) it("parses operators and special tokens correctly", function() assert.same({token = "="}, get_token("= =")) assert.same({token = "=="}, get_token("==")) assert.same({token = "<"}, get_token("< =")) assert.same({token = "<="}, get_token("<=")) assert.same({token = "<<"}, get_token("<<")) assert.same({token = ">"}, get_token("> =")) assert.same({token = ">="}, get_token(">=")) assert.same({token = ">>"}, get_token(">>")) assert.same({token = "/"}, get_token("/ /")) assert.same({token = "//"}, get_token("//")) assert.same({token = "."}, get_token(".?.")) assert.same({token = "."}, get_token(".")) assert.same({token = ".."}, get_token("..%")) assert.same({token = "...", token_value = "..."}, get_token("...")) assert.same({token = ":"}, get_token(":.:")) assert.same({token = "::"}, get_token("::.")) end) it("parses single character tokens correctly", function() assert.same({token = "("}, get_token("((")) assert.same({token = "["}, get_token("[x]")) assert.same({token = "$"}, get_token("$$$")) end) describe("when parsing short strings", function() it("parses empty short strings correctly", function() assert.same({token = "string", token_value = ""}, get_token([[""]])) assert.same({token = "string", token_value = ""}, get_token([['']])) end) it("parses short strings containing quotation marks correctly", function() assert.same({token = "string", token_value = "'"}, get_token([["'"]])) assert.same({token = "string", token_value = '"'}, get_token([['"']])) end) it("parses simple short strings correctly", function() assert.same({token = "string", token_value = "foo"}, get_token([["foo"]])) end) it("parses simple escape sequences correctly", function() assert.same({token = "string", token_value = "\r\n"}, get_token([["\r\n"]])) assert.same({token = "string", token_value = "foo\\bar"}, get_token([["foo\\bar"]])) assert.same({token = "string", token_value = "a\'\'b\"\""}, get_token([["a\'\'b\"\""]])) end) it("parses escaped newline correctly", function() assert.same({token = "string", token_value = "foo \nbar"}, get_token([["foo \ bar"]])) assert.same({token = "string", token_value = "foo \n\n\nbar"}, get_token([["foo \ \ \ bar"]])) end) it("parses \\z correctly", function() assert.same({token = "string", token_value = "foo "}, get_token([["foo \z"]])) assert.same({token = "string", token_value = "foo bar"}, get_token([["foo \zbar"]])) assert.same({token = "string", token_value = "foo bar"}, get_token([["foo \z bar"]])) -- luacheck: ignore 613 assert.same({token = "string", token_value = "foo bar"}, get_token([["foo \z bar\z "]])) end) it("parses decimal escape sequences correctly", function() assert.same({token = "string", token_value = "\0buffer exploit"}, get_token([["\0buffer exploit"]])) assert.same({token = "string", token_value = "foo bar"}, get_token([["foo b\97r"]])) assert.same({token = "string", token_value = "\1234"}, get_token([["\1234"]])) assert.same( {line = 1, offset = 2, end_offset = 5, msg = "invalid decimal escape sequence '\\300'"}, get_error([["\300"]]) ) assert.same({line = 1, offset = 2, end_offset = 2, msg = "invalid escape sequence '\\'"}, get_error([["\]])) end) it("parses hexadecimal escape sequences correctly", function() assert.same({token = "string", token_value = "\0buffer exploit"}, get_token([["\x00buffer exploit"]])) assert.same({token = "string", token_value = "foo bar"}, get_token([["foo\x20bar"]])) assert.same({token = "string", token_value = "jj"}, get_token([["\x6a\x6A"]])) assert.same( {line = 1, offset = 2, end_offset = 3, msg = "invalid escape sequence '\\X'"}, get_error([["\XFF"]]) ) assert.same( {line = 1, offset = 2, end_offset = 4, msg = "invalid hexadecimal escape sequence '\\x\"'"}, get_error([["\x"]]) ) assert.same( {line = 1, offset = 2, end_offset = 5, msg = "invalid hexadecimal escape sequence '\\x1\"'"}, get_error([["\x1"]]) ) assert.same( {line = 1, offset = 2, end_offset = 4, msg = "invalid hexadecimal escape sequence '\\x1'"}, get_error([["\x1]]) ) assert.same( {line = 1, offset = 2, end_offset = 4, msg = "invalid hexadecimal escape sequence '\\xx'"}, get_error([["\xxx"]]) ) end) it("parses utf-8 escape sequences correctly", function() assert.same({token = "string", token_value = "\0\0"}, get_token([["\u{0}\u{00000000}"]])) assert.same({token = "string", token_value = "\0\127"}, get_token([["\u{0}\u{7F}"]])) assert.same({token = "string", token_value = "\194\128\223\191"}, get_token([["\u{80}\u{7fF}"]])) assert.same({token = "string", token_value = "\224\160\128\239\191\191"}, get_token([["\u{800}\u{FFFF}"]])) assert.same({token = "string", token_value = "\240\144\128\128\244\143\191\191"}, get_token([["\u{10000}\u{10FFFF}"]])) assert.same( {line = 1, offset = 2, end_offset = 13, msg = "invalid UTF-8 escape sequence '\\u{110000000'"}, get_error([["\u{110000000}"]]) ) assert.same( {line = 1, offset = 2, end_offset = 4, msg = "invalid UTF-8 escape sequence '\\u\"'"}, get_error([["\u"]]) ) assert.same( {line = 1, offset = 2, end_offset = 4, msg = "invalid UTF-8 escape sequence '\\un'"}, get_error([["\unrelated"]]) ) assert.same( {line = 1, offset = 2, end_offset = 7, msg = "invalid UTF-8 escape sequence '\\u{11u'"}, get_error([["\u{11unrelated"]]) ) assert.same( {line = 1, offset = 2, end_offset = 6, msg = "invalid UTF-8 escape sequence '\\u{11'"}, get_error([["\u{11]]) ) assert.same( {line = 1, offset = 2, end_offset = 5, msg = "invalid UTF-8 escape sequence '\\u{u'"}, get_error([["\u{unrelated}"]]) ) assert.same( {line = 1, offset = 2, end_offset = 4, msg = "invalid UTF-8 escape sequence '\\u{'"}, get_error([["\u{]]) ) end) it("detects unknown escape sequences", function() assert.same({line = 1, offset = 2, end_offset = 3, msg = "invalid escape sequence '\\c'"}, get_error([["\c"]])) end) it("detects unfinished strings", function() assert.same({line = 1, offset = 1, end_offset = 1, msg = "unfinished string"}, get_error([["]])) assert.same({line = 1, offset = 1, end_offset = 1, msg = "unfinished string"}, get_error([["']])) assert.same({line = 1, offset = 1, end_offset = 1, msg = "unfinished string"}, get_error([[" "]])) end) end) describe("when parsing long strings", function() it("parses empty long strings correctly", function() assert.same({token = "string", token_value = ""}, get_token("[[]]")) assert.same({token = "string", token_value = ""}, get_token("[===[]===]")) end) it("parses simple long strings correctly", function() assert.same({token = "string", token_value = "foo"}, get_token("[[foo]]")) assert.same({token = "string", token_value = "'foo'\n'bar'\n"}, get_token("[===['foo'\n'bar'\n]===]")) end) it("skips first newline", function() assert.same({token = "string", token_value = ""}, get_token("[[\n]]")) assert.same({token = "string", token_value = "\n"}, get_token("[===[\n\n]===]")) end) it("ignores closing brackets of unrelated length", function() assert.same({token = "string", token_value = "]=] "}, get_token("[[]=] ]]")) assert.same({token = "string", token_value = "foo]]\n]=== ]]"}, get_token("[===[foo]]\n]=== ]]]===]")) end) it("detects invalid opening brackets", function() assert.same({line = 1, offset = 1, end_offset = 1, msg = "invalid long string delimiter"}, get_error("[=")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "invalid long string delimiter"}, get_error("[=|")) end) it("detects unfinished long strings", function() assert.same({line = 1, offset = 1, end_offset = 1, msg = "unfinished long string"}, get_error("[=[\n")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "unfinished long string"}, get_error("[[]")) end) end) describe("when parsing numbers", function() it("parses decimal integers correctly", function() assert.same({token = "number", token_value = "0"}, get_token("0")) assert.same({token = "number", token_value = "123456789"}, get_token("123456789")) end) it("parses hexadecimal integers correctly", function() assert.same({token = "number", token_value = "0x0"}, get_token("0x0")) assert.same({token = "number", token_value = "0X0"}, get_token("0X0")) assert.same({token = "number", token_value = "0xFfab"}, get_token("0xFfab")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "malformed number"}, get_error("0x")) end) it("parses decimal floats correctly", function() assert.same({token = "number", token_value = "0.0"}, get_token("0.0")) assert.same({token = "number", token_value = "0."}, get_token("0.")) assert.same({token = "number", token_value = ".1234"}, get_token(".1234")) end) it("parses hexadecimal floats correctly", function() assert.same({token = "number", token_value = "0xf.A"}, get_token("0xf.A")) assert.same({token = "number", token_value = "0x9."}, get_token("0x9.")) assert.same({token = "number", token_value = "0x.b"}, get_token("0x.b")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "malformed number"}, get_error("0x.")) end) it("parses decimal floats with exponent correctly", function() assert.same({token = "number", token_value = "1.8e1"}, get_token("1.8e1")) assert.same({token = "number", token_value = ".8e-1"}, get_token(".8e-1")) assert.same({token = "number", token_value = "1.E+20"}, get_token("1.E+20")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "malformed number"}, get_error("1.8e")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "malformed number"}, get_error("1.8e-")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "malformed number"}, get_error("1.8E+")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "malformed number"}, get_error("1.8ee")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "malformed number"}, get_error("1.8e-e")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "malformed number"}, get_error("1.8E+i")) end) it("parses hexadecimal floats with exponent correctly", function() assert.same({token = "number", token_value = "0x1.8p1"}, get_token("0x1.8p1")) assert.same({token = "number", token_value = "0x.8P-1"}, get_token("0x.8P-1")) assert.same({token = "number", token_value = "0x1.p+20"}, get_token("0x1.p+20")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "malformed number"}, get_error("0x1.8p")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "malformed number"}, get_error("0x1.8p-")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "malformed number"}, get_error("0x1.8P+")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "malformed number"}, get_error("0x1.8pF")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "malformed number"}, get_error("0x1.8p-F")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "malformed number"}, get_error("0x1.8p+LL")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "malformed number"}, get_error("0x.p1")) end) it("parses 64 bits cdata literals correctly", function() assert.same({token = "number", token_value = "1LL"}, get_token("1LL")) assert.same({token = "number", token_value = "1ll"}, get_token("1ll")) assert.same({token = "number", token_value = "1Ll"}, get_token("1Ll")) assert.same({token = "number", token_value = "1lL"}, get_token("1lL")) assert.same({token = "number", token_value = "1ULL"}, get_token("1ULL")) assert.same({token = "number", token_value = "1uLl"}, get_token("1uLl")) assert.same({token = "number", token_value = "1LLu"}, get_token("1LLu")) assert.same({token = "number", token_value = "1"}, get_token("1L")) assert.same({token = "number", token_value = "1LL"}, get_token("1LLG")) assert.same({token = "number", token_value = "1"}, get_token("1LUL")) assert.same({token = "number", token_value = "0x1LL"}, get_token("0x1LL")) assert.same({token = "number", token_value = "1.0"}, get_token("1.0LL")) end) it("parses complex cdata literals correctly", function() assert.same({token = "number", token_value = "1i"}, get_token("1i")) assert.same({token = "number", token_value = "1I"}, get_token("1I")) assert.same({token = "number", token_value = "1"}, get_token("1j")) assert.same({token = "number", token_value = "1LL"}, get_token("1LLi")) assert.same({token = "number", token_value = "0x1i"}, get_token("0x1i")) assert.same({token = "number", token_value = "0x1.0i"}, get_token("0x1.0i")) end) end) it("parses short comments correctly", function() assert.same({token = "short_comment", token_value = ""}, get_token("--")) assert.same({token = "short_comment", token_value = "foo"}, get_token("--foo\nbar")) assert.same({token = "short_comment", token_value = "["}, get_token("--[")) assert.same({token = "short_comment", token_value = "[=foo"}, get_token("--[=foo\nbar")) end) it("parses long comments correctly", function() assert.same({token = "long_comment", token_value = ""}, get_token("--[[]]")) assert.same({token = "long_comment", token_value = ""}, get_token("--[[\n]]")) assert.same({token = "long_comment", token_value = "foo\nbar"}, get_token("--[[foo\nbar]]")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "unfinished long comment"}, get_error("--[=[]]")) end) it("provides correct location info", function() assert.same({ {token = "local", line = 1, offset = 1}, {token = "function", line = 1, offset = 7}, {token = "name", token_value = "foo", line = 1, offset = 16}, {token = "(", line = 1, offset = 19}, {token = "name", token_value = "bar", line = 1, offset = 20}, {token = ")", line = 1, offset = 23}, {token = "return", line = 2, offset = 28}, {token = "name", token_value = "bar", line = 2, offset = 35}, {token = ":", line = 2, offset = 38}, {token = "name", token_value = "get_foo", line = 2, offset = 39}, {token = "string", token_value = "long string\n", line = 2, offset = 46}, {token = "end", line = 5, offset = 66}, {token = "short_comment", token_value = " hello", line = 6, offset = 70}, {token = "name", token_value = "print", line = 7, offset = 79}, {token = "string", token_value = "123\n", line = 7, offset = 85}, {token = "short_comment", token_value = " this comment ends just before EOF", line = 10, offset = 113}, {token = "eof", line = 10, offset = 149} }, get_tokens([[ local function foo(bar) return bar:get_foo[=[ long string ]=] end -- hello print "1\z 2\z 3\n" -- this comment ends just before EOF]])) end) it("provides correct location info for errors", function() assert.same({line = 7, offset = 79, end_offset = 80, msg = "invalid escape sequence '\\g'"}, get_last_error([[ local function foo(bar) return bar:get_foo[=[ long string ]=] end print "1\g 2\z 3\n" ]])) assert.same({line = 8, offset = 89, end_offset = 92, msg = "invalid decimal escape sequence '\\300'"}, get_last_error([[ local function foo(bar) return bar:get_foo[=[ long string ]=] end print "1\ 2\300 3\n" ]])) assert.same({line = 8, offset = 79, end_offset = 79, msg = "malformed number"}, get_last_error([[ local function foo(bar) return bar:get_foo[=[ long string ]=] end print ( 0xx) ]])) assert.same({line = 7, offset = 77, end_offset = 77, msg = "unfinished string"}, get_last_error([[ local function foo(bar) return bar:get_foo[=[ long string ]=] end print "1\z 2\z 3\n ]])) end) it("parses minified source correctly", function() assert.same({ {token = "name", token_value = "a", line = 1, offset = 1}, {token = ",", line = 1, offset = 2}, {token = "name", token_value = "b", line = 1, offset = 3}, {token = "=", line = 1, offset = 4}, {token = "number", token_value = "4ll", line = 1, offset = 5}, {token = "name", token_value = "f", line = 1, offset = 8}, {token = "=", line = 1, offset = 9}, {token = "string", token_value = "", line = 1, offset = 10}, {token = "function", line = 1, offset = 12}, {token = "name", token_value = "_", line = 1, offset = 21}, {token = "(", line = 1, offset = 22}, {token = ")", line = 1, offset = 23}, {token = "return", line = 1, offset = 24}, {token = "number", token_value = "1", line = 1, offset = 31}, {token = "or", line = 1, offset = 32}, {token = "string", token_value = "", line = 1, offset = 34}, {token = "end", line = 1, offset = 36}, {token = "eof", line = 1, offset = 39} }, get_tokens("a,b=4llf=''function _()return 1or''end")) end) it("handles argparse sample", function() get_tokens(io.open("spec/samples/argparse-0.2.0.lua", "rb"):read("*a")) end) end) luacheck-0.25.0/spec/linearize_spec.lua000066400000000000000000000214251410451437000200110ustar00rootroot00000000000000local helper = require "spec.helper" local function get_line_or_throw(src) return helper.get_chstate_after_stage("linearize", src).top_line end local function get_line(src) local ok, res = pcall(get_line_or_throw, src) if ok or type(res) == "table" then return res else error(res, 0) end end local function item_to_string(item) if item.tag == "Jump" or item.tag == "Cjump" then return item.tag .. " -> " .. tostring(item.to) elseif item.tag == "Eval" then return "Eval " .. item.node.tag elseif item.tag == "Local" then local buf = {} for _, node in ipairs(item.lhs) do table.insert(buf, ("%s (%d..%d)"):format(node.var.name, node.var.scope_start, node.var.scope_end)) end return "Local " .. table.concat(buf, ", ") else return item.tag end end local function get_line_as_string(src) local line = get_line(src) local buf = {} for i, item in ipairs(line.items) do buf[i] = tostring(i) .. ": " .. item_to_string(item) end return table.concat(buf, "\n") end local function value_info_to_string(item) local buf = {} for var, value in pairs(item.set_variables) do table.insert(buf, ("%s (%s / %s%s%s%s)"):format( var.name, var.type, value.type, value.empty and ", empty" or "", value.secondaries and (", " .. tostring(#value.secondaries) .. " secondaries") or "", value.secondaries and value.secondaries.used and ", used" or "")) end table.sort(buf) return item.tag .. ": " .. table.concat(buf, ", ") end local function get_value_info_as_string(src) local line = get_line(src) local buf = {} for _, item in ipairs(line.items) do if item.tag == "Local" or item.tag == "Set" then assert.is_table(item.set_variables) table.insert(buf, value_info_to_string(item)) end end return table.concat(buf, "\n") end describe("linearize", function() describe("when handling post-parse syntax errors", function() it("detects gotos without labels", function() assert.same({line = 1, offset = 1, end_offset = 4, msg = "no visible label 'fail'"}, get_line("goto fail")) end) it("detects break outside loops", function() assert.same({line = 1, offset = 1, end_offset = 5, msg = "'break' is not inside a loop"}, get_line("break")) assert.same({line = 1, offset = 28, end_offset = 32, msg = "'break' is not inside a loop"}, get_line("while true do function f() break end end")) end) it("detects duplicate labels", function() assert.same({line = 2, offset = 10, end_offset = 17, prev_line = 1, prev_offset = 1, prev_end_offset = 8, msg = "label 'fail' already defined on line 1"}, get_line("::fail::\n::fail::")) end) it("detects varargs outside vararg functions", function() assert.same({line = 1, offset = 21, end_offset = 23, msg = "cannot use '...' outside a vararg function"}, get_line("function f() return ... end")) assert.same({line = 1, offset = 42, end_offset = 44, msg = "cannot use '...' outside a vararg function"}, get_line("function f(...) return function() return ... end end")) end) end) describe("when linearizing flow", function() it("linearizes empty source correctly", function() assert.equal("1: Local ... (2..1)", get_line_as_string("")) end) it("linearizes do-end blocks correctly", function() assert.equal([[ 1: Local ... (2..4) 2: Noop 3: Noop 4: Eval Call]], get_line_as_string([[ do end do print(foo) end]])) end) it("linearizes loops correctly", function() assert.equal([[ 1: Local ... (2..8) 2: Noop 3: Eval Id 4: Cjump -> 9 5: Local s (6..6) 6: Eval Call 7: Noop 8: Jump -> 3]], get_line_as_string([[ while cond do local s = io.read() print(s) end]])) assert.equal([[ 1: Local ... (2..6) 2: Noop 3: Local s (4..5) 4: Eval Call 5: Eval Id 6: Cjump -> 3]], get_line_as_string([[ repeat local s = io.read() print(s) until cond]])) assert.equal([[ 1: Local ... (2..9) 2: Noop 3: Eval Number 4: Eval Op 5: Cjump -> 10 6: Local i (7..7) 7: Eval Call 8: Noop 9: Jump -> 5]], get_line_as_string([[ for i = 1, #t do print(t[i]) end]])) assert.equal([[ 1: Local ... (2..8) 2: Noop 3: Eval Call 4: Cjump -> 9 5: Local k (6..6), v (6..6) 6: Eval Call 7: Noop 8: Jump -> 4]], get_line_as_string([[ for k, v in pairs(t) do print(k, v) end]])) end) it("linearizes loops with literal condition correctly", function() assert.equal([[ 1: Local ... (2..6) 2: Noop 3: Eval Number 4: Eval Call 5: Noop 6: Jump -> 3]], get_line_as_string([[ while 1 do foo() end]])) assert.equal([[ 1: Local ... (2..7) 2: Noop 3: Eval False 4: Jump -> 8 5: Eval Call 6: Noop 7: Jump -> 3]], get_line_as_string([[ while false do foo() end]])) assert.equal([[ 1: Local ... (2..4) 2: Noop 3: Eval Call 4: Eval True]], get_line_as_string([[ repeat foo() until true]])) assert.equal([[ 1: Local ... (2..5) 2: Noop 3: Eval Call 4: Eval Nil 5: Jump -> 3]], get_line_as_string([[ repeat foo() until nil]])) end) it("linearizes nested loops and breaks correctly", function() assert.equal([[ 1: Local ... (2..24) 2: Noop 3: Eval Call 4: Cjump -> 25 5: Eval Call 6: Noop 7: Eval Call 8: Cjump -> 18 9: Eval Call 10: Noop 11: Eval Call 12: Cjump -> 15 13: Jump -> 18 14: Jump -> 15 15: Eval Call 16: Noop 17: Jump -> 7 18: Noop 19: Eval Call 20: Cjump -> 23 21: Jump -> 25 22: Jump -> 23 23: Noop 24: Jump -> 3]], get_line_as_string([[ while cond() do stmts() while cond() do stmts() if cond() then break end stmts() end if cond() then break end end]])) end) it("linearizes if correctly", function() assert.equal([[ 1: Local ... (2..15) 2: Noop 3: Eval Call 4: Cjump -> 16 5: Noop 6: Eval Call 7: Cjump -> 10 8: Eval Call 9: Jump -> 15 10: Eval Call 11: Cjump -> 14 12: Eval Call 13: Jump -> 15 14: Eval Call 15: Jump -> 16]], get_line_as_string([[ if cond() then if cond() then stmts() elseif cond() then stmts() else stmts() end end]])) end) it("linearizes if with literal condition correctly", function() assert.equal([[ 1: Local ... (2..14) 2: Noop 3: Eval True 4: Noop 5: Eval Call 6: Cjump -> 9 7: Eval Call 8: Jump -> 14 9: Eval False 10: Jump -> 13 11: Eval Call 12: Jump -> 14 13: Eval Call 14: Jump -> 15]], get_line_as_string([[ if true then if cond() then stmts() elseif false then stmts() else stmts() end end]])) end) it("linearizes gotos correctly", function() assert.equal([[ 1: Local ... (2..13) 2: Eval Call 3: Noop 4: Jump -> 2 5: Eval Call 6: Noop 7: Jump -> 9 8: Eval Call 9: Eval Call 10: Noop 11: Noop 12: Jump -> 14 13: Eval Call]], get_line_as_string([[ ::label1:: stmts() goto label1 stmts() goto label2 stmts() ::label2:: stmts() do goto label2 stmts() ::label2:: end]])) end) end) describe("when registering values", function() it("registers values in empty chunk correctly", function() assert.equal([[ Local: ... (arg / arg)]], get_value_info_as_string("")) end) it("registers values in assignments correctly", function() assert.equal([[ Local: ... (arg / arg) Local: a (var / var) Set: a (var / var)]], get_value_info_as_string([[ local a = b a = d]])) end) it("registers empty values correctly", function() assert.equal([[ Local: ... (arg / arg) Local: a (var / var), b (var / var, empty) Set: a (var / var), b (var / var)]], get_value_info_as_string([[ local a, b = 4 a, b = 5]])) end) it("registers function values as of type func", function() assert.equal([[ Local: ... (arg / arg) Local: f (var / func)]], get_value_info_as_string([[ local function f() end]])) end) it("registers overwritten args and counters as of type var", function() assert.equal([[ Local: ... (arg / arg) Local: i (loopi / loopi) Set: i (loopi / var)]], get_value_info_as_string([[ for i = 1, 10 do i = 6 end]])) end) it("registers groups of secondary values", function() assert.equal([[ Local: ... (arg / arg) Local: a (var / var), b (var / var, 2 secondaries), c (var / var, 2 secondaries) Set: a (var / var), b (var / var, 2 secondaries), c (var / var, 2 secondaries)]], get_value_info_as_string([[ local a, b, c = f(), g() a, b, c = f(), g()]])) end) it("marks groups of secondary values used if one of values is put into global or index", function() assert.equal([[ Local: ... (arg / arg) Local: a (var / var, empty) Set: a (var / var, 1 secondaries, used)]], get_value_info_as_string([[ local a g, a = f()]])) end) end) end) luacheck-0.25.0/spec/luacheck_spec.lua000066400000000000000000000411161410451437000176050ustar00rootroot00000000000000local luacheck = require "luacheck" local function strip_locations(report) for _, file_report in ipairs(report) do for _, event in ipairs(file_report) do event.line = nil event.column = nil event.end_column = nil event.prev_line = nil event.prev_column = nil event.prev_end_column = nil end end return report end describe("luacheck", function() it("is an alias of luacheck.check_files", function() assert.same(luacheck.check_files({ "spec/samples/good_code.lua", "spec/samples/bad_code.lua", "spec/samples/python_code.lua" }), luacheck({ "spec/samples/good_code.lua", "spec/samples/bad_code.lua", "spec/samples/python_code.lua" })) end) it("panics on bad files", function() assert.has_error(function() luacheck("foo") end, "bad argument #1 to 'luacheck.check_files' (table expected, got string)") assert.has_error(function() luacheck({123}) end, "bad argument #1 to 'luacheck.check_files' (array of paths or file handles expected, got number)") end) it("panics on bad options", function() assert.has_error(function() luacheck({"foo"}, "bar") end, "bad argument #2 to 'luacheck.check_files' (option table expected, got string)") assert.has_error(function() luacheck({"foo"}, {globals = "bar"}) end, "bad argument #2 to 'luacheck.check_files' (invalid value of option 'globals': table expected, got string)") -- luacheck: no max line length assert.has_error(function() luacheck({"foo"}, {{unused = 123}}) end, "bad argument #2 to 'luacheck.check_files' (invalid options at index [1]: invalid value of option 'unused': boolean expected, got number)") assert.has_error(function() luacheck({"foo"}, {{{}, {unused = 123}}}) end, "bad argument #2 to 'luacheck.check_files' (invalid options at index [1][2]: invalid value of option 'unused': boolean expected, got number)") end) it("works on empty list", function() assert.same({ warnings = 0, errors = 0, fatals = 0 }, strip_locations(luacheck({}))) end) it("works on files", function() assert.same({ {}, { { code = "211", name = "helper", func = true }, { code = "212", name = "..." }, { code = "111", name = "embrace", top = true }, { code = "412", name = "opt" }, { code = "113", name = "hepler" } }, { { code = "011", msg = "expected '=' near '__future__'" } }, warnings = 5, errors = 1, fatals = 0 }, strip_locations(luacheck({ "spec/samples/good_code.lua", "spec/samples/bad_code.lua", "spec/samples/python_code.lua" }))) end) it("uses options", function() assert.same({ {}, { { code = "111", name = "embrace", top = true }, { code = "412", name = "opt" }, { code = "113", name = "hepler" } }, { { code = "011", msg = "expected '=' near '__future__'" } }, warnings = 3, errors = 1, fatals = 0 }, strip_locations(luacheck({ "spec/samples/good_code.lua", "spec/samples/bad_code.lua", "spec/samples/python_code.lua" }, { unused = false }))) end) it("uses option overrides", function() assert.same({ {}, { { code = "111", name = "embrace", top = true }, { code = "113", name = "hepler" } }, { { code = "011", msg = "expected '=' near '__future__'" } }, warnings = 2, errors = 1, fatals = 0 }, strip_locations(luacheck({ "spec/samples/good_code.lua", "spec/samples/bad_code.lua", "spec/samples/python_code.lua" }, { nil, { global = true, unused = false, redefined = false }, global = false }))) end) end) describe("check_strings", function() it("panics on bad strings", function() assert.has_error(function() luacheck.check_strings("foo") end, "bad argument #1 to 'luacheck.check_strings' (table expected, got string)") assert.has_error(function() luacheck.check_strings({1}) end, "bad argument #1 to 'luacheck.check_strings' (array of strings or tables expected, got number)") end) it("panics on bad options", function() assert.has_error(function() luacheck.check_strings({"foo"}, "bar") end, "bad argument #2 to 'luacheck.check_strings' (option table expected, got string)") assert.has_error(function() luacheck.check_strings({"foo"}, {globals = "bar"}) end, "bad argument #2 to 'luacheck.check_strings' (invalid value of option 'globals': table expected, got string)") -- luacheck: no max line length assert.has_error(function() luacheck.check_strings({"foo"}, {{unused = 123}}) end, "bad argument #2 to 'luacheck.check_strings' (invalid options at index [1]: invalid value of option 'unused': boolean expected, got number)") end) it("works on empty list", function() assert.same({ warnings = 0, errors = 0, fatals = 0 }, luacheck.check_strings({})) end) it("works on strings", function() assert.same({ { { code = "113", name = "foo" } }, { { code = "011", msg = "expected expression near 'return'" } }, warnings = 1, errors = 1, fatals = 0 }, strip_locations(luacheck.check_strings({"return foo", "return return"}))) end) it("supports comments in inline options", function() assert.same({ { { code = "211", name = "bar" } }, warnings = 1, errors = 0, fatals = 0 }, strip_locations(luacheck.check_strings({"local foo, bar -- luacheck: ignore foo (not bar though)"}))) end) it("provides correct location info for warnings", function() assert.same({ { { code = "521", label = "foo", line = 1, column = 1, end_column = 6 }, { code = "312", name = "self", line = 3, column = 11, end_column = 11, overwritten_line = 4, overwritten_column = 4, overwritten_end_column = 7 }, { code = "311", name = "self", line = 4, column = 4, end_column = 7, overwritten_line = 5, overwritten_column = 4, overwritten_end_column = 7 }, { code = "511", line = 9, column = 1, end_column = 5 } }, warnings = 4, errors = 0, fatals = 0 }, luacheck.check_strings({[[ :: foo ::local t = {} function t:m(x) self = x self = x return self end do return t end (t)() ]]})) end) it("provides correct location info for bad inline options", function() assert.same({ { { code = "022", line = 1, column = 1, end_column = 17 }, { code = "023", line = 3, column = 4, end_column = 19 }, { code = "021", msg = "unknown inline option 'some invalid comment'", line = 7, column = 3, end_column = 35 } }, warnings = 0, errors = 3, fatals = 0 }, luacheck.check_strings({[[ -- luacheck: push local function f() -- luacheck: pop end return f -- luacheck: some invalid comment ]]})) end) it("provides correct location info for syntax errors", function() assert.same({ { { code = "011", msg = "unfinished string", line = 1, column = 11, end_column = 11 } }, { { code = "011", msg = "invalid hexadecimal escape sequence '\\x2'", line = 1, column = 15, end_column = 17 } }, { { code = "011", msg = "expected 'then' near ", line = 1, column = 8, end_column = 8 } }, { { code = "011", msg = "label 'b' already defined on line 1", line = 1, column = 7, end_column = 11, prev_line = 1, prev_column = 1, prev_end_column = 5 } }, { { code = "011", msg = "cannot use '...' outside a vararg function", line = 1, column = 15, end_column = 17 } }, { { code = "011", msg = "'break' is not inside a loop", line = 1, column = 1, end_column = 5 } }, warnings = 0, errors = 6, fatals = 0 }, luacheck.check_strings({ [[local x = "foo]], [[local x = "foo\x2]], [[if true ]], [[::b:: ::b::]], [[function f() (...)() end]], [[break it()]] })) end) it("uses options", function() assert.same({ {}, { { code = "011", msg = "expected expression near 'return'" } }, warnings = 0, errors = 1, fatals = 0 }, strip_locations(luacheck.check_strings({"return foo", "return return"}, {ignore = {"113"}}))) end) it("ignores tables with .fatal field", function() assert.same({ { { code = "113", name = "foo" } }, { fatal = "I/O" }, warnings = 1, errors = 0, fatals = 1 }, strip_locations(luacheck.check_strings({"return foo", {fatal = "I/O"}}))) end) end) describe("get_report", function() it("panics on bad argument", function() assert.has_error(function() luacheck.get_report({}) end, "bad argument #1 to 'luacheck.get_report' (string expected, got table)") end) it("returns a table", function() assert.is_table(luacheck.get_report("return foo")) end) it("returns a table with single error event on syntax error", function() local report = strip_locations({luacheck.get_report("return return").warnings})[1] assert.same({code = "011", msg = "expected expression near 'return'"}, report[1]) end) end) describe("process_reports", function() it("panics on bad reports", function() assert.has_error(function() luacheck.process_reports("foo") end, "bad argument #1 to 'luacheck.process_reports' (table expected, got string)") end) it("panics on bad options", function() assert.has_error(function() luacheck.process_reports({{}}, "bar") end, "bad argument #2 to 'luacheck.process_reports' (option table expected, got string)") -- luacheck: no max line length assert.has_error(function() luacheck.process_reports({{}}, {globals = "bar"}) end, "bad argument #2 to 'luacheck.process_reports' (invalid value of option 'globals': table expected, got string)") assert.has_error(function() luacheck.process_reports({{}}, {{unused = 123}}) end, "bad argument #2 to 'luacheck.process_reports' (invalid options at index [1]: invalid value of option 'unused': boolean expected, got number)") end) it("processes reports", function() assert.same({ { { code = "113", name = "foo" } }, {}, warnings = 1, errors = 0, fatals = 0 }, strip_locations(luacheck.process_reports( {luacheck.get_report("return foo"), luacheck.get_report("return math")}))) end) it("uses options", function() assert.same({ { { code = "113", name = "foo" } }, { { code = "113", name = "math", indexing = {"floor"} } }, warnings = 2, errors = 0, fatals = 0 }, strip_locations(luacheck.process_reports( {luacheck.get_report("return foo"), luacheck.get_report("return math.floor")}, { std = "none" }))) end) end) describe("get_message", function() it("panics on bad events", function() assert.has_error(function() luacheck.get_message("foo") end, "bad argument #1 to 'luacheck.get_message' (table expected, got string)") end) it("returns message for an event", function() assert.equal("unused argument 'bar'", luacheck.get_message({ code = "212", name = "bar" })) assert.equal("shadowing definition of loop variable 'foo' on line 1", luacheck.get_message({ code = "423", name = "foo", line = 2, prev_line = 1 })) assert.equal("unused label 'fail'", luacheck.get_message({ code = "521", name = "unrelated", label = "fail" })) assert.equal("value assigned to field 'actual' is overwritten on line 2 before use", luacheck.get_message({ code = "314", name = "unrelated", field = "actual", overwritten_line = 2 })) assert.equal("value assigned to index '42' is overwritten on line 2 before use", luacheck.get_message({ code = "314", name = "11037", field = "42", index = true, overwritten_line = 2 })) assert.equal("message goes here", luacheck.get_message({ code = "011", msg = "message goes here" })) assert.equal("unexpected character near '%'", luacheck.get_message({ code = "011", msg = "unexpected character near '%'" })) assert.equal("unused recursive function 'hello'", luacheck.get_message({ code = "211", name = "hello", func = true, recursive = true })) assert.equal("unused mutually recursive function 'hallo'", luacheck.get_message({ code = "211", name = "hallo", func = true, mutually_recursive = true })) assert.equal("cyclomatic complexity of main chunk is too high (yes > please no)", luacheck.get_message({ code = "561", function_type = "main_chunk", complexity = "yes", max_complexity = "please no" })) assert.equal("cyclomatic complexity of function is too high (10 > 1)", luacheck.get_message({ code = "561", function_type = "function", complexity = 10, max_complexity = 1 })) assert.equal("cyclomatic complexity of function '>>=' is too high (10 > 1)", luacheck.get_message({ code = "561", function_type = "function", function_name = ">>=", complexity = 10, max_complexity = 1 })) assert.equal("cyclomatic complexity of method 'foo.bar.baz' is too high (1000 > 10)", luacheck.get_message({ code = "561", function_type = "method", function_name = "foo.bar.baz", complexity = 1000, max_complexity = 10 })) end) end) luacheck-0.25.0/spec/options_spec.lua000066400000000000000000000166341410451437000175300ustar00rootroot00000000000000local options = require "luacheck.options" describe("options", function() describe("validate", function() it("returns true if options are empty", function() assert.is_true(options.validate(options.all_options)) end) it("returns true if options are valid", function() assert.is_true(options.validate(options.all_options, { globals = {"foo"}, compat = false, unrelated = function() end })) end) it("returns false and an error message if options are invalid", function() local ok, err = options.validate(options.all_options, { globals = 3, redefined = false }) assert.is_false(ok) assert.equal("invalid value of option 'globals': table expected, got number", err) ok, err = options.validate(options.all_options, { globals = {3} }) assert.is_false(ok) assert.equal( "invalid value of option 'globals': in field [1]: string expected as global name, got number", err) ok, err = options.validate(options.all_options, function() end) assert.is_false(ok) assert.equal("option table expected, got function", err) ok, err = options.validate(options.all_options, { unused = 0 }) assert.is_false(ok) assert.equal("invalid value of option 'unused': boolean expected, got number", err) ok, err = options.validate(options.all_options, { max_line_length = true, redefined = false }) assert.is_false(ok) assert.equal("invalid value of option 'max_line_length': number or false expected, got true", err) ok, err = options.validate(options.all_options, { max_line_length = "foo", redefined = false }) assert.is_false(ok) assert.equal("invalid value of option 'max_line_length': number or false expected, got string", err) ok, err = options.validate(options.all_options, { std = "+lua51+luaaot", redefined = false }) assert.is_false(ok) assert.equal("invalid value of option 'std': unknown std 'luaaot'", err) ok, err = options.validate(options.all_options, { std = {read_globals = {1}}, redefined = false }) assert.is_false(ok) assert.equal( "invalid value of option 'std': in field .read_globals[1]: string expected as global name, got number", err) ok, err = options.validate(options.all_options, { std = 123, redefined = false }) assert.is_false(ok) assert.equal("invalid value of option 'std': string or table expected, got number", err) end) end) describe("normalize", function() it("applies default values", function() local opts = options.normalize({}) assert.same(opts, options.normalize({{}})) assert.is_true(opts.unused_secondaries) assert.is_false(opts.module) assert.is_false(opts.allow_defined) assert.is_false(opts.allow_defined_top) assert.is_table(opts.std) assert.same({}, opts.rules) end) it("considers simple boolean options", function() local opts = options.normalize({ { module = false, unused_secondaries = true }, { module = true, allow_defined = false } }) assert.is_true(opts.module) assert.is_true(opts.unused_secondaries) assert.is_false(opts.allow_defined) end) it("considers opts.std and opts.compat", function() assert.same({fields = { baz = {read_only = false, other_fields = true} }}, options.normalize({ { std = "none" }, { globals = {"foo", "bar"}, compat = true }, { new_globals = {"baz"}, compat = false } }).std) end) it("allows compound std unions", function() assert.same(options.normalize({ { std = "max" }, }).globals, options.normalize({ { std = "lua51+lua52+lua53+luajit" }, }).globals) end) it("allows std addition", function() assert.same(options.normalize({ { std = "lua52 + busted" }, }).globals, options.normalize({ { std = "max" }, { std = "none" }, { std = "+lua52+busted" } }).globals) end) it("considers read-only and regular globals", function() local opts = options.normalize({ { std = "lua52", globals = {"foo", "bar", "removed"}, read_globals = {"baz"} }, { new_read_globals = {"quux"}, not_globals = {"removed", "unrelated", "print"} } }) local std = opts.std assert.is_table(std) assert.is_table(std.fields) assert.is_same({read_only = false, other_fields = true}, std.fields.foo) assert.is_same({read_only = false, other_fields = true}, std.fields.bar) assert.is_nil(std.fields.baz) assert.is_same({read_only = true, deep_read_only = true, other_fields = true}, std.fields.quux) assert.is_table(std.fields.string) assert.is_true(std.fields.string.deep_read_only) assert.is_nil(std.fields.string.other_fields) end) it("considers read-only and regular field definitions", function() local opts = options.normalize({ { std = "none", globals = {"foo", "bar.nested", "baz.nested.deeply"}, read_globals = {"bar", "foo.nested"} }, { not_globals = {"baz.nested", "unrelated.field"} } }) assert.same({ fields = { foo = { read_only = false, other_fields = true, fields = { nested = {deep_read_only = true, read_only = true, other_fields = true} } }, bar = { read_only = true, other_fields = true, fields = { nested = {read_only = false, other_fields = true} } }, baz = {deep_read_only = true, read_only = false, fields = {}} } }, opts.std) end) it("considers macros, ignore, enable and only", function() assert.same({ {{{nil, "^foo$"}}, "only"}, {{{"^21[23]", nil}}, "disable"}, {{{"^[23]", nil}}, "enable"}, {{{"^511", nil}}, "enable"}, {{{"^412", nil}, {"1$", "^bar$"}}, "disable"} }, options.normalize({ { unused = false }, { ignore = {"412", "1$/bar"} }, { unused = true, unused_args = false, enable = {"511"} }, { only = {"foo"} } }).rules) end) end) end) luacheck-0.25.0/spec/parser_spec.lua000066400000000000000000001603741410451437000173320ustar00rootroot00000000000000local decoder = require "luacheck.decoder" local parser = require "luacheck.parser" local function strip_locations(node) node.line = nil node.offset = nil node.end_offset = nil node.end_range = nil for _, sub_node in ipairs(node) do if type(sub_node) == "table" then strip_locations(sub_node) end end end local function get_all(src_bytes) return parser.parse(decoder.decode(src_bytes)) end local function get_ast(src) local ast = get_all(src) assert.is_table(ast) strip_locations(ast) return ast end local function get_node(src) return get_ast(src)[1] end local function get_expr(src) return get_node("return " .. src)[1] end local function get_comments(src) return (select(2, get_all(src))) end local function get_code_lines(src) return select(3, get_all(src)) end local function get_line_endings(src) return select(4, get_all(src)) end local function get_error(src) local ok, err = pcall(get_all, src) assert.is_false(ok) return err end describe("parser", function() it("parses empty source correctly", function() assert.same({}, get_ast(" ")) end) it("does not allow extra ending keywords", function() assert.same({line = 1, offset = 1, end_offset = 3, msg = "expected near 'end'"}, get_error("end")) end) it("parses return statement correctly", function() assert.same({tag = "Return"}, get_node("return")) assert.same({tag = "Return", {tag = "Number", "1"} }, get_node("return 1")) assert.same({tag = "Return", {tag = "Number", "1"}, {tag = "String", "foo"} }, get_node("return 1, 'foo'")) assert.same( {line = 1, offset = 10, end_offset = 10, msg = "expected expression near "}, get_error("return 1,") ) end) it("parses labels correctly", function() assert.same({tag = "Label", "fail"}, get_node("::fail::")) assert.same({tag = "Label", "fail"}, get_node("::\nfail\n::")) assert.same({line = 1, offset = 3, end_offset = 4, msg = "expected identifier near '::'"}, get_error("::::")) assert.same({line = 1, offset = 3, end_offset = 3, msg = "expected identifier near '1'"}, get_error("::1::")) end) it("parses goto correctly", function() assert.same({tag = "Goto", "fail"}, get_node("goto fail")) assert.same({line = 1, offset = 5, end_offset = 5, msg = "expected identifier near "}, get_error("goto")) assert.same( {line = 1, offset = 9, end_offset = 9, msg = "expected statement near ','"}, get_error("goto foo, bar") ) end) it("parses break correctly", function() assert.same({tag = "Break"}, get_node("break")) assert.same({line = 1, offset = 11, end_offset = 11, msg = "expected '=' near "}, get_error("break fail")) end) it("parses do end correctly", function() assert.same({tag = "Do"}, get_node("do end")) assert.same({line = 1, offset = 3, end_offset = 3, prev_line = 1, prev_offset = 1, prev_end_offset = 2, msg = "expected 'end' near "}, get_error("do")) assert.same({line = 1, offset = 4, end_offset = 8, prev_line = 1, prev_offset = 1, prev_end_offset = 2, msg = "expected 'end' near 'until'"}, get_error("do until false") ) assert.same({line = 2, offset = 4, end_offset = 8, prev_line = 1, prev_offset = 1, prev_end_offset = 2, msg = "expected 'end' (to close 'do' on line 1) near 'until'"}, get_error("do\nuntil false") ) end) it("parses while do end correctly", function() assert.same({tag = "While", {tag = "True"}, {} }, get_node("while true do end")) assert.same({line = 1, offset = 6, end_offset = 6, msg = "expected condition near "}, get_error("while")) assert.same({line = 1, offset = 11, end_offset = 11, msg = "expected 'do' near "}, get_error("while true")) assert.same({line = 1, offset = 14, end_offset = 14, prev_line = 1, prev_offset = 1, prev_end_offset = 5, msg = "expected 'end' near "}, get_error("while true do") ) assert.same({line = 2, offset = 14, end_offset = 14, prev_line = 1, prev_offset = 1, prev_end_offset = 5, msg = "expected 'end' (to close 'while' on line 1) near "}, get_error("while true\ndo") ) assert.same( {line = 1, offset = 7, end_offset = 8, msg = "expected condition near 'do'"}, get_error("while do end") ) assert.same( {line = 1, offset = 11, end_offset = 11, msg = "expected 'do' near ','"}, get_error("while true, false do end") ) end) it("parses repeat until correctly", function() assert.same({tag = "Repeat", {}, {tag = "True"} }, get_node("repeat until true")) assert.same({line = 1, offset = 7, end_offset = 7, prev_line = 1, prev_offset = 1, prev_end_offset = 6, msg = "expected 'until' near "}, get_error("repeat")) assert.same({line = 2, offset = 10, end_offset = 10, prev_line = 1, prev_offset = 1, prev_end_offset = 6, msg = "expected 'until' (to close 'repeat' on line 1) near "}, get_error("repeat\n--") ) assert.same( {line = 1, offset = 13, end_offset = 13, msg = "expected condition near "}, get_error("repeat until") ) assert.same( {line = 1, offset = 18, end_offset = 18, msg = "expected statement near ','"}, get_error("repeat until true, false") ) end) describe("when parsing if", function() it("parses if then end correctly", function() assert.same({tag = "If", {tag = "True"}, {} }, get_node("if true then end")) assert.same({line = 1, offset = 3, end_offset = 3, msg = "expected condition near "}, get_error("if")) assert.same({line = 1, offset = 8, end_offset = 8, msg = "expected 'then' near "}, get_error("if true")) assert.same({line = 1, offset = 13, end_offset = 13, prev_line = 1, prev_offset = 1, prev_end_offset = 2, msg = "expected 'end' near "}, get_error("if true then") ) assert.same({line = 2, offset = 13, end_offset = 13, prev_line = 1, prev_offset = 1, prev_end_offset = 2, msg = "expected 'end' (to close 'if' on line 1) near "}, get_error("if true\nthen") ) assert.same( {line = 1, offset = 4, end_offset = 7, msg = "expected condition near 'then'"}, get_error("if then end") ) assert.same( {line = 1, offset = 8, end_offset = 8, msg = "expected 'then' near ','"}, get_error("if true, false then end") ) end) it("parses if then else end correctly", function() assert.same({tag = "If", {tag = "True"}, {}, {} }, get_node("if true then else end")) assert.same({line = 1, offset = 18, end_offset = 18, prev_line = 1, prev_offset = 14, prev_end_offset = 17, msg = "expected 'end' near "}, get_error("if true then else") ) assert.same({line = 3, offset = 19, end_offset = 19, prev_line = 2, prev_offset = 14, prev_end_offset = 17, msg = "expected 'end' (to close 'else' on line 2) near "}, get_error("if true\nthen else\n") ) assert.same({line = 1, offset = 19, end_offset = 22, prev_line = 1, prev_offset = 14, prev_end_offset = 17, msg = "expected 'end' near 'else'"}, get_error("if true then else else end") ) end) it("parses if then elseif then end correctly", function() assert.same({tag = "If", {tag = "True"}, {}, {tag = "False"}, {} }, get_node("if true then elseif false then end")) assert.same( {line = 1, offset = 21, end_offset = 23, msg = "expected condition near 'end'"}, get_error("if true then elseif end") ) assert.same( {line = 1, offset = 21, end_offset = 24, msg = "expected condition near 'then'"}, get_error("if true then elseif then end") ) assert.same({line = 2, offset = 27, end_offset = 27, prev_line = 1, prev_offset = 14, prev_end_offset = 19, msg = "expected 'end' (to close 'elseif' on line 1) near "}, get_error("if true then elseif a\nthen") ) end) it("parses if then elseif then else end correctly", function() assert.same({tag = "If", {tag = "True"}, {}, {tag = "False"}, {}, {} }, get_node("if true then elseif false then else end")) assert.same({line = 1, offset = 36, end_offset = 36, prev_line = 1, prev_offset = 32, prev_end_offset = 35, msg = "expected 'end' near "}, get_error("if true then elseif false then else") ) end) end) describe("when parsing for", function() it("parses fornum correctly", function() assert.same({tag = "Fornum", {tag = "Id", "i"}, {tag = "Number", "1"}, {tag = "Op", "len", {tag = "Id", "t"}}, {} }, get_node("for i=1, #t do end")) assert.same( {line = 1, offset = 4, end_offset = 4, msg = "expected identifier near "}, get_error("for") ) assert.same( {line = 1, offset = 6, end_offset = 6, msg = "expected '=', ',' or 'in' near "}, get_error("for i") ) assert.same( {line = 1, offset = 7, end_offset = 8, msg = "expected '=', ',' or 'in' near '~='"}, get_error("for i ~= 2") ) assert.same( {line = 1, offset = 11, end_offset = 12, msg = "expected ',' near 'do'"}, get_error("for i = 2 do end") ) assert.same({line = 1, offset = 15, end_offset = 15, prev_line = 1, prev_offset = 1, prev_end_offset = 3, msg = "expected 'end' near "}, get_error("for i=1, #t do") ) assert.same({line = 2, offset = 16, end_offset = 16, prev_line = 1, prev_offset = 1, prev_end_offset = 3, msg = "expected 'end' (to close 'for' on line 1) near 'a' (indentation-based guess)"}, get_error("for i=1, #t do\na()") ) assert.same( {line = 1, offset = 5, end_offset = 5, msg = "expected identifier near '('"}, get_error("for (i)=1, #t do end") ) assert.same( {line = 1, offset = 5, end_offset = 5, msg = "expected identifier near '3'"}, get_error("for 3=1, #t do end") ) end) it("parses fornum with step correctly", function() assert.same({tag = "Fornum", {tag = "Id", "i"}, {tag = "Number", "1"}, {tag = "Op", "len", {tag = "Id", "t"}}, {tag = "Number", "2"}, {} }, get_node("for i=1, #t, 2 do end")) assert.same( {line = 1, offset = 15, end_offset = 15, msg = "expected 'do' near ','"}, get_error("for i=1, #t, 2, 3 do") ) end) it("parses forin correctly", function() assert.same({tag = "Forin", { {tag = "Id", "i"} }, { {tag = "Id", "t"} }, {} }, get_node("for i in t do end")) assert.same({tag = "Forin", { {tag = "Id", "i"}, {tag = "Id", "j"} }, { {tag = "Id", "t"}, {tag = "String", "foo"} }, {} }, get_node("for i, j in t, 'foo' do end")) assert.same( {line = 1, offset = 5, end_offset = 6, msg = "expected identifier near 'in'"}, get_error("for in foo do end") ) assert.same( {line = 1, offset = 10, end_offset = 11, msg = "expected expression near 'do'"}, get_error("for i in do end") ) end) end) describe("when parsing functions", function() it("parses simple function correctly", function() assert.same({tag = "Set", { {tag = "Id", "a"} }, { {tag = "Function", {}, {}} } }, get_node("function a() end")) assert.same( {line = 1, offset = 9, end_offset = 9, msg = "expected identifier near "}, get_error("function") ) assert.same( {line = 1, offset = 11, end_offset = 11, msg = "expected '(' near "}, get_error("function a") ) assert.same( {line = 1, offset = 12, end_offset = 12, msg = "expected argument near "}, get_error("function a(") ) assert.same({line = 1, offset = 13, end_offset = 13, prev_line = 1, prev_offset = 1, prev_end_offset = 8, msg = "expected 'end' near "}, get_error("function a()") ) assert.same({line = 2, offset = 14, end_offset = 14, prev_line = 1, prev_offset = 1, prev_end_offset = 8, msg = "expected 'end' (to close 'function' on line 1) near "}, get_error("function a(\n)") ) assert.same( {line = 1, offset = 10, end_offset = 10, msg = "expected identifier near '('"}, get_error("function (a)()") ) assert.same( {line = 1, offset = 9, end_offset = 9, msg = "expected identifier near '('"}, get_error("function() end") ) assert.same( {line = 1, offset = 11, end_offset = 11, msg = "expected '(' near 'a'"}, get_error("(function a() end)") ) assert.same( {line = 1, offset = 18, end_offset = 18, msg = "expected expression near ')'"}, get_error("function a() end()") ) end) it("parses simple function with arguments correctly", function() assert.same({tag = "Set", { {tag = "Id", "a"} }, { {tag = "Function", {{tag = "Id", "b"}}, {}} } }, get_node("function a(b) end")) assert.same({tag = "Set", { {tag = "Id", "a"} }, { {tag = "Function", {{tag = "Id", "b"}, {tag = "Id", "c"}}, {}} } }, get_node("function a(b, c) end")) assert.same({tag = "Set", { {tag = "Id", "a"} }, { {tag = "Function", {{tag = "Id", "b"}, {tag = "Dots", "..."}}, {}} } }, get_node("function a(b, ...) end")) assert.same( {line = 1, offset = 15, end_offset = 15, msg = "expected argument near ')'"}, get_error("function a(b, ) end") ) assert.same({line = 1, offset = 13, end_offset = 13, prev_line = 1, prev_offset = 11, prev_end_offset = 11, msg = "expected ')' near '.'"}, get_error("function a(b.c) end") ) assert.same({line = 2, offset = 14, end_offset = 14, prev_line = 1, prev_offset = 11, prev_end_offset = 11, msg = "expected ')' (to close '(' on line 1) near '.'"}, get_error("function a(\nb.c) end") ) assert.same( {line = 1, offset = 12, end_offset = 12, msg = "expected argument near '('"}, get_error("function a((b)) end") ) assert.same({line = 1, offset = 15, end_offset = 15, prev_line = 1, prev_offset = 11, prev_end_offset = 11, msg = "expected ')' near ','"}, get_error("function a(..., ...) end") ) end) it("parses field function correctly", function() assert.same({tag = "Set", { {tag = "Index", {tag = "Id", "a"}, {tag = "String", "b"}} }, { {tag = "Function", {}, {}} } }, get_node("function a.b() end")) assert.same({tag = "Set", { {tag = "Index", {tag = "Index", {tag = "Id", "a"}, {tag = "String", "b"}}, {tag = "String", "c"} } }, { {tag = "Function", {}, {}} } }, get_node("function a.b.c() end")) assert.same( {line = 1, offset = 11, end_offset = 11, msg = "expected '(' near '['"}, get_error("function a[b]() end") ) assert.same( {line = 1, offset = 12, end_offset = 12, msg = "expected identifier near '('"}, get_error("function a.() end") ) end) it("parses method function correctly", function() assert.same({tag = "Set", { {tag = "Index", {tag = "Id", "a"}, {tag = "String", "b"}} }, { {tag = "Function", {{tag = "Id", "self", implicit = true}}, {}} } }, get_node("function a:b() end")) assert.same({tag = "Set", { {tag = "Index", {tag = "Index", {tag = "Id", "a"}, {tag = "String", "b"}}, {tag = "String", "c"} } }, { {tag = "Function", {{tag = "Id", "self", implicit = true}}, {}} } }, get_node("function a.b:c() end")) assert.same( {line = 1, offset = 13, end_offset = 13, msg = "expected '(' near '.'"}, get_error("function a:b.c() end") ) end) end) describe("when parsing local declarations", function() it("parses simple local declaration correctly", function() assert.same({tag = "Local", { {tag = "Id", "a"} } }, get_node("local a")) assert.same({tag = "Local", { {tag = "Id", "a"}, {tag = "Id", "b"} } }, get_node("local a, b")) assert.same( {line = 1, offset = 6, end_offset = 6, msg = "expected identifier near "}, get_error("local") ) assert.same( {line = 1, offset = 9, end_offset = 9, msg = "expected identifier near "}, get_error("local a,") ) assert.same( {line = 1, offset = 8, end_offset = 8, msg = "expected statement near '.'"}, get_error("local a.b") ) assert.same( {line = 1, offset = 8, end_offset = 8, msg = "expected statement near '['"}, get_error("local a[b]") ) assert.same( {line = 1, offset = 7, end_offset = 7, msg = "expected identifier near '('"}, get_error("local (a)") ) end) it("accepts (and ignores for now) Lua 5.4 attributes", function() assert.same({tag = "Local", { {tag = "Id", "a"} } }, get_node("local a ")) assert.same({tag = "Local", { {tag = "Id", "a"}, {tag = "Id", "b"} } }, get_node("local a , b ")) assert.same({ tag = "Local", { {tag = "Id", "a"} }, { {tag = "Id", "b"} } }, get_node("local a = b")) assert.same({ tag = "Local", { {tag = "Id", "a"}, {tag = "Id", "b"} }, { {tag = "Id", "c"}, {tag = "Id", "d"} } }, get_node("local a , b = c, d")) assert.same( {line = 1, offset = 16, end_offset = 16, msg = "expected '>' near '='"}, get_error("local a "}, get_error("local a = b,") ) assert.same( {line = 1, offset = 8, end_offset = 8, msg = "expected statement near '.'"}, get_error("local a.b = c") ) assert.same( {line = 1, offset = 8, end_offset = 8, msg = "expected statement near '['"}, get_error("local a[b] = c") ) assert.same( {line = 1, offset = 10, end_offset = 10, msg = "expected identifier near '('"}, get_error("local a, (b) = c") ) end) it("parses local function declaration correctly", function() assert.same({ tag = "Localrec", {{tag = "Id", "a"}}, {{tag = "Function", {}, {}}} }, get_node("local function a() end")) assert.same( {line = 1, offset = 15, end_offset = 15, msg = "expected identifier near "}, get_error("local function") ) assert.same( {line = 1, offset = 17, end_offset = 17, msg = "expected '(' near '.'"}, get_error("local function a.b() end") ) end) end) describe("when parsing assignments", function() it("parses single target assignment correctly", function() assert.same({ tag = "Set", { {tag = "Id", "a"} }, { {tag = "Id", "b"} } }, get_node("a = b")) assert.same({ tag = "Set", { {tag = "Index", {tag = "Id", "a"}, {tag = "String", "b"}} }, { {tag = "Id", "c"} } }, get_node("a.b = c")) assert.same({ tag = "Set", { {tag = "Index", {tag = "Index", {tag = "Id", "a"}, {tag = "String", "b"}}, {tag = "String", "c"} } }, { {tag = "Id", "d"} } }, get_node("a.b.c = d")) assert.same({ tag = "Set", { {tag = "Index", {tag = "Paren", {tag = "Invoke", {tag = "Call", {tag = "Id", "f"}}, {tag = "String", "g"} } }, {tag = "Number", "9"} } }, { {tag = "Id", "d"} } }, get_node("(f():g())[9] = d")) assert.same({line = 1, offset = 2, end_offset = 2, msg = "expected '=' near "}, get_error("a")) assert.same({line = 1, offset = 5, end_offset = 5, msg = "expected expression near "}, get_error("a = ")) assert.same({line = 1, offset = 5, end_offset = 5, msg = "expected statement near '='"}, get_error("a() = b")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "expected statement near '('"}, get_error("(a) = b")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "expected statement near '1'"}, get_error("1 = b")) end) it("parses multi assignment correctly", function() assert.same({ tag = "Set", { {tag = "Id", "a"}, {tag = "Id", "b"} }, { {tag = "Id", "c"}, {tag = "Id", "d"} } }, get_node("a, b = c, d")) assert.same( {line = 1, offset = 5, end_offset = 5, msg = "expected '=' near "}, get_error("a, b") ) assert.same( {line = 1, offset = 4, end_offset = 4, msg = "expected identifier or field near '='"}, get_error("a, = b") ) assert.same( {line = 1, offset = 8, end_offset = 8, msg = "expected expression near "}, get_error("a, b = ") ) assert.same( {line = 1, offset = 10, end_offset = 10, msg = "expected expression near "}, get_error("a, b = c,") ) assert.same( {line = 1, offset = 8, end_offset = 8, msg = "expected call or indexing near '='"}, get_error("a, b() = c") ) assert.same( {line = 1, offset = 4, end_offset = 4, msg = "expected identifier or field near '('"}, get_error("a, (b) = c") ) end) end) describe("when parsing expression statements", function() it("parses calls correctly", function() assert.same({ tag = "Call", {tag = "Id", "a"} }, get_node("a()")) assert.same({ tag = "Call", {tag = "Id", "a"}, {tag = "String", "b"} }, get_node("a'b'")) assert.same({ tag = "Call", {tag = "Id", "a"}, {tag = "Table"} }, get_node("a{}")) assert.same({tag = "Call", {tag = "Id", "a"}, {tag = "Id", "b"} }, get_node("a(b)")) assert.same({tag = "Call", {tag = "Id", "a"}, {tag = "Id", "b"}, {tag = "Id", "c"} }, get_node("a(b, c)")) assert.same({tag = "Call", {tag = "Paren", {tag = "Id", "a"}}, {tag = "Id", "b"} }, get_node("(a)(b)")) assert.same({tag = "Call", {tag = "Call", {tag = "Paren", {tag = "Id", "a"}}, {tag = "Id", "b"} } }, get_node("(a)(b)()")) assert.same({line = 1, offset = 2, end_offset = 2, msg = "expected expression near ')'"}, get_error("()()")) assert.same({line = 1, offset = 3, end_offset = 3, msg = "expected expression near "}, get_error("a(")) assert.same({line = 1, offset = 4, end_offset = 4, prev_line = 1, prev_offset = 2, prev_end_offset = 2, msg = "expected ')' near "}, get_error("a(b")) assert.same({line = 2, offset = 5, end_offset = 5, prev_line = 1, prev_offset = 2, prev_end_offset = 2, msg = "expected ')' (to close '(' on line 1) near "}, get_error("a(\nb")) assert.same({line = 2, offset = 4, end_offset = 5, prev_line = 1, prev_offset = 1, prev_end_offset = 1, msg = "expected ')' (to close '(' on line 1) near 'cc'"}, get_error("(a\ncc")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "expected statement near '1'"}, get_error("1()")) assert.same({line = 1, offset = 1, end_offset = 5, msg = "expected statement near ''foo''"}, get_error("'foo'()")) assert.same({line = 1, offset = 9, end_offset = 9, msg = "expected identifier near '('"}, get_error("function() end ()")) end) it("parses method calls correctly", function() assert.same({tag = "Invoke", {tag = "Id", "a"}, {tag = "String", "b"} }, get_node("a:b()")) assert.same({tag = "Invoke", {tag = "Id", "a"}, {tag = "String", "b"}, {tag = "String", "c"} }, get_node("a:b'c'")) assert.same({tag = "Invoke", {tag = "Id", "a"}, {tag = "String", "b"}, {tag = "Table"} }, get_node("a:b{}")) assert.same({tag = "Invoke", {tag = "Id", "a"}, {tag = "String", "b"}, {tag = "Id", "c"} }, get_node("a:b(c)")) assert.same({tag = "Invoke", {tag = "Id", "a"}, {tag = "String", "b"}, {tag = "Id", "c"}, {tag = "Id", "d"} }, get_node("a:b(c, d)")) assert.same({tag = "Invoke", {tag = "Paren", {tag = "Id", "a"}}, {tag = "String", "b"}, {tag = "Id", "c"} }, get_node("(a):b(c)")) assert.same({tag = "Invoke", {tag = "Invoke", {tag = "Id", "a"}, {tag = "String", "b"} }, {tag = "String", "c"} }, get_node("a:b():c()")) assert.same({line = 1, offset = 1, end_offset = 1, msg = "expected statement near '1'"}, get_error("1:b()")) assert.same({line = 1, offset = 1, end_offset = 2, msg = "expected statement near ''''"}, get_error("'':a()")) assert.same({line = 1, offset = 9, end_offset = 9, msg = "expected identifier near '('"}, get_error("function()end:b()")) assert.same({line = 1, offset = 4, end_offset = 4, msg = "expected method arguments near ':'"}, get_error("a:b:c()")) assert.same({line = 1, offset = 3, end_offset = 3, msg = "expected identifier near "}, get_error("a:")) end) end) describe("when parsing expressions", function() it("parses singleton expressions correctly", function() assert.same({tag = "Nil"}, get_expr("nil")) assert.same({tag = "True"}, get_expr("true")) assert.same({tag = "False"}, get_expr("false")) assert.same({tag = "Number", "1"}, get_expr("1")) assert.same({tag = "String", "1"}, get_expr("'1'")) assert.same({tag = "Table"}, get_expr("{}")) assert.same({tag = "Function", {}, {}}, get_expr("function() end")) assert.same({tag = "Dots", "..."}, get_expr("...")) end) it("parses table constructors correctly", function() assert.same({tag = "Table", {tag = "Id", "a"}, {tag = "Id", "b"}, {tag = "Id", "c"} }, get_expr("{a, b, c}")) assert.same({tag = "Table", {tag = "Id", "a"}, {tag = "Pair", {tag = "String", "b"}, {tag = "Id", "c"}}, {tag = "Id", "d"} }, get_expr("{a, b = c, d}")) assert.same({tag = "Table", {tag = "String", "a"}, {tag = "Pair", {tag = "Id", "b"}, {tag = "Id", "c"}}, {tag = "Id", "d"} }, get_expr("{[[a]], [b] = c, d}")) assert.same({tag = "Table", {tag = "Id", "a"}, {tag = "Id", "b"}, {tag = "Id", "c"} }, get_expr("{a; b, c}")) assert.same({tag = "Table", {tag = "Id", "a"}, {tag = "Id", "b"}, {tag = "Id", "c"} }, get_expr("{a; b, c,}")) assert.same({tag = "Table", {tag = "Id", "a"}, {tag = "Id", "b"}, {tag = "Id", "c"} }, get_expr("{a; b, c;}")) assert.same({line = 1, offset = 9, end_offset = 9, msg = "expected expression near ';'"}, get_error("return {;}")) assert.same({line = 1, offset = 9, end_offset = 9, msg = "expected expression near "}, get_error("return {")) assert.same({line = 1, offset = 11, end_offset = 13, prev_line = 1, prev_offset = 8, prev_end_offset = 8, msg = "expected '}' near 'end'"}, get_error("return {a end")) assert.same({line = 2, offset = 11, end_offset = 13, prev_line = 1, prev_offset = 8, prev_end_offset = 8, msg = "expected '}' (to close '{' on line 1) near 'end'"}, get_error("return {a\nend")) assert.same({line = 1, offset = 11, end_offset = 11, prev_line = 1, prev_offset = 9, prev_end_offset = 9, msg = "expected ']' near "}, get_error("return {[a")) assert.same({line = 2, offset = 12, end_offset = 12, prev_line = 1, prev_offset = 9, prev_end_offset = 9, msg = "expected ']' (to close '[' on line 1) near "}, get_error("return {[\na")) assert.same({line = 1, offset = 11, end_offset = 11, msg = "expected expression near ','"}, get_error("return {a,,}")) assert.same({line = 1, offset = 13, end_offset = 13, msg = "expected expression near "}, get_error("return {a = ")) end) it("parses simple expressions correctly", function() assert.same({tag = "Op", "unm", {tag = "Number", "1"} }, get_expr("-1")) assert.same({tag = "Op", "add", {tag = "Op", "add", {tag = "Number", "1"}, {tag = "Number", "2"} }, {tag = "Number", "3"} }, get_expr("1+2+3")) assert.same({tag = "Op", "pow", {tag = "Number", "1"}, {tag = "Op", "pow", {tag = "Number", "2"}, {tag = "Number", "3"} } }, get_expr("1^2^3")) assert.same({tag = "Op", "concat", {tag = "String", "1"}, {tag = "Op", "concat", {tag = "String", "2"}, {tag = "String", "3"} } }, get_expr("'1'..'2'..'3'")) end) it("handles operator precedence correctly", function() assert.same({tag = "Op", "add", {tag = "Op", "unm", {tag = "Number", "1"} }, {tag = "Op", "mul", {tag = "Number", "2"}, {tag = "Op", "pow", {tag = "Number", "3"}, {tag = "Number", "4"} } } }, get_expr("-1+2*3^4")) assert.same({tag = "Op", "bor", {tag = "Op", "bor", {tag = "Op", "band", {tag = "Op", "shr", {tag = "Number", "1"}, {tag = "Number", "2"} }, {tag = "Op", "shl", {tag = "Number", "3"}, {tag = "Number", "4"} } }, {tag = "Op", "bxor", {tag = "Number", "5"}, {tag = "Number", "6"} } }, {tag = "Op", "bnot", {tag = "Number", "7"} } }, get_expr("1 >> 2 & 3 << 4 | 5 ~ 6 | ~7")) assert.same({tag = "Op", "or", {tag = "Op", "and", {tag = "Op", "eq", {tag = "Id", "a"}, {tag = "Id", "b"} }, {tag = "Op", "eq", {tag = "Id", "c"}, {tag = "Id", "d"} } }, {tag = "Op", "ne", {tag = "Id", "e"}, {tag = "Id", "f"} } }, get_expr("a == b and c == d or e ~= f")) end) end) describe("when parsing multiple statements", function() it("considers semicolons and comments no-op statements", function() assert.same({tag = "Set", { {tag = "Id", "a"} }, { {tag = "Id", "b"} } }, get_node(";;;a = b;--[[]];--;")) end) it("does not allow statements after return", function() assert.same({line = 1, offset = 8, end_offset = 12, msg = "expected expression near 'break'"}, get_error("return break")) assert.same({line = 1, offset = 9, end_offset = 13, msg = "expected near 'break'"}, get_error("return; break")) assert.same({line = 1, offset = 8, end_offset = 8, msg = "expected near ';'"}, get_error("return;;")) assert.same({line = 1, offset = 10, end_offset = 14, msg = "expected near 'break'"}, get_error("return 1 break")) assert.same({line = 1, offset = 11, end_offset = 15, msg = "expected near 'break'"}, get_error("return 1; break")) assert.same({line = 1, offset = 13, end_offset = 17, msg = "expected near 'break'"}, get_error("return 1, 2 break")) assert.same({line = 1, offset = 14, end_offset = 18, msg = "expected near 'break'"}, get_error("return 1, 2; break")) end) it("parses nested statements correctly", function() assert.same({ {tag = "Localrec", {{tag = "Id", "f"}}, {{tag = "Function", {}, { {tag = "While", {tag = "True"}, { {tag = "If", {tag = "Nil"}, { {tag = "Call", {tag = "Id", "f"} }, {tag = "Return"} }, {tag = "False"}, { {tag = "Call", {tag = "Id", "g"} }, {tag = "Break"} }, { {tag = "Call", {tag = "Id", "h"} }, {tag = "Repeat", { {tag = "Goto", "fail"} }, {tag = "Id", "get_forked"} } } } } }, {tag = "Label", "fail"} }}} }, {tag = "Do", {tag = "Fornum", {tag = "Id", "i"}, {tag = "Number", "1"}, {tag = "Number", "2"}, { {tag = "Call", {tag = "Id", "nothing"} } } }, {tag = "Forin", { {tag = "Id", "k"}, {tag = "Id", "v"} }, { {tag = "Call", {tag = "Id", "pairs"} } }, { {tag = "Call", {tag = "Id", "print"}, {tag = "String", "bar"} }, {tag = "Call", {tag = "Id", "assert"}, {tag = "Number", "42"} } } }, {tag = "Return"} }, }, get_ast([[ local function f() while true do if nil then f() return elseif false then g() break else h() repeat goto fail until get_forked end end ::fail:: end do for i=1, 2 do nothing() end for k, v in pairs() do print("bar") assert(42) end return end ]])) end) end) describe("indentation-based missing until/end location guessing", function() it("provides a better location on the same indentation level for missing end", function() assert.same({line = 11, offset = 145, end_offset = 150, prev_line = 2, prev_offset = 23, prev_end_offset = 24, msg = "expected 'end' (to close 'if' on line 2) near 'whoops' (indentation-based guess)"}, get_error([[ local function f() if cond then do_thing() do_more_things() while true do things_keep_happening() end whoops() end ]])) assert.same({line = 10, offset = 131, end_offset = 136, prev_line = 7, prev_offset = 84, prev_end_offset = 89, msg = "expected 'until' (to close 'repeat' on line 7) near 'whoops' (indentation-based guess)" }, get_error([[ local function f() if cond then do_thing() do_more_things() repeat things_keep_happening() whoops() end ]])) assert.same({line = 8, offset = 64, end_offset = 68, prev_line = 5, prev_offset = 41, prev_end_offset = 48, msg = "expected 'end' (to close 'function' on line 5) near 'local' (indentation-based guess)" }, get_error([[ local function f() good() end local function g() bad() local function t() irrelevant() end ]])) assert.same({line = 9, offset = 56, end_offset = 65, prev_line = 4, prev_offset = 15, prev_end_offset = 16, msg = "expected 'end' (to close 'do' on line 4) near 'two_things' (indentation-based guess)" }, get_error([[ do end do end do do end do end one_thing() two_things() ]])) assert.same({line = 8, offset = 91, end_offset = 92, prev_line = 3, prev_offset = 16, prev_end_offset = 20, msg = "expected 'end' (to close 'while' on line 3) near 'if' (indentation-based guess)" }, get_error([[ do do while cond do thing = thing another = thing if yes then end end end ]])) assert.same({line = 6, offset = 117, end_offset = 125, prev_line = 3, prev_offset = 74, prev_end_offset = 76, msg = "expected 'end' (to close 'for' on line 3) near 'something' (indentation-based guess)" }, get_error([[ function g() for i in ipairs("this is not even an error...") do for i = 1, 2, 3 do thing() something = smth end ]])) end) it("provides a better location on a lower indentation level for missing end", function() assert.same({line = 5, offset = 36, end_offset = 38, prev_line = 2, prev_offset = 7, prev_end_offset = 11, msg = "expected 'end' (to close 'while' on line 2) near less indented 'end' (indentation-based guess)" }, get_error([[ do while true do thing() end ]])) assert.same({line = 5, offset = 51, end_offset = 51, prev_line = 2, prev_offset = 7, prev_end_offset = 11, msg = "expected 'end' (to close 'while' on line 2) near 'a' (indentation-based guess)" }, get_error([[ do while true do thing() more() a() ]])) end) it("provides a better location for various configurations of if statements", function() assert.same({line = 6, offset = 67, end_offset = 69, prev_line = 2, prev_offset = 7, prev_end_offset = 8, msg = "expected 'end' (to close 'if' on line 2) near less indented 'end' (indentation-based guess)" }, get_error([[ do if thing({ long, long, long, line}) then something() end ]])) assert.same({line = 7, offset = 66, end_offset = 66, prev_line = 4, prev_offset = 43, prev_end_offset = 46, msg = "expected 'end' (to close 'else' on line 4) near 'a' (indentation-based guess)" }, get_error([[ do if cond() then something() else thing() a = b end ]])) assert.same({line = 6, offset = 66, end_offset = 68, prev_line = 4, prev_offset = 43, prev_end_offset = 48, msg = "expected 'end' (to close 'elseif' on line 4) near less indented 'end' (indentation-based guess)" }, get_error([[ do if cond() then something() elseif something then end ]])) assert.same({line = 10, offset = 119, end_offset = 119, prev_line = 8, prev_offset = 99, prev_end_offset = 104, msg = "expected 'end' (to close 'elseif' on line 8) near 'e' (indentation-based guess)" }, get_error([[ do if cond() then s() elseif something then b() elseif a() then c() elseif d() then e() end ]])) end) it("reports the first guess location outside complete blocks", function() assert.same({line = 12, offset = 92, end_offset = 98, prev_line = 10, prev_offset = 61, prev_end_offset = 65, msg = "expected 'end' (to close 'while' on line 10) near 'another' (indentation-based guess)" }, get_error([[ do while true do thing() another() end end do while true do thing() another() end do while true do thing() another() end ]])) end) it("does not report blocks with different closing token comparing to original error", function() assert.same({line = 10, offset = 87, end_offset = 91, prev_line = 8, prev_offset = 60, prev_end_offset = 65, msg = "expected 'until' (to close 'repeat' on line 8) near less indented 'until' (indentation-based guess)" }, get_error([[ do while true do thing() a() repeat repeat thing() until cond end ]])) assert.same({line = 8, offset = 58, end_offset = 63, prev_line = 5, prev_offset = 30, prev_end_offset = 31, msg = "expected 'end' (to close 'do' on line 5) near 'thing3' (indentation-based guess)" }, get_error([[ repeat thing1() do do thing2() thing3() end until another_thing ]])) end) it("does not report tokens on the same line as the innermost block opening token", function() assert.same({line = 6, offset = 78, end_offset = 80, prev_line = 3, prev_offset = 60, prev_end_offset = 61, msg = "expected 'end' (to close 'do' on line 3) near less indented 'end' (indentation-based guess)" }, get_error([[ local function f() local function g() return ret end do thing() end ]])) end) end) it("provides correct location info", function() assert.same({ {tag = "Localrec", line = 1, offset = 1, end_offset = 80, {{tag = "Id", "foo", line = 1, offset = 16, end_offset = 18}}, {{tag = "Function", line = 1, offset = 7, end_offset = 80, end_range = {line = 4, offset = 78, end_offset = 80}, { {tag = "Id", "a", line = 1, offset = 20, end_offset = 20}, {tag = "Id", "b", line = 1, offset = 23, end_offset = 23}, {tag = "Id", "c", line = 1, offset = 26, end_offset = 26}, {tag = "Dots", "...", line = 1, offset = 29, end_offset = 31} }, { {tag = "Local", line = 2, offset = 37, end_offset = 57, { {tag = "Id", "d", line = 2, offset = 43, end_offset = 43} }, { {tag = "Op", "mul", line = 2, offset = 47, end_offset = 57, {tag = "Paren", line = 2, offset = 47, end_offset = 53, {tag = "Op", "add", line = 2, offset = 48, end_offset = 52, {tag = "Id", "a", line = 2, offset = 48, end_offset = 48}, {tag = "Id", "b", line = 2, offset = 52, end_offset = 52} } }, {tag = "Id", "c", line = 2, offset = 57, end_offset = 57} } } }, {tag = "Return", line = 3, offset = 62, end_offset = 76, {tag = "Id", "d", line = 3, offset = 69, end_offset = 69}, {tag = "Paren", line = 3, offset = 72, end_offset = 76, {tag = "Dots", "...", line = 3, offset = 73, end_offset = 75} } } } }} }, {tag = "Set", line = 6, offset = 83, end_offset = 144, { {tag = "Index", line = 6, offset = 92, end_offset = 96, {tag = "Id", "t", line = 6, offset = 92, end_offset = 92}, {tag = "String", "bar", line = 6, offset = 94, end_offset = 96} } }, { {tag = "Function", line = 6, offset = 83, end_offset = 144, end_range = {line = 10, offset = 142, end_offset = 144}, { {tag = "Id", "self", implicit = true, line = 6, offset = 93, end_offset = 93}, {tag = "Id", "arg", line = 6, offset = 98, end_offset = 100} }, { {tag = "If", line = 7, offset = 106, end_offset = 140, {tag = "Id", "arg", line = 7, offset = 109, end_offset = 111}, {line = 7, offset = 113, end_offset = 116, -- Branch location. {tag = "Call", line = 8, offset = 124, end_offset = 133, {tag = "Id", "print", line = 8, offset = 124, end_offset = 128}, {tag = "Id", "arg", line = 8, offset = 130, end_offset = 132} } } } } } } } }, (get_all([[ local function foo(a, b, c, ...) local d = (a + b) * c return d, (...) end function t:bar(arg) if arg then print(arg) end end ]]))) end) it("provides correct location info for labels", function() assert.same({ {tag = "Label", "foo", line = 1, offset = 1, end_offset = 7}, {tag = "Label", "bar", line = 2, offset = 9, end_offset = 17}, {tag = "Label", "baz", line = 3, offset = 18, end_offset = 25} }, (get_all([[ ::foo:: :: bar :::: baz:: ]]))) end) it("provides correct location info for statements starting with expressions", function() assert.same({ {tag = "Call", line = 1, offset = 1, end_offset = 3, {tag = "Id", "a", line = 1, offset = 1, end_offset = 1} }, {tag = "Call", line = 2, offset = 6, end_offset = 10, {tag = "Paren", line = 2, offset = 6, end_offset = 8, {tag = "Id", "b", line = 2, offset = 7, end_offset = 7} } }, {tag = "Set", line = 3, offset = 13, end_offset = 26, { {tag = "Index", line = 3, offset = 13, end_offset = 22, {tag = "Paren", line = 3, offset = 13, end_offset = 19, {tag = "Index", line = 3, offset = 14, end_offset = 18, {tag = "Paren", line = 3, offset = 14, end_offset = 16, {tag = "Id", "c", line = 3, offset = 15, end_offset = 15} }, {tag = "String", "d", line = 3, offset = 18, end_offset = 18} } }, {tag = "Number", "3", line = 3, offset = 21, end_offset = 21} } }, { {tag = "Number", "2", line = 3, offset = 26, end_offset = 26} } } }, (get_all([[ a(); (b)(); ((c).d)[3] = 2 ]]))) end) it("provides correct location info for conditions", function() assert.same({ {tag = "If", line = 1, offset = 1, end_offset = 15, {tag = "Paren", line = 1, offset = 4, end_offset = 6, {tag = "Id", "x", line = 1, offset = 5, end_offset = 5}, }, {line = 1, offset = 8, end_offset = 11} } }, (get_all([[ if (x) then end ]]))) end) it("provides correct location info for table keys", function() assert.same({ {tag = "Return", line = 1, offset = 1, end_offset = 28, {tag = "Table", line = 1, offset = 8, end_offset = 28, {tag = "Pair", line = 1, offset = 9, end_offset = 13, {tag = "String", "a", line = 1, offset = 9, end_offset = 9}, {tag = "Id", "b", line = 1, offset = 13, end_offset = 13} }, {tag = "Pair", line = 1, offset = 16, end_offset = 22, {tag = "Id", "x", line = 1, offset = 17, end_offset = 17}, {tag = "Id", "y", line = 1, offset = 22, end_offset = 22}, }, {tag = "Paren", line = 1, offset = 25, end_offset = 27, {tag = "Id", "z", line = 1, offset = 26, end_offset = 26} } } } }, (get_all([[ return {a = b, [x] = y, (z)} ]]))) end) it("provides correct error location info", function() assert.same({line = 8, offset = 132, end_offset = 132, msg = "expected '=' near ')'"}, get_error([[ local function foo(a, b, c, ...) local d = (a + b) * c return d, (...) end function t:bar(arg) if arg then printarg) end end ]])) end) it("provides correct error location info for EOF with no endline", function() assert.same({line = 1, offset = 9, end_offset = 9, msg = "expected expression near "}, get_error("thing = ")) assert.same( {line = 1, offset = 15, end_offset = 15, msg = "expected expression near "}, get_error("thing = -- eof")) end) describe("providing misc information", function() it("provides short comments correctly", function() assert.same({ {contents = " ignore something", line = 1, offset = 1, end_offset = 19}, {contents = " comments", line = 2, offset = 33, end_offset = 43} }, get_comments([[ -- ignore something foo = bar() -- comments return true --[=[ long comment]=] ]])) end) it("provides lines with code correctly", function() assert.same({nil, true, true, true, true, true, true, true, true, nil, nil, true, true}, get_code_lines([[ -- nothing here local foo = 2 + 3 + [=[ ]=] + { --[=[empty]=] } ::bar:: ]])) assert.same({true}, get_code_lines("f() -- luacheck: ignore")) end) it("provides line ending types correctly", function() assert.same({ "comment", nil, nil, nil, "string", nil, "comment", "comment", nil, nil, "string", "string", nil }, get_line_endings([[ -- comment f() --[=[comment]=] f() f("\ string") --[=[ comment ]=] f() f([=[ string ]=]) ]])) assert.same({"comment"}, get_line_endings("f() -- comment")) end) end) end) luacheck-0.25.0/spec/projects/000077500000000000000000000000001410451437000161375ustar00rootroot00000000000000luacheck-0.25.0/spec/projects/default_stds/000077500000000000000000000000001410451437000206205ustar00rootroot00000000000000luacheck-0.25.0/spec/projects/default_stds/.luacheckrc000066400000000000000000000004121410451437000227220ustar00rootroot00000000000000std = "min" files["**/test/**/*_spec.lua"] = { std = "none" } local shared_options = {ignore = {"ignored"}} files["**/spec/**/*_spec.lua"] = shared_options files["normal_file.lua"] = shared_options local function sink() end sink(it, version, math, newproxy) luacheck-0.25.0/spec/projects/default_stds/default_stds-scm-1.rockspec000066400000000000000000000004251410451437000257530ustar00rootroot00000000000000package = "default_stds" version = "scm-1" source = { url = "https://example.com" } description = { summary = "example", detailed = "example", homepage = "https://example.com", license = "MIT" } dependencies = {} it("is a rockspec")(newproxy, math, new_globals) luacheck-0.25.0/spec/projects/default_stds/nested/000077500000000000000000000000001410451437000221025ustar00rootroot00000000000000luacheck-0.25.0/spec/projects/default_stds/nested/spec/000077500000000000000000000000001410451437000230345ustar00rootroot00000000000000luacheck-0.25.0/spec/projects/default_stds/nested/spec/sample_spec.lua000066400000000000000000000001271410451437000260320ustar00rootroot00000000000000it("is a test in a nested directory")(newproxy, math, version, read_globals) ignored() luacheck-0.25.0/spec/projects/default_stds/normal_file.lua000066400000000000000000000001151410451437000236070ustar00rootroot00000000000000it("is just a normal file")(newproxy, math, version, read_globals) ignored() luacheck-0.25.0/spec/projects/default_stds/sample_spec.lua000066400000000000000000000001021410451437000236070ustar00rootroot00000000000000it("is not really a test")(newproxy, math, version, read_globals) luacheck-0.25.0/spec/projects/default_stds/test/000077500000000000000000000000001410451437000215775ustar00rootroot00000000000000luacheck-0.25.0/spec/projects/default_stds/test/nested_normal_file.lua000066400000000000000000000001251410451437000261310ustar00rootroot00000000000000it("is a normal file in a nested directory")(newproxy, math, version, read_globals) luacheck-0.25.0/spec/projects/default_stds/test/sample_spec.lua000066400000000000000000000001131410451437000245700ustar00rootroot00000000000000it("is a test in a test directory")(newproxy, math, version, read_globals) luacheck-0.25.0/spec/projects/default_stds/tests/000077500000000000000000000000001410451437000217625ustar00rootroot00000000000000luacheck-0.25.0/spec/projects/default_stds/tests/nested/000077500000000000000000000000001410451437000232445ustar00rootroot00000000000000luacheck-0.25.0/spec/projects/default_stds/tests/nested/sample_spec.lua000066400000000000000000000001221410451437000262350ustar00rootroot00000000000000it("is a test in a very nested directory")(newproxy, math, version, read_globals) luacheck-0.25.0/spec/projects/default_stds/tests/sample_spec.lua000066400000000000000000000000671410451437000247630ustar00rootroot00000000000000it("is a test")(newproxy, math, version, read_globals) luacheck-0.25.0/spec/resolve_locals_spec.lua000066400000000000000000000056411410451437000210450ustar00rootroot00000000000000local helper = require "spec.helper" local function used_variables_to_string(chstate, item) local buf = {} for var, values in pairs(item.used_values) do local values_buf = {} for _, value in ipairs(values) do table.insert(values_buf, ("%d:%d"):format( value.var_node.line, chstate:offset_to_column(value.var_node.line, value.var_node.offset))) end table.insert(buf, var.name .. " = (" .. table.concat(values_buf, ", ") .. ")") end table.sort(buf) return item.tag .. ": " .. table.concat(buf, "; ") end local function get_used_variables_as_string(src) local chstate = helper.get_chstate_after_stage("resolve_locals", src) local buf = {} for _, item in ipairs(chstate.top_line.items) do if item.accesses and next(item.accesses) then assert.is_table(item.used_values) table.insert(buf, used_variables_to_string(chstate, item)) end end return table.concat(buf, "\n") end describe("resolve_locals", function() describe("when resolving values", function() it("resolves values in linear cases", function() assert.equal([[ Eval: a = (1:7)]], get_used_variables_as_string([[ local a = 6 print(a) ]])) end) it("resolves values after ifs", function() assert.equal([[ Eval: a = (1:7, 4:4)]], get_used_variables_as_string([[ local a if expr then a = 5 end print(a) ]])) assert.equal([[ Eval: a = (4:4, 7:4, 10:7, 13:4)]], get_used_variables_as_string([[ local a = 3 if expr then a = 4 elseif expr then a = 5 a = 8 if expr then a = 7 end else a = 6 end print(a) ]])) end) it("resolves values after loops", function() assert.equal([[ Eval: a = (1:7, 5:7) Eval: a = (1:7, 5:7)]], get_used_variables_as_string([[ local a while not a do if expr then a = expr2 end end print(a) ]])) assert.equal([[ Set: k = (2:5) Eval: v = (2:8) Eval: a = (3:4); b = (1:10) Eval: a = (1:7, 3:4); b = (1:10)]], get_used_variables_as_string([[ local a, b = 1, 2 for k, v in pairs(t) do a = k if v then print(a, b) end end print(a, b) ]])) end) end) describe("when resolving upvalues", function() it("resolves set upvalues naively", function() assert.equal([[ Eval: f = (3:16) Eval: a = (1:7, 4:4)]], get_used_variables_as_string([[ local a local function f() a = 5 end f() print(a) ]])) end) it("naively determines where closure is live", function() assert.equal([[ Eval: a = (1:7) Eval: a = (1:7, 6:4)]], get_used_variables_as_string([[ local a = 4 print(a) local function f() a = 5 end print(a) ]])) end) it("naively determines where closure is live in loops", function() assert.equal([[ Eval: a = (1:7, 6:22) Eval: a = (1:7, 6:22)]], get_used_variables_as_string([[ local a = 4 repeat print(a) escape(function() a = 5 end) until a ]])) end) end) end) luacheck-0.25.0/spec/reversed_fornum_loops_spec.lua000066400000000000000000000037761410451437000224610ustar00rootroot00000000000000local helper = require "spec.helper" local function assert_warnings(warnings, src) assert.same(warnings, helper.get_stage_warnings("detect_reversed_fornum_loops", src)) end describe("reversed fornum loop detection", function() it("does not detect anything wrong if not going down from #(expr)", function() assert_warnings({}, [[ for i = -10, 1 do print(i) end ]]) end) it("does not detect anything wrong if limit may be greater than 1", function() assert_warnings({}, [[ for i = #t, 2 do print(i) end for i = #t, x do print(i) end ]]) end) it("does not detect anything wrong if step may be negative", function() assert_warnings({}, [[ for i = #t, 1, -1 do print(i) end for i = #t, 1, x do print(i) end ]]) end) it("detects reversed loops going from #(expr) to limit less than or equal to 1", function() assert_warnings({ {code = "571", line = 1, column = 1, end_column = 16, limit = "1"}, {code = "571", line = 5, column = 1, end_column = 23, limit = "0"}, {code = "571", line = 9, column = 1, end_column = 32, limit = "-123.456"} }, [[ for i = #t, 1 do print(t[i]) end for i = #"abcdef", 0 do print(something) end for i = #(...), -123.456, 567 do print(something) end ]]) end) it("detects reversed loops in nested statements and functions", function() assert_warnings({ {code = "571", line = 7, column = 13, end_column = 28, limit = "1"}, {code = "571", line = 8, column = 16, end_column = 31, limit = "1"}, {code = "571", line = 10, column = 22, end_column = 43, limit = "1"} }, [[ do print("thing") while true do repeat for i, v in ipairs(t) do for i = #a, 1 do for i = #b, 1 do function xyz() for i = #"thing", 1 do print("thing") end end end end end until foo end end ]]) end) end) luacheck-0.25.0/spec/rock/000077500000000000000000000000001410451437000152445ustar00rootroot00000000000000luacheck-0.25.0/spec/rock/bin/000077500000000000000000000000001410451437000160145ustar00rootroot00000000000000luacheck-0.25.0/spec/rock/bin/rock.lua000066400000000000000000000000131410451437000174470ustar00rootroot00000000000000-- nothing luacheck-0.25.0/spec/rock/bin/rock.sh000066400000000000000000000000121410451437000172770ustar00rootroot00000000000000# nothing luacheck-0.25.0/spec/rock/lua_modules/000077500000000000000000000000001410451437000175555ustar00rootroot00000000000000luacheck-0.25.0/spec/rock/lua_modules/something.lua000066400000000000000000000000131410451437000222470ustar00rootroot00000000000000-- nothing luacheck-0.25.0/spec/rock/rock-dev-1.rockspec000066400000000000000000000004071410451437000206500ustar00rootroot00000000000000rockspec_format = "3.0" package = "rock" version = "dev-1" source = { url = "https://github.com/rockman/rock" } description = { license = "MIT" } dependencies = { "lua >= 5.1" } test_dependencies = { "busted = 2.0.rc12-1" } test = { type = "busted" } luacheck-0.25.0/spec/rock/src/000077500000000000000000000000001410451437000160335ustar00rootroot00000000000000luacheck-0.25.0/spec/rock/src/rock.lua000066400000000000000000000000131410451437000174660ustar00rootroot00000000000000-- nothing luacheck-0.25.0/spec/rock/src/rock/000077500000000000000000000000001410451437000167715ustar00rootroot00000000000000luacheck-0.25.0/spec/rock/src/rock/mod.lua000066400000000000000000000000131410451437000202450ustar00rootroot00000000000000-- nothing luacheck-0.25.0/spec/rock/src/rock/thing.c000066400000000000000000000000131410451437000202400ustar00rootroot00000000000000// nothing luacheck-0.25.0/spec/rock/test.lua000066400000000000000000000000131410451437000167200ustar00rootroot00000000000000-- nothing luacheck-0.25.0/spec/rock2/000077500000000000000000000000001410451437000153265ustar00rootroot00000000000000luacheck-0.25.0/spec/rock2/mod.lua000066400000000000000000000000131410451437000166020ustar00rootroot00000000000000-- nothing luacheck-0.25.0/spec/rock2/rock2-dev-1.rockspec000066400000000000000000000004241410451437000210130ustar00rootroot00000000000000rockspec_format = "3.0" package = "rock2" version = "dev-1" source = { url = "https://github.com/rockman/rock2" } description = { license = "MIT" } dependencies = { "lua >= 5.1" } test_dependencies = { "busted = 2.0.rc12-1" } build = {} test = { type = "busted" } luacheck-0.25.0/spec/rock2/spec/000077500000000000000000000000001410451437000162605ustar00rootroot00000000000000luacheck-0.25.0/spec/rock2/spec/rock2_spec.lua000066400000000000000000000000131410451437000210070ustar00rootroot00000000000000-- nothing luacheck-0.25.0/spec/samples/000077500000000000000000000000001410451437000157525ustar00rootroot00000000000000luacheck-0.25.0/spec/samples/argparse-0.2.0.lua000066400000000000000000000575011410451437000207240ustar00rootroot00000000000000local Parser, Command, Argument, Option do -- Create classes with setters local class = require "30log" local function add_setters(cl, fields) for field, setter in pairs(fields) do cl[field] = function(self, value) setter(self, value) self["_"..field] = value return self end end cl.__init = function(self, ...) return self(...) end cl.__call = function(self, ...) local name_or_options for i=1, select("#", ...) do name_or_options = select(i, ...) if type(name_or_options) == "string" then if self._aliases then table.insert(self._aliases, name_or_options) end if not self._aliases or not self._name then self._name = name_or_options end elseif type(name_or_options) == "table" then for field, setter in pairs(fields) do if name_or_options[field] ~= nil then self[field](self, name_or_options[field]) end end end end return self end return cl end local typecheck = setmetatable({}, { __index = function(self, type_) local typechecker_factory = function(field) return function(_, value) if type(value) ~= type_ then error(("bad field '%s' (%s expected, got %s)"):format(field, type_, type(value))) end end end self[type_] = typechecker_factory return typechecker_factory end }) local function aliased_name(self, name) typecheck.string "name" (self, name) table.insert(self._aliases, name) end local function aliased_aliases(self, aliases) typecheck.table "aliases" (self, aliases) if not self._name then self._name = aliases[1] end end local function parse_boundaries(boundaries) if tonumber(boundaries) then return tonumber(boundaries), tonumber(boundaries) end if boundaries == "*" then return 0, math.huge end if boundaries == "+" then return 1, math.huge end if boundaries == "?" then return 0, 1 end if boundaries:match "^%d+%-%d+$" then local min, max = boundaries:match "^(%d+)%-(%d+)$" return tonumber(min), tonumber(max) end if boundaries:match "^%d+%+$" then local min = boundaries:match "^(%d+)%+$" return tonumber(min), math.huge end end local function boundaries(field) return function(self, value) local min, max = parse_boundaries(value) if not min then error(("bad field '%s'"):format(field)) end self["_min"..field], self["_max"..field] = min, max end end local function convert(self, value) if type(value) ~= "function" then if type(value) ~= "table" then error(("bad field 'convert' (function or table expected, got %s)"):format(type(value))) end end end local function argname(self, value) if type(value) ~= "string" then if type(value) ~= "table" then error(("bad field 'argname' (string or table expected, got %s)"):format(type(value))) end end end local function add_help(self, param) if self._has_help then table.remove(self._options) self._has_help = false end if param then local help = self:flag() :description "Show this help message and exit. " :action(function() io.stdout:write(self:get_help() .. "\r\n") os.exit(0) end)(param) if not help._name then help "-h" "--help" end self._has_help = true end end Parser = add_setters(class { __name = "Parser", _arguments = {}, _options = {}, _commands = {}, _mutexes = {}, _require_command = true }, { name = typecheck.string "name", description = typecheck.string "description", epilog = typecheck.string "epilog", require_command = typecheck.boolean "require_command", usage = typecheck.string "usage", help = typecheck.string "help", add_help = add_help }) Command = add_setters(Parser:extends { __name = "Command", _aliases = {} }, { name = aliased_name, aliases = aliased_aliases, description = typecheck.string "description", epilog = typecheck.string "epilog", target = typecheck.string "target", require_command = typecheck.boolean "require_command", action = typecheck["function"] "action", usage = typecheck.string "usage", help = typecheck.string "help", add_help = add_help }) Argument = add_setters(class { __name = "Argument", _minargs = 1, _maxargs = 1, _mincount = 1, _maxcount = 1, _defmode = "unused" }, { name = typecheck.string "name", description = typecheck.string "description", target = typecheck.string "target", args = boundaries "args", default = typecheck.string "default", defmode = typecheck.string "defmode", convert = convert, argname = argname }) Option = add_setters(Argument:extends { __name = "Option", _aliases = {}, _mincount = 0, _overwrite = true }, { name = aliased_name, aliases = aliased_aliases, description = typecheck.string "description", target = typecheck.string "target", args = boundaries "args", count = boundaries "count", default = typecheck.string "default", defmode = typecheck.string "defmode", convert = convert, overwrite = typecheck.boolean "overwrite", action = typecheck["function"] "action", argname = argname }) end function Argument:_get_argument_list() local buf = {} local i = 1 while i <= math.min(self._minargs, 3) do local argname = self:_get_argname_i(i) if self._default and self._defmode:find "a" then argname = "[" .. argname .. "]" end table.insert(buf, argname) i = i+1 end while i <= math.min(self._maxargs, 3) do table.insert(buf, "[" .. self:_get_argname_i(i) .. "]") i = i+1 if self._maxargs == math.huge then break end end if i < self._maxargs then table.insert(buf, "...") end return buf end function Argument:_get_usage() local usage = table.concat(self:_get_argument_list(), " ") if self._default and self._defmode:find "u" then if self._maxargs > 1 or (self._minargs == 1 and not self._defmode:find "a") then usage = "[" .. usage .. "]" end end return usage end function Argument:_get_type() if self._maxcount == 1 then if self._maxargs == 0 then return "flag" elseif self._maxargs == 1 and (self._minargs == 1 or self._mincount == 1) then return "arg" else return "multiarg" end else if self._maxargs == 0 then return "counter" elseif self._maxargs == 1 and self._minargs == 1 then return "multicount" else return "twodimensional" end end end function Argument:_get_argname_i(i) local argname = self:_get_argname() if type(argname) == "table" then return argname[i] else return argname end end function Argument:_get_argname() return self._argname or ("<"..self._name..">") end function Option:_get_argname() return self._argname or ("<"..self:_get_target()..">") end function Argument:_get_label() return self._name end function Option:_get_label() local variants = {} local argument_list = self:_get_argument_list() table.insert(argument_list, 1, nil) for _, alias in ipairs(self._aliases) do argument_list[1] = alias table.insert(variants, table.concat(argument_list, " ")) end return table.concat(variants, ", ") end function Command:_get_label() return table.concat(self._aliases, ", ") end function Argument:_get_description() if self._default then if self._description then return ("%s (default: %s)"):format(self._description, self._default) else return ("default: %s"):format(self._default) end else return self._description or "" end end function Command:_get_description() return self._description or "" end function Option:_get_usage() local usage = self:_get_argument_list() table.insert(usage, 1, self._name) usage = table.concat(usage, " ") if self._mincount == 0 or self._default then usage = "[" .. usage .. "]" end return usage end function Option:_get_target() if self._target then return self._target end for _, alias in ipairs(self._aliases) do if alias:sub(1, 1) == alias:sub(2, 2) then return alias:sub(3) end end return self._name:sub(2) end function Parser:_get_fullname() local parent = self._parent local buf = {self._name} while parent do table.insert(buf, 1, parent._name) parent = parent._parent end return table.concat(buf, " ") end function Parser:_update_charset(charset) charset = charset or {} for _, command in ipairs(self._commands) do command:_update_charset(charset) end for _, option in ipairs(self._options) do for _, alias in ipairs(option._aliases) do charset[alias:sub(1, 1)] = true end end return charset end function Parser:argument(...) local argument = Argument:new(...) table.insert(self._arguments, argument) return argument end function Parser:option(...) local option = Option:new(...) if self._has_help then table.insert(self._options, #self._options, option) else table.insert(self._options, option) end return option end function Parser:flag(...) return self:option():args(0)(...) end function Parser:command(...) local command = Command:new():add_help(true)(...) command._parent = self table.insert(self._commands, command) return command end function Parser:mutex(...) local options = {...} for i, option in ipairs(options) do assert(getmetatable(option) == Option, ("bad argument #%d to 'mutex' (Option expected)"):format(i)) end table.insert(self._mutexes, options) return self end local max_usage_width = 70 local usage_welcome = "Usage: " function Parser:get_usage() if self._usage then return self._usage end local lines = {usage_welcome .. self:_get_fullname()} local function add(s) if #lines[#lines]+1+#s <= max_usage_width then lines[#lines] = lines[#lines] .. " " .. s else lines[#lines+1] = (" "):rep(#usage_welcome) .. s end end -- set of mentioned elements local used = {} for _, mutex in ipairs(self._mutexes) do local buf = {} for _, option in ipairs(mutex) do table.insert(buf, option:_get_usage()) used[option] = true end add("(" .. table.concat(buf, " | ") .. ")") end for _, elements in ipairs{self._options, self._arguments} do for _, element in ipairs(elements) do if not used[element] then add(element:_get_usage()) end end end if #self._commands > 0 then if self._require_command then add("") else add("[]") end add("...") end return table.concat(lines, "\r\n") end local margin_len = 3 local margin_len2 = 25 local margin = (" "):rep(margin_len) local margin2 = (" "):rep(margin_len2) local function make_two_columns(s1, s2) if s2 == "" then return margin .. s1 end s2 = s2:gsub("[\r\n][\r\n]?", function(sub) if #sub == 1 or sub == "\r\n" then return "\r\n" .. margin2 else return "\r\n\r\n" .. margin2 end end) if #s1 < (margin_len2-margin_len) then return margin .. s1 .. (" "):rep(margin_len2-margin_len-#s1) .. s2 else return margin .. s1 .. "\r\n" .. margin2 .. s2 end end function Parser:get_help() if self._help then return self._help end local blocks = {self:get_usage()} if self._description then table.insert(blocks, self._description) end local labels = {"Arguments: ", "Options: ", "Commands: "} for i, elements in ipairs{self._arguments, self._options, self._commands} do if #elements > 0 then local buf = {labels[i]} for _, element in ipairs(elements) do table.insert(buf, make_two_columns(element:_get_label(), element:_get_description())) end table.insert(blocks, table.concat(buf, "\r\n")) end end if self._epilog then table.insert(blocks, self._epilog) end return table.concat(blocks, "\r\n\r\n") end local function get_tip(context, wrong_name) local context_pool = {} local possible_name local possible_names = {} for name in pairs(context) do for i=1, #name do possible_name = name:sub(1, i-1) .. name:sub(i+1) if not context_pool[possible_name] then context_pool[possible_name] = {} end table.insert(context_pool[possible_name], name) end end for i=1, #wrong_name+1 do possible_name = wrong_name:sub(1, i-1) .. wrong_name:sub(i+1) if context[possible_name] then possible_names[possible_name] = true elseif context_pool[possible_name] then for _, name in ipairs(context_pool[possible_name]) do possible_names[name] = true end end end local first = next(possible_names) if first then if next(possible_names, first) then local possible_names_arr = {} for name in pairs(possible_names) do table.insert(possible_names_arr, "'" .. name .. "'") end table.sort(possible_names_arr) return "\r\nDid you mean one of these: " .. table.concat(possible_names_arr, " ") .. "?" else return "\r\nDid you mean '" .. first .. "'?" end else return "" end end local function plural(x) if x == 1 then return "" end return "s" end local default_cmdline = arg or {} function Parser:_parse(args, errhandler) args = args or default_cmdline local parser local charset local options = {} local arguments = {} local commands local option_mutexes = {} local used_mutexes = {} local opt_context = {} local com_context local result = {} local invocations = {} local passed = {} local cur_option local cur_arg_i = 1 local cur_arg local targets = {} local function error_(fmt, ...) return errhandler(parser, fmt:format(...)) end local function assert_(assertion, ...) return assertion or error_(...) end local function convert(element, data) if element._convert then local ok, err if type(element._convert) == "function" then ok, err = element._convert(data) else ok, err = element._convert[data] end assert_(ok ~= nil, "%s", err or "malformed argument '" .. data .. "'") data = ok end return data end local invoke, pass, close function invoke(element) local overwrite = false if invocations[element] == element._maxcount then if element._overwrite then overwrite = true else error_("option '%s' must be used at most %d time%s", element._name, element._maxcount, plural(element._maxcount)) end else invocations[element] = invocations[element]+1 end passed[element] = 0 local type_ = element:_get_type() local target = targets[element] if type_ == "flag" then result[target] = true elseif type_ == "multiarg" then result[target] = {} elseif type_ == "counter" then if not overwrite then result[target] = result[target]+1 end elseif type_ == "multicount" then if overwrite then table.remove(result[target], 1) end elseif type_ == "twodimensional" then table.insert(result[target], {}) if overwrite then table.remove(result[target], 1) end end if element._maxargs == 0 then close(element) end end function pass(element, data) passed[element] = passed[element]+1 data = convert(element, data) local type_ = element:_get_type() local target = targets[element] if type_ == "arg" then result[target] = data elseif type_ == "multiarg" or type_ == "multicount" then table.insert(result[target], data) elseif type_ == "twodimensional" then table.insert(result[target][#result[target]], data) end if passed[element] == element._maxargs then close(element) end end local function complete_invocation(element) while passed[element] < element._minargs do pass(element, element._default) end end function close(element) if passed[element] < element._minargs then if element._default and element._defmode:find "a" then complete_invocation(element) else error_("too few arguments") end else if element == cur_option then cur_option = nil elseif element == cur_arg then cur_arg_i = cur_arg_i+1 cur_arg = arguments[cur_arg_i] end end end local function switch(p) parser = p for _, option in ipairs(parser._options) do table.insert(options, option) for _, alias in ipairs(option._aliases) do opt_context[alias] = option end local type_ = option:_get_type() targets[option] = option:_get_target() if type_ == "counter" then result[targets[option]] = 0 elseif type_ == "multicount" or type_ == "twodimensional" then result[targets[option]] = {} end invocations[option] = 0 end for _, mutex in ipairs(parser._mutexes) do for _, option in ipairs(mutex) do if not option_mutexes[option] then option_mutexes[option] = {mutex} else table.insert(option_mutexes[option], mutex) end end end for _, argument in ipairs(parser._arguments) do table.insert(arguments, argument) invocations[argument] = 0 targets[argument] = argument._target or argument._name invoke(argument) end cur_arg = arguments[cur_arg_i] commands = parser._commands com_context = {} for _, command in ipairs(commands) do targets[command] = command._target or command._name for _, alias in ipairs(command._aliases) do com_context[alias] = command end end end local function get_option(name) return assert_(opt_context[name], "unknown option '%s'%s", name, get_tip(opt_context, name)) end local function do_action(element) if element._action then element._action() end end local function handle_argument(data) if cur_option then pass(cur_option, data) elseif cur_arg then pass(cur_arg, data) else local com = com_context[data] if not com then if #commands > 0 then error_("unknown command '%s'%s", data, get_tip(com_context, data)) else error_("too many arguments") end else result[targets[com]] = true do_action(com) switch(com) end end end local function handle_option(data) if cur_option then close(cur_option) end cur_option = opt_context[data] if option_mutexes[cur_option] then for _, mutex in ipairs(option_mutexes[cur_option]) do if used_mutexes[mutex] and used_mutexes[mutex] ~= cur_option then error_("option '%s' can not be used together with option '%s'", data, used_mutexes[mutex]._name) else used_mutexes[mutex] = cur_option end end end do_action(cur_option) invoke(cur_option) end local function mainloop() local handle_options = true for _, data in ipairs(args) do local plain = true local first, name, option if handle_options then first = data:sub(1, 1) if charset[first] then if #data > 1 then plain = false if data:sub(2, 2) == first then if #data == 2 then if cur_option then close(cur_option) end handle_options = false else local equal = data:find "=" if equal then name = data:sub(1, equal-1) option = get_option(name) assert_(option._maxargs > 0, "option '%s' does not take arguments", name) handle_option(data:sub(1, equal-1)) handle_argument(data:sub(equal+1)) else get_option(data) handle_option(data) end end else for i = 2, #data do name = first .. data:sub(i, i) option = get_option(name) handle_option(name) if i ~= #data and option._minargs > 0 then handle_argument(data:sub(i+1)) break end end end end end end if plain then handle_argument(data) end end end switch(self) charset = parser:_update_charset() mainloop() if cur_option then close(cur_option) end while cur_arg do if passed[cur_arg] == 0 and cur_arg._default and cur_arg._defmode:find "u" then complete_invocation(cur_arg) else close(cur_arg) end end if parser._require_command and #commands > 0 then error_("a command is required") end for _, option in ipairs(options) do if invocations[option] == 0 then if option._default and option._defmode:find "u" then invoke(option) complete_invocation(option) close(option) end end if invocations[option] < option._mincount then if option._default and option._defmode:find "a" then while invocations[option] < option._mincount do invoke(option) close(option) end else error_("option '%s' must be used at least %d time%s", option._name, option._mincount, plural(option._mincount)) end end end return result end function Parser:error(msg) if _TEST then error(msg) else io.stderr:write(("%s\r\n\r\nError: %s\r\n"):format(self:get_usage(), msg)) os.exit(1) end end function Parser:parse(args) return self:_parse(args, Parser.error) end function Parser:pparse(args) local errmsg local ok, result = pcall(function() return self:_parse(args, function(parser, err) errmsg = err return error() end) end) if ok then return true, result else assert(errmsg, result) return false, errmsg end end return function(...) return Parser(default_cmdline[0]):add_help(true)(...) end luacheck-0.25.0/spec/samples/bad.rockspec000066400000000000000000000000121410451437000202240ustar00rootroot00000000000000bad("???")luacheck-0.25.0/spec/samples/bad_code.lua000066400000000000000000000002321410451437000201720ustar00rootroot00000000000000package.loaded[...] = {} local function helper(...) -- NYI end function embrace(opt) local opt = opt or "default" return hepler(opt.."?") end luacheck-0.25.0/spec/samples/bad_flow.lua000066400000000000000000000006741410451437000202410ustar00rootroot00000000000000if io.read("*n") == "exit" then else local a, b do -- print(something) end for _ = 1, 10 do if io.read("*n") == "foobar" then a, b = 1 print(a, b) break else a, b = 1, 2, 3 print(a, b) break end print("How could this happen?") end end while true do if package.loaded.foo then return 4 else print(5) break end end luacheck-0.25.0/spec/samples/bad_whitespace.lua000066400000000000000000000015211410451437000214160ustar00rootroot00000000000000-- Examples of whitespace formatting violations local function trailing_whitespace_in_code() return "This is awful" end local function trailing_whitespace_in_comment() -- Less awful, but... return "Still bad" end local function trailing_whitespace_in_long_strings() return [[ It gets worse Much worse ]] --[[ Same in long comments ]] end local function trailing_whitespace_mixed() return "Not much better" -- You bet! end local function whitespace_only_lines() return "Lost in space" end local function inconsistent_indentation() return "Don't do this" end return { -- fake "module" table trailing_whitespace_in_code, trailing_whitespace_in_comment, trailing_whitespace_in_long_strings, trailing_whitespace_mixed, whitespace_only_lines, inconsistent_indentation, } luacheck-0.25.0/spec/samples/compat.lua000066400000000000000000000000511410451437000177340ustar00rootroot00000000000000(setfenv and rawlen)(setfenv and rawlen) luacheck-0.25.0/spec/samples/custom_std_inline_options.lua000066400000000000000000000002211410451437000237450ustar00rootroot00000000000000-- luacheck: push -- luacheck: std +busted tostring(setfenv, print(it)) -- luacheck: pop -- luacheck: std other_std tostring(setfenv, print(it)) luacheck-0.25.0/spec/samples/defined.lua000066400000000000000000000000521410451437000200500ustar00rootroot00000000000000foo = {} function foo.bar() baz() end luacheck-0.25.0/spec/samples/defined2.lua000066400000000000000000000000121410451437000201260ustar00rootroot00000000000000foo.bar() luacheck-0.25.0/spec/samples/defined3.lua000066400000000000000000000000331410451437000201320ustar00rootroot00000000000000foo = {} foo = {} bar = {} luacheck-0.25.0/spec/samples/defined4.lua000066400000000000000000000000521410451437000201340ustar00rootroot00000000000000function foo() foo = 1 bar = {} end luacheck-0.25.0/spec/samples/empty.lua000066400000000000000000000000001410451437000176010ustar00rootroot00000000000000luacheck-0.25.0/spec/samples/global_fields.lua000066400000000000000000000013141410451437000212420ustar00rootroot00000000000000local t = table local upsert = t.upsert local update = upsert print(update.something) upsert = t.insert upsert() upsert.foo = "bar" package.loaded.hello = true package.loaded[package] = true local s1 = "find" local s2 = "gfind" -- luacheck: push std max print(string[s1]) print(string[s2]) -- luacheck: pop -- luacheck: push std min print(string[s1]) print(string[s2]) -- luacheck: pop -- luacheck: not globals string.find print(string[s1]) -- luacheck: globals nest.nest.nest nest.nest = "nest" print(server) print(server.sessions) server.foo = "bar" server.bar[_G] = "baz" server.baz = "abcd" print(server.baz.abcd) server.sessions["hey"] = "you" -- luacheck: std +my_server server.bar = 1 server.baz = 2 luacheck-0.25.0/spec/samples/global_inline_options.lua000066400000000000000000000004371410451437000230320ustar00rootroot00000000000000-- luacheck: allow defined top foo = 4 print(foo) bar = 6 -- luacheck: ignore 131 function f() baz = 5 -- luacheck: allow defined qu = 4 print(qu) end -- luacheck: module, globals external quu = 7 print(external) local function g() -- luacheck: ignore external = 8 end luacheck-0.25.0/spec/samples/globals.lua000066400000000000000000000000411410451437000200730ustar00rootroot00000000000000print(setfenv(rawlen(tostring))) luacheck-0.25.0/spec/samples/good_code.lua000066400000000000000000000002551410451437000204010ustar00rootroot00000000000000local embracer = {} local function helper() -- NYI wontfix end function embracer.embrace(opt) opt = opt or "default" return helper(opt.."?") end return embracer luacheck-0.25.0/spec/samples/indirect_globals.lua000066400000000000000000000001531410451437000217600ustar00rootroot00000000000000local t = table local g = global local t_concat t_concat = t.concat t_concat.foo.bar = g:method(g, global) luacheck-0.25.0/spec/samples/inline_options.lua000066400000000000000000000010421410451437000215030ustar00rootroot00000000000000-- luacheck: ignore 4 -- luacheck: ignore foo bar foo() bar() local function f(a) -- luacheck: no unused args -- luacheck: globals baz foo() bar() baz() qu() -- luacheck: globals qu qu() end baz() -- luacheck should ignore this comment -- luacheck: push ignore 2/f local f -- luacheck: push ignore 2/g local g -- luacheck: pop local f, g -- luacheck: pop local f, g -- luacheck: push local function f() --luacheck: ignore -- luacheck: pop end -- luacheck: ignore 5 do end -- luacheck: enable 54 do end if false then end luacheck-0.25.0/spec/samples/line_length.lua000066400000000000000000000030511410451437000207440ustar00rootroot00000000000000-- luacheck: only 63 local aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ------------------------------------------------------------------------------------------------------------------------------------------ comments can be long, too -- luacheck: push no max code line length aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = "readable code" string = [[ looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong ]] normal = "still normal" -- luacheck: push max line length 40 this_overrides_code_line_specific_option = true -- luacheck: pop -- luacheck: pop aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = "unreadable" -- luacheck: max line length 80 -- luacheck: max string line length 100 -- luacheck: max comment line length 120 local i_code_in_ed_in_a_terminal_with_default_width_and_i_dont_like_long_lines = true local really = false string2 = [[kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk94 ]] string3 = [[kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk105 ]] code() -- comment kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk105 --[[ comment kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk125 ]] luacheck-0.25.0/spec/samples/python_code.lua000066400000000000000000000000371410451437000207700ustar00rootroot00000000000000from __future__ import braces luacheck-0.25.0/spec/samples/read_globals.lua000066400000000000000000000001731410451437000210740ustar00rootroot00000000000000string = "foo" table.append = table.insert _ENV = nil foo = "4"; print(foo) bar = "5"; print(bar) baz[4] = "6"; print(baz) luacheck-0.25.0/spec/samples/read_globals_inline_options.lua000066400000000000000000000002731410451437000242060ustar00rootroot00000000000000-- luacheck: read globals foo bar foo(bar, baz) foo, bar, baz, baz[1] = false, true, nil, 5 -- luacheck: ignore 111/foo 121/ba. -- luacheck: globals bar baz foo, bar, baz = 678, 829, 914 luacheck-0.25.0/spec/samples/redefined.lua000066400000000000000000000002761410451437000204070ustar00rootroot00000000000000local a = {} function a:b(...) local a, self = 4 do local a = (...)(a) each(a, function() local self = self[5]; return self.bar end) end print(a[1]) end return a luacheck-0.25.0/spec/samples/reversed_fornum.lua000066400000000000000000000000501410451437000216550ustar00rootroot00000000000000for i = #(...), -1.5 do print(i) end luacheck-0.25.0/spec/samples/sample.rockspec000066400000000000000000000002571410451437000207720ustar00rootroot00000000000000build = { type = "builtin", modules = { good = "spec/samples/good_code.lua", bad = "spec/samples/bad_code.lua", not_even_a_module = some_global } } luacheck-0.25.0/spec/samples/unused_code.lua000066400000000000000000000004201410451437000207460ustar00rootroot00000000000000local foo = {} function foo.bar(baz) for i=1, 5 do local q for a, b, c in pairs(foo) do print(4) end end end local x = 5 x = 6 x = 7; print(x) local y = 5; (function() print(y) end)() y = 6 local z = 5; (function() z = 4 end)() z = 6 luacheck-0.25.0/spec/samples/unused_secondaries.lua000066400000000000000000000002001410451437000223270ustar00rootroot00000000000000local function f() end local a, b = f() f(b) local x, y, z = f(), f() f(y) local t, o = {} o = 5 print(o) o, t[1] = f() f(t) luacheck-0.25.0/spec/samples/utf8.lua000066400000000000000000000004541410451437000173460ustar00rootroot00000000000000-- 嗨,你好嗎? math["분야 명"] = math["値"] --[[комментарий]] local t = { ["päällekkäinen nimi a​b"] = 1, ["päällekkäinen nimi a​b"] = 2 } -- 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 luacheck-0.25.0/spec/samples/utf8_error.lua000066400000000000000000000000611410451437000205510ustar00rootroot00000000000000-- 嗨,你好嗎? --[[GÆT]] ошибка =( luacheck-0.25.0/spec/serializer_spec.lua000066400000000000000000000065721410451437000202060ustar00rootroot00000000000000local serializer = require "luacheck.serializer" describe("serializer", function() describe("dump_result", function() -- luacheck: no max line length it("returns serialized result", function() assert.same( [[return {{{"111",5,100,102,"foo",{"faa"}},{"211",4,1,3,"bar",nil,true},{"011",nil,100000,nil,"near '\"'"}},{}}]], serializer.dump_check_result({ warnings = { {code = "111", name = "foo", indexing = {"faa"}, line = 5, column = 100, end_column = 102}, {code = "211", name = "bar", line = 4, column = 1, end_column = 3, secondary = true}, {code = "011", column = 100000, msg = "near '\"'"} }, inline_options = {} }) ) end) it("puts repeating string values into locals", function() assert.same( [[local A,B="111","foo";return {{{A,5,100,nil,B},{A,6,100,nil,B},{"011",nil,100000,nil,"near '\"'"}},{},{}}]], serializer.dump_check_result({ warnings = { {code = "111", name = "foo", line = 5, column = 100}, {code = "111", name = "foo", line = 6, column = 100, secondary = true}, {code = "011", column = 100000, msg = "near '\"'"} }, inline_options = {}, line_lengths = {} }) ) end) it("uses at most 52 locals", function() local warnings = {} local expected_parts1 = {"local A"} local expected_parts2 = {'="111"'} local expected_parts3 = {";return {{"} local function add_char(b) local c = string.char(b) table.insert(warnings, {code = "111", name = c}) table.insert(warnings, {code = "111", name = c}) table.insert(expected_parts1, "," .. c) table.insert(expected_parts2, ',"' .. c .. '"') table.insert(expected_parts3, ('{A,nil,nil,nil,%s},{A,nil,nil,nil,%s},'):format(c, c)) end local function add_extra(name) table.insert(warnings, {code = "111", name = name}) table.insert(warnings, {code = "111", name = name}) table.insert(expected_parts3, ('{A,nil,nil,nil,"%s"},{A,nil,nil,nil,"%s"},'):format(name, name)) end for b = ("B"):byte(), ("Z"):byte() do add_char(b) end for b = ("a"):byte(), ("z"):byte() do add_char(b) end add_extra("extra1") add_extra("extra2") local expected_part1 = table.concat(expected_parts1) local expected_part2 = table.concat(expected_parts2) local expected_part3 = table.concat(expected_parts3):sub(1, -2) local expected = expected_part1 .. expected_part2 .. expected_part3 .. "},{},{}}" assert.same(expected, serializer.dump_check_result({ warnings = warnings, inline_options = {}, line_lengths = {} }) ) end) it("handles error result", function() assert.same('return {{{"011",2,4,nil,"message"}},{},{}}', serializer.dump_check_result({ warnings = { {code = "011", line = 2, column = 4, msg = "message"} }, inline_options = {}, line_lengths = {} })) end) end) end) luacheck-0.25.0/spec/standards_spec.lua000066400000000000000000000215001410451437000200040ustar00rootroot00000000000000local standards = require "luacheck.standards" describe("standards", function() describe("validate_std_table", function() it("returns false and an error message if argument table has wrong field types", function() local ok, err = standards.validate_std_table({globals = "all of them"}) assert.is_false(ok) assert.equal("in field .globals: globals table expected, got string", err) ok, err = standards.validate_std_table({read_globals = "yes"}) assert.is_false(ok) assert.equal("in field .read_globals: globals table expected, got string", err) end) it("returns false and an error message if argument table has invalid definitions as values", function() local ok, err = standards.validate_std_table({globals = {foo = "bar"}}) assert.is_false(ok) assert.equal("in field .globals.foo: global description table expected, got string", err) end) it("returns false and an error message if argument table has invalid names as values", function() local ok, err = standards.validate_std_table({globals = {12345}}) assert.is_false(ok) assert.equal("in field .globals[1]: string expected as global name, got number", err) end) it("returns false and an error message if definition tables have wrong field types", function() local ok, err = standards.validate_std_table({globals = {foo = {read_only = "not_really"}}}) assert.is_false(ok) assert.equal("in field .globals.foo: invalid value of option 'read_only': boolean expected, got string", err) ok, err = standards.validate_std_table({read_globals = {bar = {other_fields = 0}}}) assert.is_false(ok) assert.equal( "in field .read_globals.bar: invalid value of option 'other_fields': boolean expected, got number", err) end) it("detects invalid nested definitions", function() local ok, err = standards.validate_std_table({globals = {foo = {fields = {bar = 12345}}}}) assert.is_false(ok) assert.equal("in field .globals.foo.fields.bar: field description table expected, got number", err) end) it("returns true if argument std table is valid", function() assert.is_true(standards.validate_std_table({})) assert.is_true(standards.validate_std_table({unrelated = 123})) assert.is_true(standards.validate_std_table( {globals = {"foo", bar = {read_only = true, other_fields = false}}} )) end) end) describe("add_std_table", function() it("adds two empty stds", function() local fstd = {} standards.add_std_table(fstd, {}) assert.same({}, fstd) end) describe("when merging trees", function() local tree local std before_each(function() tree = { fields = { foo = { read_only = false, other_fields = true, fields = { nested = {read_only = true} } } } } std = { read_globals = { foo = { other_fields = false, fields = { nested = {other_fields = true}, nested2 = {} } } }, globals = {"bar"} } end) it("merges in a tree", function() standards.add_std_table(tree, std) assert.same({ fields = { foo = { read_only = false, other_fields = true, fields = { nested = {read_only = true, other_fields = true}, nested2 = {} } }, bar = {read_only = false, other_fields = true} } }, tree) end) it("merges in a tree and overwrites fields with overwrite = true", function() standards.add_std_table(tree, std, true) assert.same({ fields = { foo = { read_only = true, other_fields = false, fields = { nested = {read_only = true, other_fields = true}, nested2 = {} } }, bar = {read_only = false, other_fields = true} } }, tree) end) it("can ignore top-level array part of std", function() standards.add_std_table(tree, std, true, true) assert.same({ fields = { foo = { read_only = true, other_fields = false, fields = { nested = {read_only = true, other_fields = true}, nested2 = {} } } } }, tree) end) end) end) describe("overwrite_field", function() it("adds definition of a field if it does not exist", function() local tree = { fields = { foo = {} } } standards.overwrite_field(tree, {"foo", "bar"}, false) assert.same({ fields = { foo = { fields = { bar = {other_fields = true, read_only = false} } } } }, tree) end) it("overwrites existing definitions", function() local tree = { fields = { foo = { fields = { bar = {other_fields = false, read_only = false, fields = {k = {}}} } } } } standards.overwrite_field(tree, {"foo", "bar"}, true) assert.same({ fields = { foo = { fields = { bar = {other_fields = true, read_only = true} } } } }, tree) end) end) describe("remove_field", function() it("removes definition of a field if it exists", function() local tree = { fields = { foo = { fields = { bar = {other_fields = false, read_only = false}, baz = {} } } } } standards.remove_field(tree, {"foo", "bar"}) assert.same({ fields = { foo = { fields = { baz = {} } } } }, tree) end) it("does nothing of definition does not exist already", function() local tree = { fields = { foo = { fields = { bar = {other_fields = false, read_only = false} } } } } standards.remove_field(tree, {"foo", "baz"}) assert.same({ fields = { foo = { fields = { bar = {other_fields = false, read_only = false} } } } }, tree) end) end) describe("finalize", function() it("annotates nodes without writable fields with deep_read_only = true", function() local tree = { read_only = true, fields = { foo = { read_only = false, fields = { nested = {other_fields = true} } }, bar = { fields = {one = {other_fields = true}, another = {}} } } } standards.finalize(tree) assert.same({ read_only = true, fields = { foo = { read_only = false, fields = { nested = {other_fields = true} } }, bar = { deep_read_only = true, fields = {one = {deep_read_only = true, other_fields = true}, another = {deep_read_only = true}} } } }, tree) end) end) describe("def_fields", function() it("returns a definition table containing empty fields with given names", function() assert.same({ fields = { foo = {}, bar = {} } }, standards.def_fields("foo", "bar")) end) end) end) luacheck-0.25.0/spec/unbalanced_assignments_spec.lua000066400000000000000000000026071410451437000225370ustar00rootroot00000000000000local helper = require "spec.helper" local function assert_warnings(warnings, src) assert.same(warnings, helper.get_stage_warnings("detect_unbalanced_assignments", src)) end describe("unbalanced assignment detection", function() it("detects unbalanced assignments", function() assert_warnings({ {code = "532", line = 4, column = 1, end_column = 8}, {code = "531", line = 5, column = 1, end_column = 14} }, [[ local a, b = 4; (...)(a) a, b = (...)(); (...)(a, b) a, b = 5; (...)(a, b) a, b = 1, 2, 3; (...)(a, b) local c, d ]]) end) it("detects unbalanced assignments in nested blocks and functions", function() assert_warnings({ {code = "532", line = 6, column = 10, end_column = 17}, {code = "532", line = 9, column = 13, end_column = 20}, {code = "532", line = 14, column = 22, end_column = 29}, {code = "531", line = 17, column = 25, end_column = 38} }, [[ do local a, b, c, d while x do if y then a, b = 1 else repeat a, b = 1 function t() for i = 1, 10 do for _, v in ipairs(tab) do a, b = 1 if c then a, b = 1, 2, 3 end end end end until z end end end ]]) end) end) luacheck-0.25.0/spec/uninit_accesses_spec.lua000066400000000000000000000043471410451437000212120ustar00rootroot00000000000000local helper = require "spec.helper" local function assert_warnings(warnings, src) assert.same(warnings, helper.get_stage_warnings("detect_uninit_accesses", src)) end describe("uninitalized access detection", function() it("detects accessing uninitialized variables", function() assert_warnings({ {code = "321", name = "a", line = 6, column = 12, end_column = 12} }, [[ local a if ... then a = 5 else a = get(a) end return a ]]) end) it("detects accessing uninitialized variables in unreachable functions", function() assert_warnings({ {code = "321", name = "a", line = 12, column = 20, end_column = 20} }, [[ return function() return function() do return end return function(x) local a if x then a = 1 return a + 2 else return a + 1 end end end end ]]) end) it("detects mutating uninitialized variables", function() assert_warnings({ {code = "341", name = "a", line = 4, column = 4, end_column = 4} }, [[ local a if ... then a.k = 5 else a = get(5) end return a ]]) end) it("detects accessing uninitialized variables in nested functions", function() assert_warnings({ {code = "321", name = "a", line = 7, column = 12, end_column = 12} }, [[ return function() return function(...) local a if ... then a = 5 else a = get(a) end return a end end ]]) end) it("handles accesses with no reaching values", function() assert_warnings({}, [[ local var = "foo" (...)(var) do return end (...)(var) ]]) end) it("handles upvalue accesses with no reaching values", function() assert_warnings({}, [[ local var = "foo" (...)(var) do return end (...)(function() return var end) ]]) end) it("handles upvalue accesses with no reaching values in a nested function", function() assert_warnings({}, [[ return function(...) local var = "foo" (...)(var) do return end (...)(function() return var end) end ]]) end) it("does not detect accessing unitialized variables incorrectly in loops", function() assert_warnings({}, [[ local a while not a do a = get() end return a ]]) end) end) luacheck-0.25.0/spec/unreachable_code_spec.lua000066400000000000000000000044461410451437000212760ustar00rootroot00000000000000local helper = require "spec.helper" local function assert_warnings(warnings, src) assert.same(warnings, helper.get_stage_warnings("detect_unreachable_code", src)) end describe("unreachable code detection", function() it("detects unreachable code", function() assert_warnings({ {code = "511", line = 2, column = 1, end_column = 24} }, [[ do return end if ... then return 6 end return 3 ]]) assert_warnings({ {code = "511", line = 7, column = 1, end_column = 11}, {code = "511", line = 13, column = 1, end_column = 8} }, [[ if ... then return 4 else return 6 end if ... then return 7 else return 8 end return 3 ]]) end) it("detects unreachable code with literal conditions", function() assert_warnings({ {code = "511", line = 4, column = 1, end_column = 6} }, [[ while true do (...)() end return ]]) assert_warnings({}, [[ repeat if ... then break end until false return ]]) assert_warnings({ {code = "511", line = 6, column = 1, end_column = 6} }, [[ repeat if nil then break end until false return ]]) end) it("detects unreachable expressions", function() assert_warnings({ {code = "511", line = 3, column = 7, end_column = 9} }, [[ repeat return until ... ]]) assert_warnings({ {code = "511", line = 3, column = 8, end_column = 10} }, [[ if true then (...)() elseif ... then (...)() end ]]) end) it("detects unreachable functions", function() assert_warnings({ {code = "511", line = 3, column = 1, end_column = 16} }, [[ local f = nil do return end function f() end ]]) end) it("detects unreachable code in nested function", function() assert_warnings({ {code = "511", line = 4, column = 7, end_column = 12} }, [[ return function() return function() do return end return end end ]]) end) it("detects unreachable code in unreachable nested function", function() assert_warnings({ {code = "511", line = 4, column = 4, end_column = 20}, {code = "511", line = 6, column = 7, end_column = 12} }, [[ return function() do return end return function() do return end return end end ]]) end) end) luacheck-0.25.0/spec/unused_fields_spec.lua000066400000000000000000000031301410451437000206510ustar00rootroot00000000000000local helper = require "spec.helper" local function assert_warnings(warnings, src) assert.same(warnings, helper.get_stage_warnings("detect_unused_fields", src)) end describe("unused field detection", function() it("detects unused fields in table literals", function() assert_warnings({ {code = "314", field = "key", line = 3, column = 5, end_column = 9, overwritten_line = 7, overwritten_column = 4, overwritten_end_column = 6}, {code = "314", field = "2", index = true, line = 6, column = 4, end_column = 4, overwritten_line = 9, overwritten_column = 5, overwritten_end_column = 9}, {code = "314", field = "key", line = 7, column = 4, end_column = 6, overwritten_line = 8, overwritten_column = 4, overwritten_end_column = 6}, {code = "314", field = "0.2e1", line = 9, column = 5, end_column = 9, overwritten_line = 10, overwritten_column = 5, overwritten_end_column = 5} }, [[ local x, y, z = 1, 2, 3 return { ["key"] = 4, [z] = 7, 1, y, key = x, key = 0, [0.2e1] = 6, [2] = 7 } ]]) end) it("detects unused fields in nested table literals", function() assert_warnings({ {code = "314", field = "a", line = 2, column = 5, end_column = 5, overwritten_line = 2, overwritten_column = 12, overwritten_end_column = 12}, {code = "314", field = "b", line = 3, column = 11, end_column = 11, overwritten_line = 3, overwritten_column = 18, overwritten_end_column = 18} }, [[ return { {a = 1, a = 2}, key = {b = 1, b = 2} } ]]) end) end) luacheck-0.25.0/spec/unused_locals_spec.lua000066400000000000000000000211431410451437000206640ustar00rootroot00000000000000local helper = require "spec.helper" local function assert_warnings(warnings, src) assert.same(warnings, helper.get_stage_warnings("detect_unused_locals", src)) end describe("unused locals detection", function() it("does not find anything wrong in used locals", function() assert_warnings({}, [[ local a local b = 5 a = 6 do print(b, {(a)}) end ]]) end) it("detects unused locals", function() assert_warnings({ {code = "211", name = "a", line = 1, column = 7, end_column = 7} }, [[ local a = 4 do local b = 6 print(b) end ]]) end) it("detects useless local _ variable", function() assert_warnings({ {code = "211", name = "_", useless = true, line = 2, column = 10, end_column = 10}, {code = "211", name = "_", useless = true, line = 7, column = 13, end_column = 13}, {code = "211", name = "_", secondary = true, line = 12, column = 13, end_column = 13} }, [[ do local _ end do local a = 5 local b, _ = a b() end do local c, _ = ... c() end ]]) end) it("reports unused function with forward declaration as variable, not value", function() assert_warnings({ {code = "211", name = "noop", func = true, line = 1, column = 22, end_column = 25} }, [[ local noop; function noop() end ]]) end) it("detects unused locals from function arguments", function() assert_warnings({ {code = "212", name = "foo", line = 1, column = 17, end_column = 19} }, [[ return function(foo, ...) return ... end ]]) end) it("detects unused implicit self", function() assert_warnings({ {code = "212", name = "self", self = true, line = 2, column = 11, end_column = 11} }, [[ local a = {} function a:b() end return a ]]) end) it("detects unused locals from loops", function() assert_warnings({ {code = "213", name = "i", line = 1, column = 5, end_column = 5}, {code = "213", name = "i", line = 2, column = 5, end_column = 5} }, [[ for i=1, 2 do end for i in pairs{} do end ]]) end) it("detects unused values", function() assert_warnings({ {code = "311", name = "a", line = 3, column = 4, end_column = 4, overwritten_line = 3, overwritten_column = 7, overwritten_end_column = 7}, {code = "311", name = "a", line = 3, column = 7, end_column = 7, overwritten_line = 8, overwritten_column = 1, overwritten_end_column = 1}, {code = "311", name = "a", line = 5, column = 4, end_column = 4, overwritten_line = 8, overwritten_column = 1, overwritten_end_column = 1} }, [[ local a if ... then a, a = 2, 4 else a = 3 end a = 5 return a ]]) end) it("does not provide overwriting location if value can reach end of scope", function() assert_warnings({ {code = "311", name = "a", line = 4, column = 4, end_column = 4}, {code = "311", name = "a", line = 7, column = 7, end_column = 7} }, [[ do local a = 1 (...)(a) a = 2 if ... then a = 3 end end ]]) end) it("does not provide overwriting location if the value overwrites itself", function() assert_warnings({ {code = "311", name = "a", line = 5, column = 4, end_column = 4} }, [[ local a = 1 print(a) while true do a = 2 end ]]) end) it("does not detect unused value when it and a closure using it can live together", function() assert_warnings({}, [[ local a = 3 if true then escape(function() return a end) end ]]) end) it("does not consider value assigned to upvalue as unused if it is accessed in another closure", function() assert_warnings({}, [[ local a local function f(x) a = x end local function g() return a end return f, g ]]) end) it("does not consider a variable initialized if it can't get a value due to short rhs", function() assert_warnings({}, [[ local a, b = "foo" b = "bar" return a, b ]]) end) it("considers a variable initialized if short rhs ends with potential multivalue", function() assert_warnings({ {code = "311", name = "b", line = 2, column = 13, end_column = 13, secondary = true, overwritten_line = 3, overwritten_column = 4, overwritten_end_column = 4} }, [[ return function(...) local a, b = ... b = "bar" return a, b end ]]) end) it("reports unused variable as secondary if it is assigned together with a used one", function() assert_warnings({ {code = "211", name = "a", line = 2, column = 10, end_column = 10, secondary = true} }, [[ return function(f) local a, b = f() return b end ]]) end) it("reports unused value as secondary if it is assigned together with a used one", function() assert_warnings({ {code = "231", name = "a", line = 2, column = 10, end_column = 10, secondary = true} }, [[ return function(f) local a, b a, b = f() return b end ]]) assert_warnings({ {code = "231", name = "a", line = 2, column = 10, end_column = 10, secondary = true} }, [[ return function(f, t) local a a, t[1] = f() end ]]) end) it("detects variable that is mutated but never accessed", function() assert_warnings({ {code = "241", name = "a", line = 1, column = 7, end_column = 7} }, [[ local a = {} a.k = 1 ]]) assert_warnings({ {code = "241", name = "a", line = 1, column = 7, end_column = 7} }, [[ local a if ... then a = {} a.k1 = 1 else a = {} a.k2 = 2 end ]]) assert_warnings({ {code = "241", name = "a", line = 1, column = 7, end_column = 7}, {code = "311", name = "a", line = 7, column = 4, end_column = 4} }, [[ local a if ... then a = {} a.k1 = 1 else a = {} end ]]) end) it("detects values that are mutated but never accessed", function() assert_warnings({ {code = "331", name = "a", line = 5, column = 4, end_column = 4} }, [[ local a local b = (...).k if (...)[1] then a = {} a.k1 = 1 elseif (...)[2] then a = b a.k2 = 2 elseif (...)[3] then a = b() a.k3 = 3 elseif (...)[4] then a = b(1) or b(2) a.k4 = 4 else a = {} return a end ]]) end) it("detects unset variables", function() assert_warnings({ {code = "221", name = "a", line = 1, column = 7, end_column = 7} }, [[ local a return a ]]) end) end) describe("unused recurisve function detection", function() it("detects unused recursive functions", function() assert_warnings({ {code = "211", name = "f", func = true, recursive = true, line = 1, column = 16, end_column = 16} }, [[ local function f(x) return x <= 1 and 1 or x * f(x - 1) end ]]) end) it("handles functions defined without a local value", function() assert_warnings({}, [[ print(function() return function() end end) ]]) end) it("detects unused mutually recursive functions", function() assert_warnings({ {code = "211", name = "odd", func = true, mutually_recursive = true, line = 3, column = 16, end_column = 18}, {code = "211", name = "even", func = true, mutually_recursive = true, line = 7, column = 10, end_column = 13} }, [[ local even local function odd(x) return x == 1 or even(x - 1) end function even(x) return x == 0 or odd(x - 1) end ]]) end) it("detects unused mutually recursive functions as values", function() assert_warnings({ {code = "311", name = "odd", line = 5, column = 10, end_column = 12}, {code = "311", name = "even", line = 9, column = 10, end_column = 13} }, [[ local even = 2 local odd = 3 (...)(even, odd) function odd(x) return x == 1 or even(x - 1) end function even(x) return x == 0 or odd(x - 1) or even(x) end ]]) end) it("does not incorrectly detect unused recursive functions inside unused functions", function() assert_warnings({ {code = "211", name = "unused", func = true, line = 1, column = 16, end_column = 21} }, [[ local function unused() local function nested1() end local function nested2() nested2() end return nested1(), nested2() end ]]) end) it("does not incorrectly detect unused recursive functions used by an unused recursive function", function() assert_warnings({ {code = "211", name = "g", func = true, recursive = true, line = 2, column = 16, end_column = 16} }, [[ local function f() return 1 end local function g() return f() + g() end ]]) assert_warnings({ {code = "211", name = "g", func = true, recursive = true, line = 2, column = 16, end_column = 16} }, [[ local f local function g() return f() + g() end function f() return 1 end ]]) end) end) luacheck-0.25.0/spec/utils_spec.lua000066400000000000000000000156251410451437000171740ustar00rootroot00000000000000local utils = require "luacheck.utils" describe("utils", function() describe("read_file", function() it("returns contents of a file", function() assert.match("contents\r?\n", utils.read_file("spec/folder/foo")) end) it("removes UTF-8 BOM", function() assert.match("foo\r?\nbar\r?\n", utils.read_file("spec/folder/bom")) end) it("returns nil for non-existent paths", function() assert.is_nil(utils.read_file("spec/folder/non-existent")) end) it("returns nil for directories", function() assert.is_nil(utils.read_file("spec/folder")) end) end) describe("load", function() it("loads function in an environment", function() local f = utils.load("return g", {g = "foo"}) assert.is_function(f) assert.is_equal("foo", f()) end) it("returns nil on syntax error", function() assert.is_nil(utils.load("return return", {})) end) end) describe("load_config", function() it("loads config from a file and returns it", function() assert.same({foo = "bar"}, (utils.load_config("spec/folder/config"))) end) it("passes second argument as environment", function() local function bar() return "bar" end assert.same({ foo = "bar", bar = bar }, (utils.load_config("spec/folder/env_config", {bar = bar}))) end) it("returns nil, \"I/O\" for non-existent paths", function() local ok, err = utils.load_config("spec/folder/non-existent") assert.is_nil(ok) assert.equal("I/O", err) end) it("returns nil, \"syntax\" for configs with syntax errors", function() local ok, err = utils.load_config("spec/folder/bad_config") assert.is_nil(ok) assert.equal("syntax", err) end) it("returns nil, \"runtime\" for configs with run-time errors", function() local ok, err = utils.load_config("spec/folder/env_config") assert.is_nil(ok) assert.equal("runtime", err) end) end) describe("array_to_set", function() it("converts array to set and returns it", function() assert.same({foo = 3, bar = 2}, utils.array_to_set({"foo", "bar", "foo"})) end) end) describe("concat_arrays", function() it("returns concatenated arrays", function() assert.same({1, 2, 3, 4}, utils.concat_arrays({{}, {1}, {2, 3, 4}, {}})) end) end) describe("update", function() it("updates first table with entries from second", function() local t1 = {k1 = 1, k2 = 2} local t2 = {k2 = 3, k3 = 4} local ret = utils.update(t1, t2) assert.same({k1 = 1, k2 = 3, k3 = 4}, t1) assert.equal(t1, ret) end) end) describe("class", function() it("returns an object creator", function() local cl = utils.class() assert.is_table(cl) cl.field = "foo" local obj = cl() assert.is_table(obj) obj.field2 = "bar" assert.equal("foo", obj.field) assert.is_nil(cl.field2) end) it("calls __init on object creation", function() local cl = utils.class() cl.__init = spy.new(function() end) local obj = cl("foo", "bar") assert.spy(cl.__init).was_called(1) assert.spy(cl.__init).was_called_with(obj, "foo", "bar") end) end) describe("Stack", function() it("supports push/pop operations and top/size fields", function() local stack = utils.Stack() assert.equal(0, stack.size) assert.is_nil(stack.top) stack:push(7) stack:push(8) assert.equal(2, stack.size) assert.equal(8, stack.top) assert.equal(8, stack:pop()) assert.equal(1, stack.size) assert.equal(7, stack.top) stack:push(4) assert.equal(2, stack.size) assert.equal(4, stack.top) assert.equal(4, stack:pop()) assert.equal(7, stack:pop()) assert.equal(0, stack.size) assert.is_nil(stack.top) end) end) describe("try", function() it("returns true, original return values on success", function() local ok, ret1, ret2 = utils.try(function(x, y) return x*2, y*2 end, 1, 2) assert.is_true(ok) assert.equal(2, ret1) assert.equal(4, ret2) end) it("returns false, error wrapper on error", function() local ok, res = utils.try(function() error("foo", 0) end) assert.is_false(ok) assert.table(res) assert.equal(res.err, "foo") assert.string(res.traceback) end) it("does not wrap already wrapped errors", function() local orig_traceback local ok, res = utils.try(function() local _, orig_res = utils.try(function() error("foo", 0) end) orig_traceback = orig_res.traceback error(orig_res, 0) end) assert.is_false(ok) assert.table(res) assert.equal(res.err, "foo") assert.string(res.traceback) assert.equal(res.traceback, orig_traceback) end) end) describe("ripairs", function() it("returns reversed ipairs", function() local arr = {foo = "bar", 5, 6, 7} local iterated = {} for i, v in utils.ripairs(arr) do table.insert(iterated, {i, v}) end assert.same({{3, 7}, {2, 6}, {1, 5}}, iterated) end) end) describe("sorted_pairs", function() it("returns sorted pairs", function() local t = {foo = 1, bar = 3, baz = 5, zero = 0, something = "nothing"} local iterated = {} for k, v in utils.sorted_pairs(t) do table.insert(iterated, {k, v}) end assert.same({{"bar", 3}, {"baz", 5}, {"foo", 1}, {"something", "nothing"}, {"zero", 0}}, iterated) end) end) describe("after", function() it("returns substring after match", function() assert.equal("foo bar: baz", utils.after("bar: foo bar: baz", "bar:%s*")) end) it("returns nil when there is no match", function() assert.is_nil(utils.after("bar: foo bar: baz", "baz:%s*")) end) end) describe("strip", function() it("returns string without whitespace on ends", function() assert.equal("foo bar", utils.strip("\tfoo bar\n ")) end) end) describe("split", function() it("without separator, returns non-whitespace substrings", function() assert.same({"foo", "bar", "baz"}, utils.split(" foo bar\n baz ")) end) it("with separator, returns substrings between them", function() assert.same({"", "foo", " bar", "", " baz "}, utils.split(",foo, bar,, baz ", ",")) end) end) describe("map", function() it("maps function over an array", function() assert.same({3, 1, 2}, utils.map(math.sqrt, {9, 1, 4})) end) end) end) luacheck-0.25.0/src/000077500000000000000000000000001410451437000141435ustar00rootroot00000000000000luacheck-0.25.0/src/luacheck/000077500000000000000000000000001410451437000157225ustar00rootroot00000000000000luacheck-0.25.0/src/luacheck/builtin_standards/000077500000000000000000000000001410451437000214335ustar00rootroot00000000000000luacheck-0.25.0/src/luacheck/builtin_standards/init.lua000066400000000000000000000241141410451437000231030ustar00rootroot00000000000000local love = require "luacheck.builtin_standards.love" local ngx = require "luacheck.builtin_standards.ngx" local standards = require "luacheck.standards" local builtin_standards = {} local function def_to_std(def) return {read_globals = def.fields} end local function add_defs(...) local res = {} for _, def in ipairs({...}) do standards.add_std_table(res, def_to_std(def)) end return res end local empty = {} local string_defs = {} string_defs.min = standards.def_fields("byte", "char", "dump", "find", "format", "gmatch", "gsub", "len", "lower", "match", "rep", "reverse", "sub", "upper") string_defs.lua51 = add_defs(string_defs.min, standards.def_fields("gfind")) string_defs.lua52 = string_defs.min string_defs.lua53 = add_defs(string_defs.min, standards.def_fields("pack", "packsize", "unpack")) string_defs.lua54 = string_defs.lua53 string_defs.luajit = string_defs.lua51 local file_defs = {} file_defs.min = { fields = { __gc = empty, __index = {other_fields = true}, __tostring = empty, close = empty, flush = empty, lines = empty, read = empty, seek = empty, setvbuf = empty, write = empty } } file_defs.lua51 = file_defs.min file_defs.lua52 = file_defs.min file_defs.lua53 = add_defs(file_defs.min, {fields = {__name = string_defs.lua53}}) file_defs.lua54 = add_defs(file_defs.min, {fields = {__name = string_defs.lua54}}) file_defs.luajit = file_defs.min local function make_min_def(method_defs) local string_def = string_defs[method_defs] local file_def = file_defs[method_defs] return { fields = { _G = {other_fields = true, read_only = false}, _VERSION = string_def, arg = {other_fields = true}, assert = empty, collectgarbage = empty, coroutine = standards.def_fields("create", "resume", "running", "status", "wrap", "yield"), debug = standards.def_fields("debug", "gethook", "getinfo", "getlocal", "getmetatable", "getregistry", "getupvalue", "sethook", "setlocal", "setmetatable", "setupvalue", "traceback"), dofile = empty, error = empty, getmetatable = empty, io = { fields = { close = empty, flush = empty, input = empty, lines = empty, open = empty, output = empty, popen = empty, read = empty, stderr = file_def, stdin = file_def, stdout = file_def, tmpfile = empty, type = empty, write = empty } }, ipairs = empty, load = empty, loadfile = empty, math = standards.def_fields("abs", "acos", "asin", "atan", "ceil", "cos", "deg", "exp", "floor", "fmod", "huge", "log", "max", "min", "modf", "pi", "rad", "random", "randomseed", "sin", "sqrt", "tan"), next = empty, os = standards.def_fields("clock", "date", "difftime", "execute", "exit", "getenv", "remove", "rename", "setlocale", "time", "tmpname"), package = { fields = { config = string_def, cpath = {fields = string_def.fields, read_only = false}, loaded = {other_fields = true, read_only = false}, loadlib = empty, path = {fields = string_def.fields, read_only = false}, preload = {other_fields = true, read_only = false} } }, pairs = empty, pcall = empty, print = empty, rawequal = empty, rawget = empty, rawset = empty, require = empty, select = empty, setmetatable = empty, string = string_def, table = standards.def_fields("concat", "insert", "remove", "sort"), tonumber = empty, tostring = empty, type = empty, xpcall = empty } } end local bit32_def = standards.def_fields("arshift", "band", "bnot", "bor", "btest", "bxor", "extract", "lrotate", "lshift", "replace", "rrotate", "rshift") local lua_defs = {} lua_defs.min = make_min_def("min") lua_defs.lua51 = add_defs(make_min_def("lua52"), { fields = { debug = standards.def_fields("getfenv", "setfenv"), getfenv = empty, loadstring = empty, math = standards.def_fields("atan2", "cosh", "frexp", "ldexp", "log10", "pow", "sinh", "tanh"), module = empty, newproxy = empty, package = { fields = { loaders = {other_fields = true, read_only = false}, seeall = empty } }, setfenv = empty, table = standards.def_fields("maxn"), unpack = empty } }) lua_defs.lua51c = add_defs(lua_defs.lua51, make_min_def("lua51"), { fields = { gcinfo = empty, math = standards.def_fields("mod"), table = standards.def_fields("foreach", "foreachi", "getn", "setn") } }) lua_defs.lua52 = add_defs(make_min_def("lua52"), { fields = { _ENV = {other_fields = true, read_only = false}, bit32 = bit32_def, debug = standards.def_fields("getuservalue", "setuservalue", "upvalueid", "upvaluejoin"), math = standards.def_fields("atan2", "cosh", "frexp", "ldexp", "pow", "sinh", "tanh"), package = { fields = { searchers = {other_fields = true, read_only = false}, searchpath = empty } }, rawlen = empty, table = standards.def_fields("pack", "unpack") } }) lua_defs.lua52c = add_defs(lua_defs.lua52, { fields = { loadstring = empty, math = standards.def_fields("log10"), module = empty, package = { fields = { loaders = {other_fields = true, read_only = false}, seeall = empty } }, table = standards.def_fields("maxn"), unpack = empty } }) lua_defs.lua53 = add_defs(make_min_def("lua53"), { fields = { _ENV = {other_fields = true, read_only = false}, coroutine = standards.def_fields("isyieldable"), debug = standards.def_fields("getuservalue", "setuservalue", "upvalueid", "upvaluejoin"), math = standards.def_fields("maxinteger", "mininteger", "tointeger", "type", "ult"), package = { fields = { searchers = {other_fields = true, read_only = false}, searchpath = empty } }, rawlen = empty, table = standards.def_fields("move", "pack", "unpack"), utf8 = { fields = { char = empty, charpattern = string_defs.lua53, codepoint = empty, codes = empty, len = empty, offset = empty } } } }) lua_defs.lua53c = add_defs(lua_defs.lua53, { fields = { bit32 = bit32_def, math = standards.def_fields("atan2", "cosh", "frexp", "ldexp", "log10", "pow", "sinh", "tanh") } }) lua_defs.lua54 = add_defs(lua_defs.lua53, { fields = { warn = empty, debug = standards.def_fields("setcstacklimit"), coroutine = standards.def_fields("close"), } }) lua_defs.lua54c = add_defs(lua_defs.lua54, { fields = { math = standards.def_fields("atan2", "cosh", "frexp", "ldexp", "log10", "pow", "sinh", "tanh") } }) lua_defs.luajit = add_defs(make_min_def("luajit"), { fields = { bit = standards.def_fields("arshift", "band", "bnot", "bor", "bswap", "bxor", "lshift", "rol", "ror", "rshift", "tobit", "tohex"), coroutine = standards.def_fields("isyieldable"), debug = standards.def_fields("getfenv", "setfenv", "upvalueid", "upvaluejoin"), gcinfo = empty, getfenv = empty, jit = {other_fields = true}, loadstring = empty, math = standards.def_fields("atan2", "cosh", "frexp", "ldexp", "log10", "mod", "pow", "sinh", "tanh"), module = empty, newproxy = empty, package = { fields = { loaders = {other_fields = true, read_only = false}, searchpath = empty, seeall = empty } }, setfenv = empty, table = standards.def_fields("clear", "foreach", "foreachi", "getn", "maxn", "move", "new"), unpack = empty } }) lua_defs.ngx_lua = add_defs(lua_defs.luajit, ngx) lua_defs.max = add_defs(lua_defs.lua51c, lua_defs.lua52c, lua_defs.lua53c, lua_defs.lua54c, lua_defs.luajit) for name, def in pairs(lua_defs) do builtin_standards[name] = def_to_std(def) end local function get_running_lua_std_name() if rawget(_G, "jit") then return "luajit" elseif _VERSION == "Lua 5.1" then return "lua51c" elseif _VERSION == "Lua 5.2" then return "lua52c" elseif _VERSION == "Lua 5.3" then return "lua53c" elseif _VERSION == "Lua 5.4" then return "lua54c" else return "max" end end builtin_standards._G = builtin_standards[get_running_lua_std_name()] builtin_standards.busted = { read_globals = { "describe", "insulate", "expose", "it", "pending", "before_each", "after_each", "match", "lazy_setup", "lazy_teardown", "strict_setup", "strict_teardown", "setup", "teardown", "context", "spec", "test", "assert", "spy", "mock", "stub", "finally", "randomize" } } builtin_standards.love = love builtin_standards.rockspec = { globals = { "rockspec_format", "package", "version", "description", "dependencies", "supported_platforms", "external_dependencies", "source", "build", "hooks", "deploy", "build_dependencies", "test_dependencies", "test" } } builtin_standards.luacheckrc = { globals = { "global", "unused", "redefined", "unused_args", "unused_secondaries", "self", "compat", "allow_defined", "allow_defined_top", "module", "globals", "read_globals", "new_globals", "new_read_globals", "not_globals", "ignore", "enable", "only", "std", "max_line_length", "max_code_line_length", "max_string_line_length", "max_comment_line_length", "max_cyclomatic_complexity", "quiet", "color", "codes", "ranges", "formatter", "cache", "jobs", "files", "stds", "exclude_files", "include_files" } } builtin_standards.none = {} return builtin_standards luacheck-0.25.0/src/luacheck/builtin_standards/love.lua000066400000000000000000000160501410451437000231050ustar00rootroot00000000000000local standards = require "luacheck.standards" local empty = {} local read_write = {read_only = false} local love = { fields = { getVersion = empty, conf = read_write, directorydropped = read_write, draw = read_write, errhand = read_write, errorhandler = read_write, filedropped = read_write, focus = read_write, gamepadaxis = read_write, gamepadpressed = read_write, gamepadreleased = read_write, handlers = read_write, hasDeprecationOutput = empty, joystickadded = read_write, joystickaxis = read_write, joystickhat = read_write, joystickpressed = read_write, joystickreleased = read_write, joystickremoved = read_write, keypressed = read_write, keyreleased = read_write, load = read_write, lowmemory = read_write, mousefocus = read_write, mousemoved = read_write, mousepressed = read_write, mousereleased = read_write, quit = read_write, resize = read_write, run = read_write, setDeprecationOutput = empty, textedited = read_write, textinput = read_write, threaderror = read_write, touchmoved = read_write, touchpressed = read_write, touchreleased = read_write, update = read_write, visible = read_write, wheelmoved = read_write, audio = standards.def_fields("getDistanceModel","getDopplerScale","getSourceCount","getOrientation", "getPosition","getVelocity","getVolume","newSource","pause","play","setDistanceModel","setDopplerScale", "setOrientation","setPosition","setVelocity", "setVolume","stop","getActiveSourceCount","getRecordingDevices", "newQueueableSource","setEffect","getActiveEffects","getEffect","getMaxSceneEffects","getMaxSourceEffects", "isEffectsSupported","setMixWithSystem"), data = standards.def_fields("compress","decode","decompress","encode","getPackedSize","hash","newByteData", "newDataView","pack","unpack"), event = standards.def_fields("clear","poll","pump","push","quit","wait"), filesystem = standards.def_fields("append","areSymlinksEnabled","createDirectory","exists", "getAppdataDirectory","getDirectoryItems","getIdentity","getLastModified", "getRealDirectory","getRequirePath","getSaveDirectory","getSize","getSource", "getSourceBaseDirectory","getUserDirectory","getWorkingDirectory","init","isDirectory", "isFile","isFused","isSymlink","lines","load","mount","newFile","newFileData","read", "remove","setIdentity","setRequirePath","setSource","setSymlinksEnabled","unmount","write", "getInfo","setCRequirePath","getCRequirePath"), font = standards.def_fields("newImageRasterizer", "newGlyphData", "newTrueTypeRasterizer", "newBMFontRasterizer", "newRasterizer"), graphics = standards.def_fields("arc","circle","clear","discard","draw","ellipse","getBackgroundColor", "getBlendMode","getCanvas","getCanvasFormats","getColor","getColorMask", "getDefaultFilter","getDimensions","getFont","getHeight", "getLineJoin","getLineStyle","getLineWidth","getShader","getStats","getStencilTest", "getSupported","getSystemLimits","getPointSize","getRendererInfo","getScissor","getWidth", "intersectScissor","isGammaCorrect","isWireframe","line","newCanvas","newFont","newMesh", "newImage","newImageFont","newParticleSystem","newShader","newText","newQuad", "newSpriteBatch","newVideo","origin","points","polygon","pop","present", "print","printf","push","rectangle","reset","rotate","scale","setBackgroundColor", "setBlendMode","setCanvas","setColor","setColorMask","setDefaultFilter","setFont", "setLineJoin","setLineStyle","setLineWidth","setNewFont","setShader","setPointSize", "setScissor","setStencilTest","setWireframe","shear","stencil","translate", "captureScreenshot","getImageFormats","newArrayImage","newVolumeImage","newCubeImage", "getTextureTypes","drawLayer","setDepthMode","setMeshCullMode","setFrontFaceWinding", "applyTransform","replaceTransform","transformPoint","inverseTransformPoint","getStackDepth", "flushBatch","validateShader","drawInstanced","getDepthMode","getFrontFaceWinding","getMeshCullMode", "getDPIScale","getPixelDimensions","getPixelHeight","getPixelWidth","isActive","getDefaultMipmapFilter", "setDefaultMipmapFilter"), image = standards.def_fields("isCompressed","newCompressedData","newImageData","newCubeFaces"), joystick = standards.def_fields("getJoystickCount","getJoysticks","loadGamepadMappings", "saveGamepadMappings","setGamepadMapping"), keyboard = standards.def_fields("getKeyFromScancode","getScancodeFromKey","hasKeyRepeat","hasTextInput", "isDown","isScancodeDown","setKeyRepeat","setTextInput","hasScreenKeyboard"), math = standards.def_fields("compress","decompress","gammaToLinear","getRandomSeed","getRandomState", "isConvex","linearToGamma","newBezierCurve","newRandomGenerator","noise","random", "randomNormal","setRandomSeed","setRandomState","triangulate","newTransform"), mouse = standards.def_fields("getCursor","getPosition","getRelativeMode","getSystemCursor","getX", "getY","hasCursor","isDown","isGrabbed","isVisible","newCursor","setCursor","setGrabbed", "setPosition","setRelativeMode","setVisible","setX","setY","isCursorSupported"), physics = standards.def_fields("getDistance","getMeter","newBody","newChainShape","newCircleShape", "newDistanceJoint","newEdgeShape","newFixture","newFrictionJoint","newGearJoint", "newMotorJoint","newMouseJoint","newPolygonShape","newPrismaticJoint","newPulleyJoint", "newRectangleShape","newRevoluteJoint","newRopeJoint","newWeldJoint","newWheelJoint", "newWorld","setMeter"), sound = standards.def_fields("newDecoder","newSoundData"), system = standards.def_fields("getClipboardText","getOS","getPowerInfo","getProcessorCount","openURL", "setClipboardText","vibrate","hasBackgroundMusic"), thread = standards.def_fields("getChannel","newChannel","newThread"), timer = standards.def_fields("getAverageDelta","getDelta","getFPS","getTime","sleep","step"), touch = standards.def_fields("getPosition","getPressure","getTouches"), video = standards.def_fields("newVideoStream"), window = standards.def_fields("close","fromPixels","getDisplayName","getFullscreen", "getFullscreenModes","getIcon","getMode","getPixelScale","getPosition","getTitle", "hasFocus","hasMouseFocus","isDisplaySleepEnabled","isMaximized","isOpen","isVisible", "maximize","minimize","requestAttention","setDisplaySleepEnabled","setFullscreen", "setIcon","setMode","setPosition","setTitle","showMessageBox","toPixels", "updateMode","isMinimized","restore","getDPIScale") } } -- `love` standard contains only `love` global, so return it here directly using normal std format. return { read_globals = {love = love} } luacheck-0.25.0/src/luacheck/builtin_standards/ngx.lua000066400000000000000000000130701410451437000227330ustar00rootroot00000000000000local standards = require "luacheck.standards" local empty = {} local luajit_string_def = standards.def_fields("byte", "char", "dump", "find", "format", "gmatch", "gsub", "len", "lower", "match", "rep", "reverse", "sub", "upper") -- Globals added by lua-nginx-module 0.10.10 in internal definition table format. -- Will be added to `luajit` std to form `ngx_lua` std. local ngx_defs = { fields = { ngx = { fields = { arg = {other_fields = true, read_only = false}, var = {other_fields = true, read_only = false}, OK = empty, ERROR = empty, AGAIN = empty, DONE = empty, DECLINED = empty, null = empty, HTTP_GET = empty, HTTP_HEAD = empty, HTTP_PUT = empty, HTTP_POST = empty, HTTP_DELETE = empty, HTTP_OPTIONS = empty, HTTP_MKCOL = empty, HTTP_COPY = empty, HTTP_MOVE = empty, HTTP_PROPFIND = empty, HTTP_PROPPATCH = empty, HTTP_LOCK = empty, HTTP_UNLOCK = empty, HTTP_PATCH = empty, HTTP_TRACE = empty, HTTP_CONTINUE = empty, HTTP_SWITCHING_PROTOCOLS = empty, HTTP_OK = empty, HTTP_CREATED = empty, HTTP_ACCEPTED = empty, HTTP_NO_CONTENT = empty, HTTP_PARTIAL_CONTENT = empty, HTTP_SPECIAL_RESPONSE = empty, HTTP_MOVED_PERMANENTLY = empty, HTTP_MOVED_TEMPORARILY = empty, HTTP_SEE_OTHER = empty, HTTP_NOT_MODIFIED = empty, HTTP_TEMPORARY_REDIRECT = empty, HTTP_BAD_REQUEST = empty, HTTP_UNAUTHORIZED = empty, HTTP_PAYMENT_REQUIRED = empty, HTTP_FORBIDDEN = empty, HTTP_NOT_FOUND = empty, HTTP_NOT_ALLOWED = empty, HTTP_NOT_ACCEPTABLE = empty, HTTP_REQUEST_TIMEOUT = empty, HTTP_CONFLICT = empty, HTTP_GONE = empty, HTTP_UPGRADE_REQUIRED = empty, HTTP_TOO_MANY_REQUESTS = empty, HTTP_CLOSE = empty, HTTP_ILLEGAL = empty, HTTP_INTERNAL_SERVER_ERROR = empty, HTTP_METHOD_NOT_IMPLEMENTED = empty, HTTP_BAD_GATEWAY = empty, HTTP_SERVICE_UNAVAILABLE = empty, HTTP_GATEWAY_TIMEOUT = empty, HTTP_VERSION_NOT_SUPPORTED = empty, HTTP_INSUFFICIENT_STORAGE = empty, STDERR = empty, EMERG = empty, ALERT = empty, CRIT = empty, ERR = empty, WARN = empty, NOTICE = empty, INFO = empty, DEBUG = empty, ctx = {other_fields = true, read_only = false}, location = standards.def_fields("capture", "capture_multi"), status = {read_only = false}, header = {other_fields = true, read_only = false}, resp = standards.def_fields("get_headers"), req = standards.def_fields("is_internal", "start_time", "http_version", "raw_header", "get_method", "set_method", "set_uri", "set_uri_args", "get_uri_args", "get_post_args", "get_headers", "set_header", "clear_header", "read_body", "discard_body", "get_body_data", "get_body_file", "set_body_data", "set_body_file", "init_body", "append_body", "finish_body", "socket"), exec = empty, redirect = empty, send_headers = empty, headers_sent = empty, print = empty, say = empty, log = empty, flush = empty, exit = empty, eof = empty, sleep = empty, escape_uri = empty, unescape_uri = empty, encode_args = empty, decode_args = empty, encode_base64 = empty, decode_base64 = empty, crc32_short = empty, crc32_long = empty, hmac_sha1 = empty, md5 = empty, md5_bin = empty, sha1_bin = empty, quote_sql_str = empty, today = empty, time = empty, now = empty, update_time = empty, localtime = empty, utctime = empty, cookie_time = empty, http_time = empty, parse_http_time = empty, is_subrequest = empty, re = standards.def_fields("match", "find", "gmatch", "sub", "gsub"), shared = {other_fields = true, read_only = false}, socket = standards.def_fields("udp", "tcp", "connect", "stream"), get_phase = empty, thread = standards.def_fields("spawn", "wait", "kill"), on_abort = empty, timer = standards.def_fields("at", "every", "running_count", "pending_count"), config = { fields = { subsystem = luajit_string_def, debug = empty, prefix = empty, nginx_version = empty, nginx_configure = empty, ngx_lua_version = empty, } }, worker = standards.def_fields("pid", "count", "id", "exiting"), }, }, ndk = { fields = { set_var = {other_fields = true}, }, }, table = standards.def_fields("clear", "clone", "isarray", "isempty", "nkeys"), thread = standards.def_fields("exdata"), }, } return ngx_defs luacheck-0.25.0/src/luacheck/cache.lua000066400000000000000000000070641410451437000174770ustar00rootroot00000000000000local fs = require "luacheck.fs" local serializer = require "luacheck.serializer" local sha1 = require "luacheck.vendor.sha1" local utils = require "luacheck.utils" local cache = {} -- Check results can be cached inside a given cache directory. -- Check result for a file is stored in `/`. -- Cache file format: \n\n= cache_mtime then return end local fh = io.open(cache_filename, "rb") if not fh then return end if fh:read() ~= format_version then fh:close() return end if fh:read() ~= normalized_filename then fh:close() return end local serialized_result = fh:read("*a") fh:close() if not serialized_result then return nil, true end local result = serializer.load_check_result(serialized_result) if not result then return nil, true end return result end function cache.new(cache_directory) return Cache(cache_directory) end return cache luacheck-0.25.0/src/luacheck/check.lua000066400000000000000000000060751410451437000175120ustar00rootroot00000000000000local check_state = require "luacheck.check_state" local core_utils = require "luacheck.core_utils" local parse_inline_options = require "luacheck.stages.parse_inline_options" local parser = require "luacheck.parser" local stages = require "luacheck.stages" local utils = require "luacheck.utils" local inline_option_fields = utils.array_to_set(parse_inline_options.inline_option_fields) local function validate_fields(tables, per_code_fields) for _, t in ipairs(tables) do local fields_set if per_code_fields then if not t.code then error("Warning has no code", 0) end local warning_info = stages.warnings[t.code] if not warning_info then error("Unknown issue code " .. t.code, 0) end fields_set = warning_info.fields_set else fields_set = inline_option_fields end for field in pairs(t) do if not fields_set[field] then error("Unknown field " .. field .. " in " .. (per_code_fields and "issue with code " .. t.code or "inline option table"), 0) end end end end --- Checks source. -- Returns a table with results, with the following fields: -- `events`: array of issues and inline option events (options, push, or pop). -- `per_line_options`: map from line numbers to arrays of inline option events. -- `line_lengths`: map from line numbers to line lengths. -- `line_endings`: map from line numbers to "comment", "string", or `nil` base on -- whether the line ending is within a token. -- If `events` array contains a syntax error, the other fields are empty tables. local function check(source) local chstate = check_state.new(source) local ok, error_wrapper = utils.try(stages.run, chstate) local warnings, inline_options, line_lengths, line_endings if ok then warnings = chstate.warnings core_utils.sort_by_location(warnings) inline_options = chstate.inline_options line_lengths = chstate.line_lengths line_endings = chstate.line_endings else local err = error_wrapper.err if not utils.is_instance(err, parser.SyntaxError) then error(error_wrapper, 0) end local syntax_error = { code = "011", line = err.line, column = chstate:offset_to_column(err.line, err.offset), end_column = chstate:offset_to_column(err.line, err.end_offset), msg = err.msg } if err.prev_line then syntax_error.prev_line = err.prev_line syntax_error.prev_column = chstate:offset_to_column(err.prev_line, err.prev_offset) syntax_error.prev_end_column = chstate:offset_to_column(err.prev_line, err.prev_end_offset) end warnings = {syntax_error} inline_options = {} line_lengths = {} line_endings = {} end validate_fields(warnings, true) validate_fields(inline_options) return { warnings = warnings, inline_options = inline_options, line_lengths = line_lengths, line_endings = line_endings } end return check luacheck-0.25.0/src/luacheck/check_state.lua000066400000000000000000000034001410451437000206770ustar00rootroot00000000000000local utils = require "luacheck.utils" local check_state = {} local CheckState = utils.class() function CheckState:__init(source_bytes) self.source_bytes = source_bytes self.warnings = {} end -- Returns column of a character in a line given its offset. -- The column is never larger than the line length. -- This can be called if line length is not yet known. function CheckState:offset_to_column(line, offset) local line_length = self.line_lengths[line] local column = offset - self.line_offsets[line] + 1 if not line_length then return column end return math.max(1, math.min(line_length, column)) end function CheckState:warn_column_range(code, range, warning) warning = warning or {} warning.code = code warning.line = range.line warning.column = range.column warning.end_column = range.end_column table.insert(self.warnings, warning) return warning end function CheckState:warn(code, line, offset, end_offset, warning) warning = warning or {} warning.code = code warning.line = line warning.column = self:offset_to_column(line, offset) warning.end_column = self:offset_to_column(line, end_offset) table.insert(self.warnings, warning) return warning end function CheckState:warn_range(code, range, warning) return self:warn(code, range.line, range.offset, range.end_offset, warning) end function CheckState:warn_var(code, var, warning) warning = self:warn_range(code, var.node, warning) warning.name = var.name return warning end function CheckState:warn_value(code, value, warning) warning = self:warn_range(code, value.var_node, warning) warning.name = value.var.name return warning end function check_state.new(source_bytes) return CheckState(source_bytes) end return check_state luacheck-0.25.0/src/luacheck/config.lua000066400000000000000000000367161410451437000177070ustar00rootroot00000000000000local cache = require "luacheck.cache" local options = require "luacheck.options" local builtin_standards = require "luacheck.builtin_standards" local fs = require "luacheck.fs" local globbing = require "luacheck.globbing" local standards = require "luacheck.standards" local utils = require "luacheck.utils" local config = {} local function get_global_config_dir() if utils.is_windows then local local_app_data_dir = os.getenv("LOCALAPPDATA") if not local_app_data_dir then local user_profile_dir = os.getenv("USERPROFILE") if user_profile_dir then local_app_data_dir = fs.join(user_profile_dir, "Local Settings", "Application Data") end end if local_app_data_dir then return fs.join(local_app_data_dir, "Luacheck") end else local fh = assert(io.popen("uname -s")) local system = fh:read("*l") fh:close() if system == "Darwin" then local home_dir = os.getenv("HOME") if home_dir then return fs.join(home_dir, "Library", "Application Support", "Luacheck") end else local config_home_dir = os.getenv("XDG_CONFIG_HOME") if not config_home_dir then local home_dir = os.getenv("HOME") if home_dir then config_home_dir = fs.join(home_dir, ".config") end end if config_home_dir then return fs.join(config_home_dir, "luacheck") end end end end config.default_path = ".luacheckrc" function config.get_default_global_path() local global_config_dir = get_global_config_dir() if global_config_dir then return fs.join(global_config_dir, config.default_path) end end -- A single config is represented by a table with fields: -- * `options`: table with all config scope options, including `stds` and `files`. -- * `config_path`: optional path to file from which config was loaded, used only in error messages. -- * `anchor_dir`: absolute path to directory relative to which config was loaded, -- or nil if the config is not anchored. Paths within a config are adjusted to be absolute -- relative to anchor directory, or current directory if it's not anchored. -- As current directory can change between config usages, this adjustment happens on demand. -- Returns config path and optional anchor directory or nil and optional error message. local function locate_config(path, global_path) if path == false then return end local is_default_path = not path path = path or config.default_path if fs.is_absolute(path) then return path end local current_dir = fs.get_current_dir() local anchor_dir, rel_dir = fs.find_file(current_dir, path) if anchor_dir then return fs.join(rel_dir, path), anchor_dir end if not is_default_path then return nil, ("Couldn't find configuration file %s"):format(path) end if global_path == false then return end global_path = global_path or config.get_default_global_path() if global_path and fs.is_file(global_path) then return global_path, (fs.split_base(global_path)) end end local function try_load(path) local src = utils.read_file(path) if not src then return end local func, err = utils.load(src, nil, "@"..path) return err or func end local function add_relative_loader(anchor_dir) if not anchor_dir then return end local function loader(modname) local modpath = fs.join(anchor_dir, (modname:gsub("%.", utils.dir_sep))) return try_load(modpath..".lua") or try_load(modpath..utils.dir_sep.."init.lua"), modname end table.insert(package.loaders or package.searchers, 1, loader) -- luacheck: compat return loader end local function remove_relative_loader(loader) if not loader then return end for i, func in ipairs(package.loaders or package.searchers) do -- luacheck: compat if func == loader then table.remove(package.loaders or package.searchers, i) -- luacheck: compat return end end end -- Requires module from config anchor directory. -- Returns success flag and module or error message. function config.relative_require(anchor_dir, modname) local loader = add_relative_loader(anchor_dir) local ok, mod_or_err = pcall(require, modname) remove_relative_loader(loader) return ok, mod_or_err end -- Config must support special metatables for some keys: -- autovivification for `files`, fallback to built-in stds for `stds`. local special_mts = { stds = {__index = builtin_standards}, files = {__index = function(files, key) files[key] = {} return files[key] end} } local function make_config_env_mt() local env_mt = {} local special_values = {} for key, mt in pairs(special_mts) do special_values[key] = setmetatable({}, mt) end function env_mt.__index(_, key) if special_mts[key] then return special_values[key] else return _G[key] end end function env_mt.__newindex(env, key, value) if special_mts[key] then if type(value) == "table" then setmetatable(value, special_mts[key]) end special_values[key] = value else rawset(env, key, value) end end return env_mt, special_values end local function make_config_env() local mt, special_values = make_config_env_mt() return setmetatable({}, mt), special_values end local function remove_env_mt(env, special_values) setmetatable(env, nil) utils.update(env, special_values) end local function set_default_std(files, pattern, std) -- Avoid mutating option tables, they may be shared between different patterns. local pattern_opts = {std = std} if files[pattern] then pattern_opts = utils.update(pattern_opts, files[pattern]) end files[pattern] = pattern_opts end local function add_default_path_options(opts) local files = {} if opts.files then files = utils.update(files, opts.files) end opts.files = files set_default_std(files, "**/spec/**/*_spec.lua", "+busted") set_default_std(files, "**/test/**/*_spec.lua", "+busted") set_default_std(files, "**/tests/**/*_spec.lua", "+busted") set_default_std(files, "**/*.rockspec", "+rockspec") set_default_std(files, "**/*.luacheckrc", "+luacheckrc") end local fallback_config = {options = {}, anchor_dir = ""} add_default_path_options(fallback_config.options) -- Loads config from a file, if possible. -- `path` and `global_path` can be nil (will use default), false (will disable loading), or a string. -- Doesn't validate the config. -- Returns a table or nil and an error message. function config.load_config(path, global_path) local config_path, anchor_dir = locate_config(path, global_path) if not config_path then if anchor_dir then return nil, anchor_dir else return fallback_config end end local env, special_values = make_config_env() local loader = add_relative_loader(anchor_dir) local load_ok, ret, load_err = utils.load_config(config_path, env) remove_relative_loader(loader) if not load_ok then return nil, ("Couldn't load configuration from %s: %s error (%s)"):format(config_path, ret, load_err) end -- Support returning some options from config instead of setting them as globals. -- This allows easily loading options from another file, for example using require. if type(ret) == "table" then utils.update(env, ret) end remove_env_mt(env, special_values) add_default_path_options(env) return {options = env, config_path = config_path, anchor_dir = anchor_dir} end function config.table_to_config(opts) return {options = opts} end -- Validates custom stds within a config table and adds them to stds map. -- Returns true on success or nil and an error message on error. local function add_stds_from_config(conf, stds) if conf.options.stds ~= nil then if type(conf.options.stds) ~= "table" then return nil, ("invalid option 'stds': table expected, got %s"):format(type(conf.options.stds)) end -- Validate stds in sorted order for deterministic output when more than one std is invalid. local std_names = {} for std_name in pairs(conf.options.stds) do if type(std_name) == "string" then table.insert(std_names, std_name) end end table.sort(std_names) for _, std_name in ipairs(std_names) do local std = conf.options.stds[std_name] if type(std) ~= "table" then return nil, ("invalid custom std '%s': table expected, got %s"):format(std_name, type(std)) end local ok, err = standards.validate_std_table(std) if not ok then return nil, ("invalid custom std '%s': %s"):format(std_name, err) end stds[std_name] = std end end return true end local function error_prefix(conf) if conf.config_path then return ("in config loaded from %s: "):format(conf.config_path) else return "" end end local function quiet_validator(x) if type(x) == "number" then if math.floor(x) == x and x >= 0 and x <= 3 then return true else return false, ("integer in range 0..3 expected, got %.20g"):format(x) end else return false, ("integer in range 0..3 expected, got %s"):format(type(x)) end end local function jobs_validator(x) if type(x) == "number" then if math.floor(x) == x and x >= 1 then return true else return false, ("positive integer expected, got %.20g"):format(x) end else return false, ("positive integer expected, got %s"):format(type(x)) end end config.format_options = { quiet = quiet_validator, color = utils.has_type("boolean"), codes = utils.has_type("boolean"), ranges = utils.has_type("boolean"), formatter = utils.has_either_type("string", "function") } local top_options = { cache = utils.has_either_type("string", "boolean"), jobs = jobs_validator, files = utils.has_type("table"), stds = utils.has_type("table"), exclude_files = utils.array_of("string"), include_files = utils.array_of("string") } utils.update(top_options, config.format_options) utils.update(top_options, options.all_options) -- Returns true if config is valid, nil and error message otherwise. local function validate_config(conf, stds) local ok, err = options.validate(top_options, conf.options, stds) if not ok then return nil, err end if conf.options.files then for path, opts in pairs(conf.options.files) do if type(path) == "string" then ok, err = options.validate(options.all_options, opts, stds) if not ok then return nil, ("invalid options for path '%s': %s"):format(path, err) end end end end return true end local ConfigStack = utils.class() function ConfigStack:__init(configs, stds) self._configs = configs self._stds = stds end function ConfigStack:get_stds() return self._stds end -- Accepts an array of config tables, as returned from `load_config` and `table_to_config`. -- Assumes that configs closer to end of the array override configs closer to beginning. -- Returns an instance of `ConfigStack`. On validation error returns nil and an error message. function config.stack_configs(configs) -- First, collect and validate stds from all configs, they are required to validate `std` option. local stds = utils.update({}, builtin_standards) for _, conf in ipairs(configs) do local ok, err = add_stds_from_config(conf, stds) if not ok then return nil, error_prefix(conf) .. err end end for _, conf in ipairs(configs) do local ok, err = validate_config(conf, stds) if not ok then return nil, error_prefix(conf) .. err end end return ConfigStack(configs, stds) end -- Returns a table of top-level config options, except `files` and `stds`. function ConfigStack:get_top_options() local res = { quiet = 0, color = true, codes = false, ranges = false, formatter = "default", cache = false, jobs = false, include_files = {}, exclude_files = {} } local current_dir = fs.get_current_dir() local last_anchor_dir for _, conf in ipairs(self._configs) do for _, option in ipairs({"quiet", "color", "codes", "ranges", "jobs"}) do if conf.options[option] ~= nil then res[option] = conf.options[option] end end -- It's not immediately obvious relatively to which config formatter modules -- should be resolved when they are specified in a config without an anchor dir. -- For now, use the last anchor directory available, that should result -- in reasonable behaviour in the current case of a single anchored config (loaded from file) -- + a single not anchored config (loaded from CLI options). last_anchor_dir = conf.anchor_dir or last_anchor_dir if conf.options.formatter ~= nil then res.formatter = conf.options.formatter res.formatter_anchor_dir = last_anchor_dir end -- Path options, on the other hand, are interpreted relatively to the current directory -- when specified in a config without anchor. Behaviour similar to formatter could also -- make sense, but this is consistent with pre 0.22.0 behaviou local anchor_dir = conf.anchor_dir or current_dir for _, option in ipairs({"include_files", "exclude_files"}) do if conf.options[option] ~= nil then for _, glob in ipairs(conf.options[option]) do table.insert(res[option], fs.normalize(fs.join(anchor_dir, glob))) end end end if conf.options.cache ~= nil then if conf.options.cache == true then if not res.cache then res.cache = fs.normalize(fs.join(last_anchor_dir or current_dir, cache.get_default_dir())) end elseif conf.options.cache == false then res.cache = false else res.cache = fs.normalize(fs.join(anchor_dir, conf.options.cache)) end end end return res end local function add_applying_overrides(option_stack, conf, filename) if not filename or not conf.options.files then return end local current_dir = fs.get_current_dir() local abs_filename = fs.normalize(fs.join(current_dir, filename)) local anchor_dir if conf.anchor_dir == "" then anchor_dir = fs.split_base(current_dir) else anchor_dir = conf.anchor_dir or current_dir end local matching_pairs = {} for glob, opts in pairs(conf.options.files) do if type(glob) == "string" then local abs_glob = fs.normalize(fs.join(anchor_dir, glob)) if globbing.match(abs_glob, abs_filename) then table.insert(matching_pairs, { abs_glob = abs_glob, opts = opts }) end end end table.sort(matching_pairs, function(pair1, pair2) return globbing.compare(pair1.abs_glob, pair2.abs_glob) end) for _, pair in ipairs(matching_pairs) do table.insert(option_stack, pair.opts) end end -- Returns an option stack applicable to a file with given name, or in general if name is not given. function ConfigStack:get_options(filename) local res = {} for _, conf in ipairs(self._configs) do table.insert(res, conf.options) add_applying_overrides(res, conf, filename) end return res end return config luacheck-0.25.0/src/luacheck/core_utils.lua000066400000000000000000000053431410451437000206020ustar00rootroot00000000000000local decoder = require "luacheck.decoder" local utils = require "luacheck.utils" local core_utils = {} -- Attempts to evaluate a node as a Lua value, without resolving locals. -- Returns Lua value and its string representation on success, nothing on failure. function core_utils.eval_const_node(node) if node.tag == "True" then return true, "true" elseif node.tag == "False" then return false, "false" elseif node.tag == "String" then local chars = decoder.decode(node[1]) return node[1], chars:get_printable_substring(1, chars:get_length()) else local is_negative if node.tag == "Op" and node[1] == "unm" then is_negative = true node = node[2] end if node.tag ~= "Number" then return end local str = node[1] if str:find("[iIuUlL]") then -- Ignore LuaJIT cdata literals. return end -- On Lua 5.3+ convert to float to get same results as on Lua 5.1 and 5.2. if (_VERSION == "Lua 5.3" or _VERSION == "Lua 5.4") and not str:find("[%.eEpP]") then str = str .. ".0" end local number = tonumber(str) if not number then return end if is_negative then number = -number end if number == number and number < 1/0 and number > -1/0 then return number, (is_negative and "-" or "") .. node[1] end end end local statement_containing_tags = utils.array_to_set({"Do", "While", "Repeat", "Fornum", "Forin", "If"}) -- `items` is an array of nodes or nested item arrays. local function scan_for_statements(chstate, items, tags, callback, ...) for _, item in ipairs(items) do if tags[item.tag] then callback(chstate, item, ...) end if not item.tag or statement_containing_tags[item.tag] then scan_for_statements(chstate, item, tags, callback, ...) end end end -- Calls `callback(chstate, node, ...)` for each statement node within AST with tag in given array. function core_utils.each_statement(chstate, tags_array, callback, ...) local tags = utils.array_to_set(tags_array) for _, line in ipairs(chstate.lines) do scan_for_statements(chstate, line.node[2], tags, callback, ...) end end local function location_comparator(warning1, warning2) if warning1.line ~= warning2.line then return warning1.line < warning2.line elseif warning1.column ~= warning2.column then return warning1.column < warning2.column else return warning1.code < warning2.code end end -- Sorts an array of warnings by location information as provided in `line` and `column` fields. function core_utils.sort_by_location(warnings) table.sort(warnings, location_comparator) end return core_utils luacheck-0.25.0/src/luacheck/decoder.lua000066400000000000000000000126341410451437000200400ustar00rootroot00000000000000local unicode = require "luacheck.unicode" local utils = require "luacheck.utils" local decoder = {} local sbyte = string.byte local sfind = string.find local sgsub = string.gsub local ssub = string.sub -- `LatinChars` and `UnicodeChars` objects represent source strings -- and provide Unicode-aware access to them with a common interface. -- Source bytes should not be accessed directly. -- Provided methods are: -- `Chars:get_codepoint(index)`: returns codepoint at given index as integer or nil if index is out of range. -- `Chars:get_substring(from, to)`: returns substring of original bytes corresponding to characters from `from` to `to`. -- `Chars:get_printable_substring(from. to)`: like get_substring but escapes not printable characters. -- `Chars:get_length()`: returns total number of characters. -- `Chars:find(pattern, from)`: `string.find` but `from` is in characters. Return values are still in bytes. -- `LatinChars` is an optimized special case for latin1 strings. local LatinChars = utils.class() function LatinChars:__init(bytes) self._bytes = bytes end function LatinChars:get_codepoint(index) return sbyte(self._bytes, index) end function LatinChars:get_substring(from, to) return ssub(self._bytes, from, to) end local function hexadecimal_escaper(byte) return ("\\x%02X"):format(sbyte(byte)) end function LatinChars:get_printable_substring(from, to) return (sgsub(ssub(self._bytes, from, to), "[^\32-\126]", hexadecimal_escaper)) end function LatinChars:get_length() return #self._bytes end function LatinChars:find(pattern, from) return sfind(self._bytes, pattern, from) end -- Decodes `bytes` as UTF8. Returns arrays of codepoints as integers and their byte offsets. -- Byte offsets have one extra item pointing to one byte past the end of `bytes`. -- On decoding error returns nothing. local function get_codepoints_and_byte_offsets(bytes) local codepoints = {} local byte_offsets = {} local byte_index = 1 local codepoint_index = 1 while true do byte_offsets[codepoint_index] = byte_index -- Attempt to decode the next codepoint from UTF8. local codepoint = sbyte(bytes, byte_index) if not codepoint then return codepoints, byte_offsets end byte_index = byte_index + 1 if codepoint >= 0x80 then -- Not ASCII. if codepoint < 0xC0 then return end local cont = (sbyte(bytes, byte_index) or 0) - 0x80 if cont < 0 or cont >= 0x40 then return end byte_index = byte_index + 1 if codepoint < 0xE0 then -- Two bytes. codepoint = cont + (codepoint - 0xC0) * 0x40 elseif codepoint < 0xF0 then -- Three bytes. codepoint = cont + (codepoint - 0xE0) * 0x40 cont = (sbyte(bytes, byte_index) or 0) - 0x80 if cont < 0 or cont >= 0x40 then return end byte_index = byte_index + 1 codepoint = cont + codepoint * 0x40 elseif codepoint < 0xF8 then -- Four bytes. codepoint = cont + (codepoint - 0xF0) * 0x40 cont = (sbyte(bytes, byte_index) or 0) - 0x80 if cont < 0 or cont >= 0x40 then return end byte_index = byte_index + 1 codepoint = cont + codepoint * 0x40 cont = (sbyte(bytes, byte_index) or 0) - 0x80 if cont < 0 or cont >= 0x40 then return end byte_index = byte_index + 1 codepoint = cont + codepoint * 0x40 if codepoint > 0x10FFFF then return end else return end end codepoints[codepoint_index] = codepoint codepoint_index = codepoint_index + 1 end end -- `UnicodeChars` is the general case for non-latin1 strings. -- Assumes UTF8, on decoding error falls back to latin1. local UnicodeChars = utils.class() function UnicodeChars:__init(bytes, codepoints, byte_offsets) self._bytes = bytes self._codepoints = codepoints self._byte_offsets = byte_offsets end function UnicodeChars:get_codepoint(index) return self._codepoints[index] end function UnicodeChars:get_substring(from, to) local byte_offsets = self._byte_offsets return ssub(self._bytes, byte_offsets[from], byte_offsets[to + 1] - 1) end function UnicodeChars:get_printable_substring(from, to) -- This is only called on syntax error, it's okay to be slow. local parts = {} for index = from, to do local codepoint = self._codepoints[index] if unicode.is_printable(codepoint) then table.insert(parts, self:get_substring(index, index)) else table.insert(parts, (codepoint > 255 and "\\u{%X}" or "\\x%02X"):format(codepoint)) end end return table.concat(parts) end function UnicodeChars:get_length() return #self._codepoints end function UnicodeChars:find(pattern, from) return sfind(self._bytes, pattern, self._byte_offsets[from]) end function decoder.decode(bytes) -- Only use UnicodeChars if necessary. LatinChars isn't much faster but noticeably more memory efficient. if sfind(bytes, "[\128-\255]") then local codepoints, byte_offsets = get_codepoints_and_byte_offsets(bytes) if codepoints then return UnicodeChars(bytes, codepoints, byte_offsets) end end return LatinChars(bytes) end return decoder luacheck-0.25.0/src/luacheck/expand_rockspec.lua000066400000000000000000000056561410451437000216110ustar00rootroot00000000000000local fs = require "luacheck.fs" local utils = require "luacheck.utils" local blacklist = utils.array_to_set({"spec", ".luarocks", "lua_modules", "test.lua", "tests.lua"}) -- This reimplements relevant parts of `luarocks.build.builtin.autodetect_modules`. -- Autodetection works relatively to the directory containing the rockspec. local function autodetect_modules(rockspec_path) rockspec_path = fs.normalize(rockspec_path) local base, rest = fs.split_base(rockspec_path) local project_dir = base .. (rest:match("^(.*)" .. utils.dir_sep .. ".*$") or "") if project_dir == "" then project_dir = "." end local module_dir = project_dir for _, module_subdir in ipairs({"src", "lua", "lib"}) do local full_module_dir = fs.join(project_dir, module_subdir) if fs.is_dir(full_module_dir) then module_dir = full_module_dir break end end local res = {} for _, file in ipairs((fs.extract_files(module_dir, "%.lua$"))) do -- Extract first part of the path from module_dir to the file, or file name itself. if not blacklist[file:match("^" .. module_dir:gsub("%p", "%%%0") .. "[\\/]*([^\\/]*)")] then table.insert(res, file) end end local bin_dir for _, bin_subdir in ipairs({"src/bin", "bin"}) do local full_bin_dir = fs.join(project_dir, bin_subdir) if fs.is_dir(full_bin_dir) then bin_dir = full_bin_dir end end if bin_dir then local iter, state, var = fs.dir_iter(bin_dir) if iter then for basename in iter, state, var do if basename:sub(-#".lua") == ".lua" then table.insert(res, fs.join(bin_dir, basename)) end end end end return res end local function extract_lua_files(rockspec_path, rockspec) local build = rockspec.build if type(build) ~= "table" then return autodetect_modules(rockspec_path) end if not build.type or build.type == "builtin" or build.type == "module" then if not build.modules then return autodetect_modules(rockspec_path) end end local res = {} local function scan(t) if type(t) == "table" then for _, file in pairs(t) do if type(file) == "string" and file:sub(-#".lua") == ".lua" then table.insert(res, file) end end end end scan(build.modules) if type(build.install) == "table" then scan(build.install.lua) scan(build.install.bin) end table.sort(res) return res end -- Receives a name of a rockspec, returns list of related .lua files. -- On error returns nil and "I/O", "syntax", or "runtime" and error message. local function expand_rockspec(rockspec_path) local rockspec, err_type, err_msg = utils.load_config(rockspec_path) if not rockspec then return nil, err_type, err_msg end return extract_lua_files(rockspec_path, rockspec) end return expand_rockspec luacheck-0.25.0/src/luacheck/filter.lua000066400000000000000000000434451410451437000177240ustar00rootroot00000000000000local core_utils = require "luacheck.core_utils" local decoder = require "luacheck.decoder" local options = require "luacheck.options" local utils = require "luacheck.utils" local filter = {} -- Returns two optional booleans indicating if warning matches pattern by code and name. local function match(pattern, code, name) local matches_code, matches_name local code_pattern, name_pattern = pattern[1], pattern[2] if code_pattern then matches_code = utils.pmatch(code, code_pattern) end if name_pattern then if not name then -- Warnings without name field can't match by name. matches_name = false else matches_name = utils.pmatch(name, name_pattern) end end return matches_code, matches_name end local function passes_rules_filter(rules, code, name) -- A warning is enabled when its code and name are enabled. local enabled_code, enabled_name = false, false for _, rule in ipairs(rules) do local matches_one = false for _, pattern in ipairs(rule[1]) do local matches_code, matches_name = match(pattern, code, name) -- If a factor is enabled, warning can't be disabled by it. if enabled_code then matches_code = rule[2] ~= "disable" end if enabled_name then matches_code = rule[2] ~= "disable" end if (matches_code and matches_name ~= false) or (matches_name and matches_code ~= false) then matches_one = true end if rule[2] == "enable" then if matches_code then enabled_code = true end if matches_name then enabled_name = true end if enabled_code and enabled_name then -- Enable as matching to some `enable` pattern by code and to another by name. return true end elseif rule[2] == "disable" then if matches_one then -- Disable as matching to `disable` pattern. return false end end end if rule[2] == "only" and not matches_one then -- Disable as not matching to any of `only` patterns. return false end end -- Enable by default. return true end local function get_field_string(warning) local parts = {} if warning.indexing then for _, index in ipairs(warning.indexing) do local part if type(index) == "string" then local chars = decoder.decode(index) part = chars:get_printable_substring(1, chars:get_length()) else part = "?" end table.insert(parts, part) end end return table.concat(parts, ".") end local function get_field_status(opts, warning, depth) local def = opts.std local defined = true local read_only = true for i = 1, depth or (warning.indexing and #warning.indexing or 0) + 1 do local index_string = i == 1 and warning.name or warning.indexing[i - 1] if index_string == true then -- Indexing with something that may or may not be a string. if (def.fields and next(def.fields)) or def.other_fields then if def.deep_read_only then read_only = true else read_only = false end else defined = false end break elseif index_string == false then -- Indexing with not a string. if not def.other_fields then defined = false end break else -- Indexing with a constant string. if def.fields and def.fields[index_string] then -- The field is defined, recurse into it. def = def.fields[index_string] if def.read_only ~= nil then read_only = def.read_only end else -- The field is not defined, but it may be okay to index if `other_fields` is true. if not def.other_fields then defined = false end break end end end return defined and (read_only and "read_only" or "global") or "undefined" end -- Checks if a warning passes options filter. May add some fields required for formatting. local function passes_filter(normalized_options, warning) if warning.code == "561" then local max_complexity = normalized_options.max_cyclomatic_complexity if not max_complexity or warning.complexity <= max_complexity then return false end warning.max_complexity = max_complexity elseif warning.code:find("^[234]") and warning.name == "_" and not warning.useless then return false elseif warning.code:find("^1[14]") then if warning.indirect and get_field_status(normalized_options, warning, warning.previous_indexing_len) == "undefined" then return false end if not warning.module and get_field_status(normalized_options, warning) ~= "undefined" then return false end end if warning.code:find("^1[24][23]") then warning.field = get_field_string(warning) end if warning.secondary and not normalized_options.unused_secondaries then return false end if warning.self and not normalized_options.self then return false end return passes_rules_filter(normalized_options.rules, warning.code, warning.name) end local empty_options = {} -- Updates option_stack for given line with next_index pointing to the inline option past the previous line. -- Adds warnings for invalid inline options to check_result, filtered_warnings. -- Returns updated next_index. local function update_option_stack_for_new_line(check_result, stds, option_stack, line, next_index) local inline_option = check_result.inline_options[next_index] if not inline_option or inline_option.line > line then -- No inline options on this line, option stack for the line is ready. return next_index end next_index = next_index + 1 if inline_option.pop_count then for _ = 1, inline_option.pop_count do table.remove(option_stack) end end if not inline_option.options then -- No inline option push on this line, option stack for the line is ready. return next_index end local options_ok, err_msg = options.validate(options.all_options, inline_option.options, stds) if not options_ok then -- Warn about invalid inline option, push a dummy empty table instead to keep pop counts correct. inline_option.options = nil inline_option.code = "021" inline_option.msg = err_msg table.insert(check_result.filtered_warnings, inline_option) -- Reuse empty table identity so that normalized option caching works better. table.insert(option_stack, empty_options) else table.insert(option_stack, inline_option.options) end return next_index end -- Warns (adds to check_result.filtered_warnings) about a line if it's too long -- and the warning is not filtered out by options. local function check_line_length(check_result, normalized_options, line) local line_length = check_result.line_lengths[line] local line_type = check_result.line_endings[line] local max_length = normalized_options["max_" .. (line_type or "code") .. "_line_length"] if max_length and line_length > max_length then if passes_rules_filter(normalized_options.rules, "631") then table.insert(check_result.filtered_warnings, { code = "631", line = line, column = max_length + 1, end_column = line_length, max_length = max_length, line_ending = line_type }) end end end -- Adds warnings passing filtering and not related to globals to check_result.filtered_warnings. -- If there is a global related warning on this line, sets check_results[line] to normalized_optuons. local function filter_warnings_on_new_line(check_result, normalized_options, line, next_index) while true do local warning = check_result.warnings[next_index] if not warning or warning.line > line then -- No more warnings on this line. break end if warning.code:find("^1") then check_result.normalized_options[line] = normalized_options elseif passes_filter(normalized_options, warning) then table.insert(check_result.filtered_warnings, warning) end next_index = next_index + 1 end return next_index end -- Normalizing options is relatively expensive because full std definitions are quite large. -- `CachingOptionsNormalizer` implements a caching layer that reduces number of `options.normalize` calls. -- Caching is done based on identities of option tables. local CachingOptionsNormalizer = utils.class() function CachingOptionsNormalizer:__init() self.result_trie = {} end function CachingOptionsNormalizer:normalize_options(stds, option_stack) local result_node = self.result_trie for _, option_table in ipairs(option_stack) do if not result_node[option_table] then result_node[option_table] = {} end result_node = result_node[option_table] end if result_node.result then return result_node.result end local result = options.normalize(option_stack, stds) result_node.result = result return result end -- May mutate base_opts_stack. local function filter_not_global_related_in_file(check_result, options_normalizer, stds, option_stack) check_result.filtered_warnings = {} check_result.normalized_options = {} -- Iterate over lines, warnings, and inline options at the same time, keeping opts_stack up to date. local next_warning_index = 1 local next_inline_option_index = 1 for line in ipairs(check_result.line_lengths) do next_inline_option_index = update_option_stack_for_new_line( check_result, stds, option_stack, line, next_inline_option_index) local normalized_options = options_normalizer:normalize_options(stds, option_stack) check_line_length(check_result, normalized_options, line) next_warning_index = filter_warnings_on_new_line(check_result, normalized_options, line, next_warning_index) end end local function may_have_options(opts_table) for key in pairs(opts_table) do if type(key) == "string" then return true end end return false end local function get_option_stack(opts, file_index) local res = {opts} if opts and opts[file_index] then -- Don't add useless per-file option tables, that messes up normalized option caching -- since it memorizes based on option table identities. if may_have_options(opts[file_index]) then table.insert(res, opts[file_index]) end for _, nested_opts in ipairs(opts[file_index]) do table.insert(res, nested_opts) end end return res end -- For each file check result: -- * Stores invalid inline options, not filtered out not global-related warnings, and newly created line length warnings -- in .filtered_warnings. -- * Stores a map from line numbers to normalized options for lines of global-related warnings in .normalized_options. local function filter_not_global_related(check_results, opts, stds) local caching_options_normalizer = CachingOptionsNormalizer() for file_index, check_result in ipairs(check_results) do if not check_result.fatal then if check_result.warnings[1] and check_result.warnings[1].code == "011" then -- Special case syntax errors, they don't have line numbers so normal filtering does not work. check_result.filtered_warnings = check_result.warnings check_result.normalized_options = {} else local base_file_option_stack = get_option_stack(opts, file_index) filter_not_global_related_in_file(check_result, caching_options_normalizer, stds, base_file_option_stack) end end end end -- A global is implicitly defined in a file if opts.allow_defined == true and it is set anywhere in the file, -- or opts.allow_defined_top == true and it is set in the top level function scope. -- By default, accessing and setting globals in a file is allowed for explicitly defined globals (standard and custom) -- for that file and implicitly defined globals from that file and -- all other files except modules (files with opts.module == true). -- Accessing other globals results in "accessing undefined variable" warning. -- Setting other globals results in "setting non-standard global variable" warning. -- Unused implicitly defined global results in "unused global variable" warning. -- For modules, accessing globals uses same rules as normal files, however, -- setting globals is only allowed for implicitly defined globals from the module. -- Setting a global not defined in the module results in "setting non-module global variable" warning. local function is_definition(normalized_options, warning) return normalized_options.allow_defined or (normalized_options.allow_defined_top and warning.top) end -- Extracts sets of defined, exported and used globals from a file check result. local function get_implicit_globals_in_file(check_result) local defined = {} local exported = {} local used = {} for _, warning in ipairs(check_result.warnings) do if warning.code:find("^11") then if warning.code == "111" then local normalized_options = check_result.normalized_options[warning.line] if is_definition(normalized_options, warning) then if normalized_options.module then defined[warning.name] = true else exported[warning.name] = true end end else used[warning.name] = true end end end return defined, exported, used end -- Returns set of globals defines across all files except modules, a set of globals used across all files, -- and an array of sets of globals defined per file, parallel to the check results array. local function get_implicit_globals(check_results) local globally_defined = {} local globally_used = {} local locally_defined = {} for file_index, check_result in ipairs(check_results) do if not check_result.fatal then local defined, exported, used = get_implicit_globals_in_file(check_result) utils.update(globally_defined, exported) utils.update(globally_used, used) locally_defined[file_index] = defined end end return globally_defined, globally_used, locally_defined end -- Mutates the warning and returns it or discards it by returning nothing if it's filtered out. local function apply_implicit_definitions(globally_defined, globally_used, locally_defined, normalized_options, warning) if not warning.code:find("^11") then return warning end if warning.code == "111" then if normalized_options.module then if locally_defined[warning.name] then return end warning.module = true else if is_definition(normalized_options, warning) then if globally_used[warning.name] then return end warning.code = "131" warning.top = nil else if globally_defined[warning.name] then return end end end else if globally_defined[warning.name] or locally_defined[warning.name] then return end end return warning end local function filter_global_related_in_file(check_result, globally_defined, globally_used, locally_defined) for _, warning in ipairs(check_result.warnings) do if warning.code:find("^1") then local normalized_options = check_result.normalized_options[warning.line] warning = apply_implicit_definitions( globally_defined, globally_used, locally_defined, normalized_options, warning) if warning then if warning.code:find("^11[12]") and not warning.module and get_field_status(normalized_options, warning) == "read_only" then warning.code = "12" .. warning.code:sub(3, 3) elseif warning.code:find("^11[23]") and get_field_status(normalized_options, warning, 1) ~= "undefined" then warning.code = "14" .. warning.code:sub(3, 3) end if warning.code:match("11[23]") and get_field_status(normalized_options, warning, 1) ~= "undefined" then warning.code = "14" .. warning.code:sub(3, 3) end if passes_filter(normalized_options, warning) then table.insert(check_result.filtered_warnings, warning) end end end end end local function filter_global_related(check_results) local globally_defined, globally_used, locally_defined = get_implicit_globals(check_results) for file_index, check_result in ipairs(check_results) do if not check_result.fatal then filter_global_related_in_file(check_result, globally_defined, globally_used, locally_defined[file_index]) end end end -- Processes an array of results of the check stage (or tables with .fatal field) into the final report. -- `opts[i]`, if present, is used as options when processing `report[i]` together with options in its array part. -- This function may mutate check results or reuse its parts in the return value. function filter.filter(check_results, opts, stds) filter_not_global_related(check_results, opts, stds) filter_global_related(check_results) local report = {} for file_index, check_result in ipairs(check_results) do if check_result.fatal then report[file_index] = check_result else core_utils.sort_by_location(check_result.filtered_warnings) report[file_index] = check_result.filtered_warnings end end return report end return filter luacheck-0.25.0/src/luacheck/format.lua000066400000000000000000000244141410451437000177220ustar00rootroot00000000000000local stages = require "luacheck.stages" local utils = require "luacheck.utils" local format = {} local color_support = not utils.is_windows or os.getenv("ANSICON") local function get_message_format(warning) local message_format = assert(stages.warnings[warning.code], "Unkown warning code " .. warning.code).message_format if type(message_format) == "function" then return message_format(warning) else return message_format end end local function plural(number) return (number == 1) and "" or "s" end local color_codes = { reset = 0, bright = 1, red = 31, green = 32 } local function encode_color(c) return "\27[" .. tostring(color_codes[c]) .. "m" end local function colorize(str, ...) str = str .. encode_color("reset") for _, color in ipairs({...}) do str = encode_color(color) .. str end return encode_color("reset") .. str end local function format_color(str, color, ...) return color and colorize(str, ...) or str end local function format_number(number, color) return format_color(tostring(number), color, "bright", (number > 0) and "red" or "reset") end -- Substitutes markers within string format with values from a table. -- "{field_name}" marker is replaced with `values.field_name`. -- "{field_name!}" marker adds highlight or quoting depending on color -- option. local function substitute(string_format, values, color) return (string_format:gsub("{([_a-zA-Z0-9]+)(!?)}", function(field_name, highlight) local value = tostring(assert(values[field_name], "No field " .. field_name)) if highlight == "!" then if color then return colorize(value, "bright") else return "'" .. value .. "'" end else return value end end)) end local function format_message(event, color) return substitute(get_message_format(event), event, color) end -- Returns formatted message for an issue, without color. function format.get_message(event) return format_message(event) end local function capitalize(str) return str:gsub("^.", string.upper) end local function fatal_type(file_report) return capitalize(file_report.fatal) .. " error" end local function count_warnings_errors(events) local warnings, errors = 0, 0 for _, event in ipairs(events) do if event.code:sub(1, 1) == "0" then errors = errors + 1 else warnings = warnings + 1 end end return warnings, errors end local function format_file_report_header(report, file_name, opts) local label = "Checking " .. file_name local status if report.fatal then status = format_color(fatal_type(report), opts.color, "bright") elseif #report == 0 then status = format_color("OK", opts.color, "bright", "green") else local warnings, errors = count_warnings_errors(report) if warnings > 0 then status = format_color(tostring(warnings).." warning"..plural(warnings), opts.color, "bright", "red") end if errors > 0 then status = status and (status.." / ") or "" status = status..(format_color(tostring(errors).." error"..plural(errors), opts.color, "bright")) end end return label .. (" "):rep(math.max(50 - #label, 1)) .. status end local function format_location(file, location, opts) local res = ("%s:%d:%d"):format(file, location.line, location.column) if opts.ranges then res = ("%s-%d"):format(res, location.end_column) end return res end local function event_code(event) return (event.code:sub(1, 1) == "0" and "E" or "W")..event.code end local function format_event(file_name, event, opts) local message = format_message(event, opts.color) if opts.codes then message = ("(%s) %s"):format(event_code(event), message) end return format_location(file_name, event, opts) .. ": " .. message end local function format_file_report(report, file_name, opts) local buf = {format_file_report_header(report, file_name, opts)} if #report > 0 then table.insert(buf, "") for _, event in ipairs(report) do table.insert(buf, " " .. format_event(file_name, event, opts)) end table.insert(buf, "") elseif report.fatal then table.insert(buf, "") table.insert(buf, " " .. file_name .. ": " .. report.msg) table.insert(buf, "") end return table.concat(buf, "\n") end local function escape_xml(str) str = str:gsub("&", "&") str = str:gsub('"', """) str = str:gsub("'", "'") str = str:gsub("<", "<") str = str:gsub(">", ">") return str end format.builtin_formatters = {} function format.builtin_formatters.default(report, file_names, opts) local buf = {} if opts.quiet <= 2 then for i, file_report in ipairs(report) do if opts.quiet == 0 or file_report.fatal or #file_report > 0 then table.insert(buf, (opts.quiet == 2 and format_file_report_header or format_file_report) ( file_report, file_names[i], opts)) end end if #buf > 0 and buf[#buf]:sub(-1) ~= "\n" then table.insert(buf, "") end end local total = ("Total: %s warning%s / %s error%s in %d file%s"):format( format_number(report.warnings, opts.color), plural(report.warnings), format_number(report.errors, opts.color), plural(report.errors), #report - report.fatals, plural(#report - report.fatals)) if report.fatals > 0 then total = total..(", couldn't check %s file%s"):format( report.fatals, plural(report.fatals)) end table.insert(buf, total) return table.concat(buf, "\n") end function format.builtin_formatters.TAP(report, file_names, opts) opts.color = false local buf = {} for i, file_report in ipairs(report) do if file_report.fatal then table.insert(buf, ("not ok %d %s: %s"):format(#buf + 1, file_names[i], fatal_type(file_report))) elseif #file_report == 0 then table.insert(buf, ("ok %d %s"):format(#buf + 1, file_names[i])) else for _, warning in ipairs(file_report) do table.insert(buf, ("not ok %d %s"):format(#buf + 1, format_event(file_names[i], warning, opts))) end end end table.insert(buf, 1, "1.." .. tostring(#buf)) return table.concat(buf, "\n") end function format.builtin_formatters.JUnit(report, file_names) -- JUnit formatter doesn't support any options. local opts = {} local buf = {[[]]} local num_testcases = 0 for _, file_report in ipairs(report) do if file_report.fatal or #file_report == 0 then num_testcases = num_testcases + 1 else num_testcases = num_testcases + #file_report end end table.insert(buf, ([[]]):format(num_testcases)) for file_i, file_report in ipairs(report) do if file_report.fatal then table.insert(buf, ([[ ]]):format( escape_xml(file_names[file_i]), escape_xml(file_names[file_i]))) table.insert(buf, ([[ ]]):format(escape_xml(fatal_type(file_report)))) table.insert(buf, [[ ]]) elseif #file_report == 0 then table.insert(buf, ([[ ]]):format( escape_xml(file_names[file_i]), escape_xml(file_names[file_i]))) else for event_i, event in ipairs(file_report) do table.insert(buf, ([[ ]]):format( escape_xml(file_names[file_i]), event_i, escape_xml(file_names[file_i]))) table.insert(buf, ([[ ]]):format( escape_xml(event_code(event)), escape_xml(format_event(file_names[file_i], event, opts)))) table.insert(buf, [[ ]]) end end end table.insert(buf, [[]]) return table.concat(buf, "\n") end local fatal_error_codes = { ["I/O"] = "F1", ["syntax"] = "F2", ["runtime"] = "F3" } function format.builtin_formatters.visual_studio(report, file_names) local buf = {} for i, file_report in ipairs(report) do if file_report.fatal then -- Older docs suggest that line number after a file name is optional; newer docs mark it as required. -- Just use tool name as origin and put file name into the message. table.insert(buf, ("luacheck : fatal error %s: couldn't check %s: %s"):format( fatal_error_codes[file_report.fatal], file_names[i], file_report.msg)) else for _, event in ipairs(file_report) do -- Older documentation on the format suggests that it could support column range. -- Newer docs don't mention it. Don't use it for now. local event_type = event.code:sub(1, 1) == "0" and "error" or "warning" local message = format_message(event) table.insert(buf, ("%s(%d,%d) : %s %s: %s"):format( file_names[i], event.line, event.column, event_type, event_code(event), message)) end end end return table.concat(buf, "\n") end function format.builtin_formatters.plain(report, file_names, opts) opts.color = false local buf = {} for i, file_report in ipairs(report) do if file_report.fatal then table.insert(buf, ("%s: %s (%s)"):format(file_names[i], fatal_type(file_report), file_report.msg)) else for _, event in ipairs(file_report) do table.insert(buf, format_event(file_names[i], event, opts)) end end end return table.concat(buf, "\n") end --- Formats a report. -- Recognized options: -- `options.formatter`: name of used formatter. Default: "default". -- `options.quiet`: integer in range 0-3. See CLI. Default: 0. -- `options.color`: should use ansicolors? Default: true. -- `options.codes`: should output warning codes? Default: false. -- `options.ranges`: should output token end column? Default: false. function format.format(report, file_names, options) return format.builtin_formatters[options.formatter or "default"](report, file_names, { quiet = options.quiet or 0, color = (options.color ~= false) and color_support, codes = options.codes, ranges = options.ranges }) end return format luacheck-0.25.0/src/luacheck/fs.lua000066400000000000000000000122401410451437000170340ustar00rootroot00000000000000local fs = {} local lfs = require "lfs" local utils = require "luacheck.utils" local function ensure_dir_sep(path) if path:sub(-1) ~= utils.dir_sep then return path .. utils.dir_sep end return path end function fs.split_base(path) if utils.is_windows then if path:match("^%a:\\") then return path:sub(1, 3), path:sub(4) else -- Disregard UNC paths and relative paths with drive letter. return "", path end else if path:match("^/") then if path:match("^//") then return "//", path:sub(3) else return "/", path:sub(2) end else return "", path end end end function fs.is_absolute(path) return fs.split_base(path) ~= "" end function fs.normalize(path) if utils.is_windows then path = path:lower() end local base, rest = fs.split_base(path) rest = rest:gsub("[/\\]", utils.dir_sep) local parts = {} for part in rest:gmatch("[^"..utils.dir_sep.."]+") do if part ~= "." then if part == ".." and #parts > 0 and parts[#parts] ~= ".." then parts[#parts] = nil else parts[#parts + 1] = part end end end if base == "" and #parts == 0 then return "." else return base..table.concat(parts, utils.dir_sep) end end local function join_two_paths(base, path) if base == "" or fs.is_absolute(path) then return path else return ensure_dir_sep(base) .. path end end function fs.join(base, ...) local res = base for i = 1, select("#", ...) do res = join_two_paths(res, select(i, ...)) end return res end function fs.is_subpath(path, subpath) local base1, rest1 = fs.split_base(path) local base2, rest2 = fs.split_base(subpath) if base1 ~= base2 then return false end if rest2:sub(1, #rest1) ~= rest1 then return false end return rest1 == rest2 or rest2:sub(#rest1 + 1, #rest1 + 1) == utils.dir_sep end function fs.is_dir(path) return lfs.attributes(path, "mode") == "directory" end function fs.is_file(path) return lfs.attributes(path, "mode") == "file" end -- Searches for file starting from path, going up until the file -- is found or root directory is reached. -- Path must be absolute. -- Returns absolute and relative paths to directory containing file or nil. function fs.find_file(path, file) if fs.is_absolute(file) then return fs.is_file(file) and path, "" end path = fs.normalize(path) local base, rest = fs.split_base(path) local rel_path = "" while true do if fs.is_file(fs.join(base..rest, file)) then return base..rest, rel_path elseif rest == "" then return end rest = rest:match("^(.*)"..utils.dir_sep..".*$") or "" rel_path = rel_path..".."..utils.dir_sep end end -- Returns iterator over directory items or nil, error message. function fs.dir_iter(dir_path) local ok, iter, state, var = pcall(lfs.dir, dir_path) if not ok then local err = utils.unprefix(iter, "cannot open " .. dir_path .. ": ") return nil, "couldn't list directory: " .. err end return iter, state, var end -- Returns list of all files in directory matching pattern. -- Additionally returns a mapping from directory paths that couldn't be expanded -- to error messages. function fs.extract_files(dir_path, pattern) local res = {} local err_map = {} local function scan(dir) local iter, state, var = fs.dir_iter(dir) if not iter then err_map[dir] = state table.insert(res, dir) return end for path in iter, state, var do if path ~= "." and path ~= ".." then local full_path = fs.join(dir, path) if fs.is_dir(full_path) then scan(full_path) elseif path:match(pattern) and fs.is_file(full_path) then table.insert(res, full_path) end end end end scan(dir_path) table.sort(res) return res, err_map end local function make_absolute_dirs(dir_path) if fs.is_dir(dir_path) then return true end local upper_dir = fs.normalize(fs.join(dir_path, "..")) if upper_dir == dir_path then return nil, ("Filesystem root %s is not a directory"):format(upper_dir) end local upper_ok, upper_err = make_absolute_dirs(upper_dir) if not upper_ok then return nil, upper_err end local make_ok, make_error = lfs.mkdir(dir_path) if not make_ok then return nil, ("Couldn't make directory %s: %s"):format(dir_path, make_error) end return true end -- Ensures that a given path is a directory, creating intermediate directories if necessary. -- Returns true on success, nil and an error message on failure. function fs.make_dirs(dir_path) return make_absolute_dirs(fs.normalize(fs.join(fs.get_current_dir(), dir_path))) end -- Returns modification time for a file. function fs.get_mtime(path) return lfs.attributes(path, "modification") end -- Returns absolute path to current working directory, with trailing directory separator. function fs.get_current_dir() return ensure_dir_sep(assert(lfs.currentdir())) end return fs luacheck-0.25.0/src/luacheck/globbing.lua000066400000000000000000000101001410451437000202000ustar00rootroot00000000000000local fs = require "luacheck.fs" local utils = require "luacheck.utils" -- Only ?, *, ** and simple character classes (with ranges and negation) are supported. -- Hidden files are not treated specially. Special characters can't be escaped. local globbing = {} local function is_regular_path(glob) return not glob:find("[*?%[]") end local function get_parts(path) local parts = {} for part in path:gmatch("[^"..utils.dir_sep.."]+") do table.insert(parts, part) end return parts end local function glob_pattern_escaper(c) return ((c == "*" or c == "?") and "." or "%")..c end local function glob_range_escaper(c) return c == "-" and c or ("%"..c) end local function glob_part_to_pattern(glob_part) local buffer = {"^"} local i = 1 while i <= #glob_part do local bracketless bracketless, i = glob_part:match("([^%[]*)()", i) table.insert(buffer, (bracketless:gsub("%p", glob_pattern_escaper))) if glob_part:sub(i, i) == "[" then table.insert(buffer, "[") i = i + 1 local first_char = glob_part:sub(i, i) if first_char == "!" then table.insert(buffer, "^") i = i + 1 elseif first_char == "]" then table.insert(buffer, "%]") i = i + 1 end bracketless, i = glob_part:match("([^%]]*)()", i) if bracketless:sub(1, 1) == "-" then table.insert(buffer, "%-") bracketless = bracketless:sub(2) end local last_dash = "" if bracketless:sub(-1) == "-" then last_dash = "-" bracketless = bracketless:sub(1, -2) end table.insert(buffer, (bracketless:gsub("%p", glob_range_escaper))) table.insert(buffer, last_dash.."]") i = i + 1 end end table.insert(buffer, "$") return table.concat(buffer) end local function part_match(glob_part, path_part) return utils.pmatch(path_part, glob_part_to_pattern(glob_part)) end local function parts_match(glob_parts, glob_i, path_parts, path_i) local glob_part = glob_parts[glob_i] if not glob_part then -- Reached glob end, path matches the glob or its subdirectory. -- E.g. path "foo/bar/baz/src.lua" matches glob "foo/*/baz". return true end if glob_part == "**" then -- "**" can consume any number of path parts. for i = path_i, #path_parts + 1 do if parts_match(glob_parts, glob_i + 1, path_parts, i) then return true end end return false end local path_part = path_parts[path_i] return path_part and part_match(glob_part, path_part) and ( parts_match(glob_parts, glob_i + 1, path_parts, path_i + 1)) end -- Checks if a path matches a globbing pattern. -- Both must be absolute. function globbing.match(glob, path) if is_regular_path(glob) then return fs.is_subpath(glob, path) end local glob_base, path_base glob_base, glob = fs.split_base(glob) path_base, path = fs.split_base(path) if glob_base ~= path_base then return false end local glob_parts = get_parts(glob) local path_parts = get_parts(path) return parts_match(glob_parts, 1, path_parts, 1) end -- Checks if glob1 is less specific than glob2 and should be applied -- first in overrides. function globbing.compare(glob1, glob2) local base1, base2 base1, glob1 = fs.split_base(glob1) base2, glob2 = fs.split_base(glob2) if base1 ~= base2 then return base1 < base2 end local parts1 = get_parts(glob1) local parts2 = get_parts(glob2) for i = 1, math.max(#parts1, #parts2) do if not parts1[i] then return true elseif not parts2[i] then return false end if (parts1[i] == "**" or parts2[i] == "**") and parts1[i] ~= parts2[i] then return parts1[i] == "**" end local _, specials1 = parts1[i]:gsub("[%*%?%[]", {}) local _, specials2 = parts2[i]:gsub("[%*%?%[]", {}) if specials1 ~= specials2 then return specials1 > specials2 end end return glob1 < glob2 end return globbing luacheck-0.25.0/src/luacheck/init.lua000066400000000000000000000103151410451437000173700ustar00rootroot00000000000000local check = require "luacheck.check" local filter = require "luacheck.filter" local options = require "luacheck.options" local format = require "luacheck.format" local utils = require "luacheck.utils" local luacheck = { _VERSION = "0.25.0" } local function raw_validate_options(fname, opts, stds, context) local ok, err = options.validate(options.all_options, opts, stds) if not ok then if context then error(("bad argument #2 to '%s' (%s: %s)"):format(fname, context, err)) else error(("bad argument #2 to '%s' (%s)"):format(fname, err)) end end end local function validate_options(fname, items, opts, stds) raw_validate_options(fname, opts) if opts ~= nil then for i in ipairs(items) do raw_validate_options(fname, opts[i], stds, ("invalid options at index [%d]"):format(i)) if opts[i] ~= nil then for j, nested_opts in ipairs(opts[i]) do raw_validate_options(fname, nested_opts, stds, ("invalid options at index [%d][%d]"):format(i, j)) end end end end end -- Returns report for a string. function luacheck.get_report(src) local msg = ("bad argument #1 to 'luacheck.get_report' (string expected, got %s)"):format(type(src)) assert(type(src) == "string", msg) return check(src) end -- Applies options to reports. Reports with .fatal field are unchanged. -- Options are applied to reports[i] in order: options, options[i], options[i][1], options[i][2], ... -- Returns new array of reports, adds .warnings, .errors and .fatals fields to this array. function luacheck.process_reports(reports, opts, stds) local msg = ("bad argument #1 to 'luacheck.process_reports' (table expected, got %s)"):format(type(reports)) assert(type(reports) == "table", msg) validate_options("luacheck.process_reports", reports, opts, stds) local report = filter.filter(reports, opts, stds) report.warnings = 0 report.errors = 0 report.fatals = 0 for _, file_report in ipairs(report) do if file_report.fatal then report.fatals = report.fatals + 1 else for _, event in ipairs(file_report) do if event.code:sub(1, 1) == "0" then report.errors = report.errors + 1 else report.warnings = report.warnings + 1 end end end end return report end -- Checks strings with options, returns report. -- Tables with .fatal field are unchanged. function luacheck.check_strings(srcs, opts) local msg = ("bad argument #1 to 'luacheck.check_strings' (table expected, got %s)"):format(type(srcs)) assert(type(srcs) == "table", msg) for _, item in ipairs(srcs) do msg = ("bad argument #1 to 'luacheck.check_strings' (array of strings or tables expected, got %s)"):format( type(item)) assert(type(item) == "string" or type(item) == "table", msg) end validate_options("luacheck.check_strings", srcs, opts) local reports = {} for i, src in ipairs(srcs) do if type(src) == "table" and src.fatal then reports[i] = src else reports[i] = luacheck.get_report(src) end end return luacheck.process_reports(reports, opts) end function luacheck.check_files(files, opts) local msg = ("bad argument #1 to 'luacheck.check_files' (table expected, got %s)"):format(type(files)) assert(type(files) == "table", msg) for _, item in ipairs(files) do msg = ("bad argument #1 to 'luacheck.check_files' (array of paths or file handles expected, got %s)"):format( type(item)) assert(type(item) == "string" or io.type(item) == "file", msg ) end validate_options("luacheck.check_files", files, opts) local srcs = {} for i, file in ipairs(files) do local src, err = utils.read_file(file) srcs[i] = src or {fatal = "I/O", msg = err} end return luacheck.check_strings(srcs, opts) end function luacheck.get_message(issue) local msg = ("bad argument #1 to 'luacheck.get_message' (table expected, got %s)"):format(type(issue)) assert(type(issue) == "table", msg) return format.get_message(issue) end setmetatable(luacheck, {__call = function(_, ...) return luacheck.check_files(...) end}) return luacheck luacheck-0.25.0/src/luacheck/lexer.lua000066400000000000000000000465521410451437000175600ustar00rootroot00000000000000local utils = require "luacheck.utils" -- Lexer should support syntax of Lua 5.1, Lua 5.2, Lua 5.3, Lua 5.4 and LuaJIT(64bit and complex cdata literals). local lexer = {} local sbyte = string.byte local schar = string.char local sreverse = string.reverse local tconcat = table.concat local mfloor = math.floor -- No point in inlining these, fetching a constant ~= fetching a local. local BYTE_0, BYTE_9, BYTE_f, BYTE_F = sbyte("0"), sbyte("9"), sbyte("f"), sbyte("F") local BYTE_x, BYTE_X, BYTE_i, BYTE_I = sbyte("x"), sbyte("X"), sbyte("i"), sbyte("I") local BYTE_l, BYTE_L, BYTE_u, BYTE_U = sbyte("l"), sbyte("L"), sbyte("u"), sbyte("U") local BYTE_e, BYTE_E, BYTE_p, BYTE_P = sbyte("e"), sbyte("E"), sbyte("p"), sbyte("P") local BYTE_a, BYTE_z, BYTE_A, BYTE_Z = sbyte("a"), sbyte("z"), sbyte("A"), sbyte("Z") local BYTE_DOT, BYTE_COLON = sbyte("."), sbyte(":") local BYTE_OBRACK, BYTE_CBRACK = sbyte("["), sbyte("]") local BYTE_OBRACE, BYTE_CBRACE = sbyte("{"), sbyte("}") local BYTE_QUOTE, BYTE_DQUOTE = sbyte("'"), sbyte('"') local BYTE_PLUS, BYTE_DASH, BYTE_LDASH = sbyte("+"), sbyte("-"), sbyte("_") local BYTE_SLASH, BYTE_BSLASH = sbyte("/"), sbyte("\\") local BYTE_EQ, BYTE_NE = sbyte("="), sbyte("~") local BYTE_LT, BYTE_GT = sbyte("<"), sbyte(">") local BYTE_LF, BYTE_CR = sbyte("\n"), sbyte("\r") local BYTE_SPACE, BYTE_FF, BYTE_TAB, BYTE_VTAB = sbyte(" "), sbyte("\f"), sbyte("\t"), sbyte("\v") local function to_hex(b) if BYTE_0 <= b and b <= BYTE_9 then return b-BYTE_0 elseif BYTE_a <= b and b <= BYTE_f then return 10+b-BYTE_a elseif BYTE_A <= b and b <= BYTE_F then return 10+b-BYTE_A else return nil end end local function to_dec(b) if BYTE_0 <= b and b <= BYTE_9 then return b-BYTE_0 else return nil end end local function to_utf(codepoint) if codepoint < 0x80 then -- ASCII? return schar(codepoint) end local buf = {} local mfb = 0x3F repeat buf[#buf+1] = schar(codepoint % 0x40 + 0x80) codepoint = mfloor(codepoint / 0x40) mfb = mfloor(mfb / 2) until codepoint <= mfb buf[#buf+1] = schar(0xFE - mfb*2 + codepoint) return sreverse(tconcat(buf)) end local function is_alpha(b) return (BYTE_a <= b and b <= BYTE_z) or (BYTE_A <= b and b <= BYTE_Z) or b == BYTE_LDASH end local function is_newline(b) return (b == BYTE_LF) or (b == BYTE_CR) end local function is_space(b) return (b == BYTE_SPACE) or (b == BYTE_FF) or (b == BYTE_TAB) or (b == BYTE_VTAB) end local keywords = utils.array_to_set({ "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "goto", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while"}) local simple_escapes = { [sbyte("a")] = sbyte("\a"), [sbyte("b")] = sbyte("\b"), [sbyte("f")] = sbyte("\f"), [sbyte("n")] = sbyte("\n"), [sbyte("r")] = sbyte("\r"), [sbyte("t")] = sbyte("\t"), [sbyte("v")] = sbyte("\v"), [BYTE_BSLASH] = BYTE_BSLASH, [BYTE_QUOTE] = BYTE_QUOTE, [BYTE_DQUOTE] = BYTE_DQUOTE } local function next_byte(state) local offset = state.offset + 1 state.offset = offset return state.src:get_codepoint(offset) end -- Skipping helpers. -- Take the current character, skip something, return next character. local function skip_newline(state, newline) local first_newline_offset = state.offset local b = next_byte(state) if b ~= newline and is_newline(b) then b = next_byte(state) end local line = state.line local line_offsets = state.line_offsets state.line_lengths[line] = first_newline_offset - line_offsets[line] line = line + 1 state.line = line line_offsets[line] = state.offset return b end local function skip_to_newline(state, b) while not is_newline(b) and b do b = next_byte(state) end return b end local function skip_space(state, b) while is_space(b) or is_newline(b) do if is_newline(b) then b = skip_newline(state, b) else b = next_byte(state) end end return b end -- Skips "[=*" or "]=*". Returns next character and number of "="s. local function skip_long_bracket(state) local start = state.offset local b = next_byte(state) while b == BYTE_EQ do b = next_byte(state) end return b, state.offset-start-1 end -- Token handlers. -- Called after the opening "[=*" has been skipped. -- Takes number of "=" in the opening bracket and token type(comment or string). local function lex_long_string(state, opening_long_bracket, token) local b = next_byte(state) if is_newline(b) then b = skip_newline(state, b) end local lines = {} local line_start = state.offset while true do if is_newline(b) then -- Add the finished line. lines[#lines+1] = state.src:get_substring(line_start, state.offset-1) b = skip_newline(state, b) line_start = state.offset elseif b == BYTE_CBRACK then local long_bracket b, long_bracket = skip_long_bracket(state) if b == BYTE_CBRACK and long_bracket == opening_long_bracket then break end elseif b == nil then return nil, token == "string" and "unfinished long string" or "unfinished long comment" else b = next_byte(state) end end -- Add last line. lines[#lines+1] = state.src:get_substring(line_start, state.offset-opening_long_bracket-2) state.offset = state.offset + 1 return token, tconcat(lines, "\n") end local function lex_short_string(state, quote) local b = next_byte(state) local chunks -- Buffer is only required when there are escape sequences. local chunk_start = state.offset while b ~= quote do if b == BYTE_BSLASH then -- Escape sequence. if not chunks then -- This is the first escape sequence, init buffer. chunks = {} end -- Put previous chunk into buffer. if chunk_start ~= state.offset then chunks[#chunks+1] = state.src:get_substring(chunk_start, state.offset-1) end b = next_byte(state) -- The final string escape sequence evaluates to. local s local escape_byte = simple_escapes[b] if escape_byte then -- Is it a simple escape sequence? b = next_byte(state) s = schar(escape_byte) elseif is_newline(b) then b = skip_newline(state, b) s = "\n" elseif b == BYTE_x then -- Hexadecimal escape. b = next_byte(state) -- Skip "x". -- Exactly two hexadecimal digits. local c1, c2 if b then c1 = to_hex(b) end if not c1 then return nil, "invalid hexadecimal escape sequence", -2 end b = next_byte(state) if b then c2 = to_hex(b) end if not c2 then return nil, "invalid hexadecimal escape sequence", -3 end b = next_byte(state) s = schar(c1*16 + c2) elseif b == BYTE_u then b = next_byte(state) -- Skip "u". if b ~= BYTE_OBRACE then return nil, "invalid UTF-8 escape sequence", -2 end b = next_byte(state) -- Skip "{". local codepoint -- There should be at least one digit. if b then codepoint = to_hex(b) end if not codepoint then return nil, "invalid UTF-8 escape sequence", -3 end local hexdigits = 0 while true do b = next_byte(state) local hex if b then hex = to_hex(b) end if hex then hexdigits = hexdigits + 1 codepoint = codepoint*16 + hex if codepoint > 0x7FFFFFFF then -- UTF-8 value too large. return nil, "invalid UTF-8 escape sequence", -hexdigits-3 end else break end end if b ~= BYTE_CBRACE then return nil, "invalid UTF-8 escape sequence", -hexdigits-4 end b = next_byte(state) -- Skip "}". s = to_utf(codepoint) elseif b == BYTE_z then -- Zap following span of spaces. b = skip_space(state, next_byte(state)) else -- Must be a decimal escape. local cb if b then cb = to_dec(b) end if not cb then return nil, "invalid escape sequence", -1 end -- Up to three decimal digits. b = next_byte(state) if b then local c2 = to_dec(b) if c2 then cb = 10*cb + c2 b = next_byte(state) if b then local c3 = to_dec(b) if c3 then cb = 10*cb + c3 if cb > 255 then return nil, "invalid decimal escape sequence", -3 end b = next_byte(state) end end end end s = schar(cb) end if s then chunks[#chunks+1] = s end -- Next chunk starts after escape sequence. chunk_start = state.offset elseif b == nil or is_newline(b) then return nil, "unfinished string" else b = next_byte(state) end end -- Offset now points at the closing quote. local string_value if chunks then -- Put last chunk into buffer. if chunk_start ~= state.offset then chunks[#chunks+1] = state.src:get_substring(chunk_start, state.offset-1) end string_value = tconcat(chunks) else -- There were no escape sequences. string_value = state.src:get_substring(chunk_start, state.offset-1) end -- Skip the closing quote. state.offset = state.offset + 1 return "string", string_value end -- Payload for a number is simply a substring. -- Luacheck is supposed to be forward-compatible with Lua 5.3 and LuaJIT syntax, so -- parsing it into actual number may be problematic. -- It is not needed currently anyway as Luacheck does not do static evaluation yet. local function lex_number(state, b) local start = state.offset local exp_lower, exp_upper = BYTE_e, BYTE_E local is_digit = to_dec local has_digits = false local is_float = false if b == BYTE_0 then b = next_byte(state) if b == BYTE_x or b == BYTE_X then exp_lower, exp_upper = BYTE_p, BYTE_P is_digit = to_hex b = next_byte(state) else has_digits = true end end while b ~= nil and is_digit(b) do b = next_byte(state) has_digits = true end if b == BYTE_DOT then -- Fractional part. is_float = true b = next_byte(state) -- Skip dot. while b ~= nil and is_digit(b) do b = next_byte(state) has_digits = true end end if b == exp_lower or b == exp_upper then -- Exponent part. is_float = true b = next_byte(state) -- Skip optional sign. if b == BYTE_PLUS or b == BYTE_DASH then b = next_byte(state) end -- Exponent consists of one or more decimal digits. if b == nil or not to_dec(b) then return nil, "malformed number" end repeat b = next_byte(state) until b == nil or not to_dec(b) end if not has_digits then return nil, "malformed number" end -- Is it cdata literal? if b == BYTE_i or b == BYTE_I then -- It is complex literal. Skip "i" or "I". state.offset = state.offset + 1 else -- uint64_t and int64_t literals can not be fractional. if not is_float then if b == BYTE_u or b == BYTE_U then -- It may be uint64_t literal. local b1 = state.src:get_codepoint(state.offset+1) if b1 == BYTE_l or b1 == BYTE_L then local b2 = state.src:get_codepoint(state.offset+2) if b2 == BYTE_l or b2 == BYTE_L then -- It is uint64_t literal. state.offset = state.offset + 3 end end elseif b == BYTE_l or b == BYTE_L then -- It may be uint64_t or int64_t literal. local b1 = state.src:get_codepoint(state.offset+1) if b1 == BYTE_l or b1 == BYTE_L then local b2 = state.src:get_codepoint(state.offset+2) if b2 == BYTE_u or b2 == BYTE_U then -- It is uint64_t literal. state.offset = state.offset + 3 else -- It is int64_t literal. state.offset = state.offset + 2 end end end end end return "number", state.src:get_substring(start, state.offset-1) end local function lex_ident(state) local start = state.offset local b = next_byte(state) while (b ~= nil) and (is_alpha(b) or to_dec(b)) do b = next_byte(state) end local ident = state.src:get_substring(start, state.offset-1) if keywords[ident] then return ident else return "name", ident end end local function lex_dash(state) local b = next_byte(state) -- Is it "-" or comment? if b ~= BYTE_DASH then return "-" end -- It is a comment. b = next_byte(state) local start = state.offset -- Is it a long comment? if b == BYTE_OBRACK then local long_bracket b, long_bracket = skip_long_bracket(state) if b == BYTE_OBRACK then return lex_long_string(state, long_bracket, "long_comment") end end -- Short comment. skip_to_newline(state, b) local comment_value = state.src:get_substring(start, state.offset - 1) return "short_comment", comment_value end local function lex_bracket(state) -- Is it "[" or long string? local b, long_bracket = skip_long_bracket(state) if b == BYTE_OBRACK then return lex_long_string(state, long_bracket, "string") elseif long_bracket == 0 then return "[" else return nil, "invalid long string delimiter" end end local function lex_eq(state) local b = next_byte(state) if b == BYTE_EQ then state.offset = state.offset + 1 return "==" else return "=" end end local function lex_lt(state) local b = next_byte(state) if b == BYTE_EQ then state.offset = state.offset + 1 return "<=" elseif b == BYTE_LT then state.offset = state.offset + 1 return "<<" else return "<" end end local function lex_gt(state) local b = next_byte(state) if b == BYTE_EQ then state.offset = state.offset + 1 return ">=" elseif b == BYTE_GT then state.offset = state.offset + 1 return ">>" else return ">" end end local function lex_div(state) local b = next_byte(state) if b == BYTE_SLASH then state.offset = state.offset + 1 return "//" else return "/" end end local function lex_ne(state) local b = next_byte(state) if b == BYTE_EQ then state.offset = state.offset + 1 return "~=" else return "~" end end local function lex_colon(state) local b = next_byte(state) if b == BYTE_COLON then state.offset = state.offset + 1 return "::" else return ":" end end local function lex_dot(state) local b = next_byte(state) if b == BYTE_DOT then b = next_byte(state) if b == BYTE_DOT then state.offset = state.offset + 1 return "...", "..." else return ".." end elseif b and to_dec(b) then -- Backtrack to dot. state.offset = state.offset - 2 return lex_number(state, next_byte(state)) else return "." end end local function lex_any(state, b) state.offset = state.offset + 1 if b > 255 then b = 255 end return schar(b) end -- Maps first bytes of tokens to functions that handle them. -- Each handler takes the first byte as an argument. -- Each handler stops at the character after the token and returns the token and, -- optionally, a value associated with the token. -- On error handler returns nil, error message and, optionally, start of reported location as negative offset. local byte_handlers = { [BYTE_DOT] = lex_dot, [BYTE_COLON] = lex_colon, [BYTE_OBRACK] = lex_bracket, [BYTE_QUOTE] = lex_short_string, [BYTE_DQUOTE] = lex_short_string, [BYTE_DASH] = lex_dash, [BYTE_SLASH] = lex_div, [BYTE_EQ] = lex_eq, [BYTE_NE] = lex_ne, [BYTE_LT] = lex_lt, [BYTE_GT] = lex_gt, [BYTE_LDASH] = lex_ident } for b=BYTE_0, BYTE_9 do byte_handlers[b] = lex_number end for b=BYTE_a, BYTE_z do byte_handlers[b] = lex_ident end for b=BYTE_A, BYTE_Z do byte_handlers[b] = lex_ident end -- Creates and returns lexer state for source. function lexer.new_state(src, line_offsets, line_lengths) local state = { src = src, line = 1, line_offsets = line_offsets or {}, line_lengths = line_lengths or {}, offset = 1 } state.line_offsets[1] = 1 if src:get_length() >= 2 and src:get_substring(1, 2) == "#!" then -- Skip shebang line. state.offset = 2 skip_to_newline(state, next_byte(state)) end return state end function lexer.get_quoted_substring_or_line(state, line, offset, end_offset) local line_length = state.line_lengths[line] if line_length then local line_end_offset = state.line_offsets[line] + line_length - 1 if line_end_offset < end_offset then end_offset = line_end_offset end end return "'" .. state.src:get_printable_substring(offset, end_offset) .. "'" end -- Looks for next token starting from state.line, state.offset. -- Returns next token, its value and its location (line, offset). -- Sets state.line, state.offset to token end location + 1. -- Fills state.line_offsets and state.line_lengths. -- On error returns nil, error message, error location (line, offset), error end offset. function lexer.next_token(state) local line_offsets = state.line_offsets local b = skip_space(state, state.src:get_codepoint(state.offset)) -- Save location of token start. local token_line = state.line local line_offset = line_offsets[token_line] local token_offset = state.offset if not b then -- EOF token has length 1. state.offset = state.offset + 1 state.line_lengths[token_line] = token_offset - line_offset return "eof", nil, token_line, token_offset end local token, token_value, relative_error_offset = (byte_handlers[b] or lex_any)(state, b) if relative_error_offset then -- Error relative to current offset. local error_offset = state.offset + relative_error_offset local error_end_offset = math.min(state.offset, state.src:get_length()) local error_message = token_value .. " " .. lexer.get_quoted_substring_or_line(state, state.line, error_offset, error_end_offset) return nil, error_message, state.line, error_offset, error_end_offset end -- Single character errors fall through here. return token, token_value, token_line, token_offset, not token and token_offset end return lexer luacheck-0.25.0/src/luacheck/main.lua000066400000000000000000000324761410451437000173650ustar00rootroot00000000000000local argparse = require "argparse" local cache = require "luacheck.cache" local config = require "luacheck.config" local luacheck = require "luacheck" local multithreading = require "luacheck.multithreading" local profiler = require "luacheck.profiler" local runner = require "luacheck.runner" local utils = require "luacheck.utils" local version = require "luacheck.version" local exit_codes = { ok = 0, warnings = 1, errors = 2, fatals = 3, critical = 4 } local function critical(msg) io.stderr:write("Critical error: "..msg.."\n") os.exit(exit_codes.critical) end local function get_parser() local parser = argparse( "luacheck", "luacheck " .. luacheck._VERSION .. ", a linter and a static analyzer for Lua.", [[ Links: Luacheck on GitHub: https://github.com/luarocks/luacheck Luacheck documentation: https://luacheck.readthedocs.org]]) :help_max_width(80) parser:argument("files", "List of files, directories and rockspecs to check. Pass '-' to check stdin.") :args "+" :argname "" parser:group("Options for filtering warnings", parser:flag("-g --no-global", "Filter out warnings related to global variables. " .. "Equivalent to --ignore 1."):target("global"):action("store_false"), parser:flag("-u --no-unused", "Filter out warnings related to unused variables and values. " .. "Equivalent to --ignore [23]."):target("unused"):action("store_false"), parser:flag("-r --no-redefined", "Filter out warnings related to redefined variables. " .. "Equivalent to --ignore 4."):target("redefined"):action("store_false"), parser:flag("-a --no-unused-args", "Filter out warnings related to unused arguments and " .. "loop variables. Equivalent to --ignore 21[23]."):target("unused_args"):action("store_false"), parser:flag("-s --no-unused-secondaries", "Filter out warnings related to unused variables set " .. "together with used ones."):target("unused_secondaries"):action("store_false"), parser:flag("--no-self", "Filter out warnings related to implicit self argument.") :target("self"):action("store_false"), parser:option("--ignore -i", "Filter out warnings matching these patterns.\n" .. "If a pattern contains slash, part before slash matches warning code and part after it matches name of " .. "related variable. Otherwise, if the pattern contains letters or underscore, it matches name of " .. "related variable. Otherwise, the pattern matches warning code.") :args "+" :count "*" :argname "" :action "concat" :init(nil), parser:option("--enable -e", "Do not filter out warnings matching these patterns.") :args "+" :count "*" :argname "" :action "concat" :init(nil), parser:option("--only -o", "Filter out warnings not matching these patterns.") :args "+" :count "*" :argname "" :action "concat" :init(nil)) parser:group("Options for configuring allowed globals", parser:option("--std", "Set standard globals, default is max. can be one of:\n" .. " max - union of globals of Lua 5.1, Lua 5.2, Lua 5.3 and LuaJIT 2.x;\n" .. " min - intersection of globals of Lua 5.1, Lua 5.2, Lua 5.3 and LuaJIT 2.x;\n" .. " lua51 - globals of Lua 5.1 without deprecated ones;\n" .. " lua51c - globals of Lua 5.1;\n" .. " lua52 - globals of Lua 5.2;\n" .. " lua52c - globals of Lua 5.2 with LUA_COMPAT_ALL;\n" .. " lua53 - globals of Lua 5.3;\n" .. " lua53c - globals of Lua 5.3 with LUA_COMPAT_5_2;\n" .. " lua54 - globals of Lua 5.4;\n" .. " lua54c - globals of Lua 5.4 with LUA_COMPAT_5_3;\n" .. " luajit - globals of LuaJIT 2.x;\n" .. " ngx_lua - globals of Openresty lua-nginx-module 0.10.10, including standard LuaJIT 2.x globals;\n" .. " love - globals added by LÖVE;\n" .. " busted - globals added by Busted 2.0, by default added for files ending with _spec.lua within spec, " .. "test, and tests subdirectories;\n" .. " rockspec - globals allowed in rockspecs, by default added for files ending with .rockspec;\n" .. " luacheckrc - globals allowed in Luacheck configs, by default added for files ending with .luacheckrc;\n" .. " none - no standard globals.\n\n" .. "Sets can be combined using '+'. Extra sets can be defined in config by " .. "adding to `stds` global in config."), parser:flag("-c --compat", "Equivalent to --std max."), parser:option("--globals", "Add custom global variables (e.g. foo) or fields (e.g. foo.bar) " .. "on top of standard ones.") :args "*" :count "*" :argname "" :action "concat" :init(nil), parser:option("--read-globals", "Add read-only global variables or fields.") :args "*" :count "*" :argname "" :action "concat" :init(nil), parser:option("--new-globals", "Set custom global variables or fields. Removes custom globals added previously.") :args "*" :count "*" :argname "" :action "concat" :init(nil), parser:option("--new-read-globals", "Set read-only global variables or fields. " .. "Removes read-only globals added previously.") :args "*" :count "*" :argname "" :action "concat" :init(nil), parser:option("--not-globals", "Remove custom and standard global variables or fields.") :args "*" :count "*" :argname "" :action "concat" :init(nil), parser:flag("-d --allow-defined", "Allow defining globals implicitly by setting them."), parser:flag("-t --allow-defined-top", "Allow defining globals implicitly by setting them in the top level scope."), parser:flag("-m --module", "Limit visibility of implicitly defined globals to their files.")) parser:group("Options for configuring line length limits", parser:option("--max-line-length", "Set maximum allowed line length (default: 120).") :argname "" :convert(tonumber), parser:flag("--no-max-line-length", "Do not limit line length.") :action "store_false" :target "max_line_length", parser:option("--max-code-line-length", "Set maximum allowed length for lines ending with code (default: 120).") :argname "" :convert(tonumber), parser:flag("--no-max-code-line-length", "Do not limit code line length.") :action "store_false" :target "max_code_line_length", parser:option("--max-string-line-length", "Set maximum allowed length for lines within a string (default: 120).") :argname "" :convert(tonumber), parser:flag("--no-max-string-line-length", "Do not limit string line length.") :action "store_false" :target "max_string_line_length", parser:option("--max-comment-line-length", "Set maximum allowed length for comment lines (default: 120).") :argname "" :convert(tonumber), parser:flag("--no-max-comment-line-length", "Do not limit comment line length.") :action "store_false" :target "max_comment_line_length") parser:option("--max-cyclomatic-complexity", "Set maximum cyclomatic complexity for functions.") :argname "" :convert(tonumber) parser:flag("--no-max-cyclomatic-complexity", "Do not limit function cyclomatic complexity (default).") :action "store_false" :target "max_cyclomatic_complexity" local default_global_path = config.get_default_global_path() local config_opt = parser:option("--config", "Path to configuration file. (default: "..config.default_path..")") local no_config_opt = parser:flag("--no-config", "Do not look up configuration file.") :action "store_false" :target "config" parser:mutex(config_opt, no_config_opt) local default_config_opt = parser:option("--default-config", ("Path to configuration file to use if --[no-]config ".. "is not used and project-specific %s is not found. (default: %s)"):format( config.default_path, default_global_path or "could not detect")) local no_default_config_opt = parser:flag("--no-default-config", "Do not use default configuration file.") :action "store_false" :target "default_config" parser:mutex(default_config_opt, no_default_config_opt) parser:group("Configuration file options", config_opt, no_config_opt, default_config_opt, no_default_config_opt) parser:group("File filtering options", parser:option("--exclude-files", "Do not check files matching these globbing patterns.") :args "+" :count "*" :argname "" :action "concat" :init(nil), parser:option("--include-files", "Do not check files not matching these globbing patterns.") :args "+" :count "*" :argname "" :action "concat" :init(nil)) parser:option("--filename", "Use another filename in output and for selecting configuration overrides.") local cache_opt = parser:option("--cache", ("Path to cache directory. (default: %s)"):format( cache.get_default_dir())) :args "?" local no_cache_opt = parser:flag("--no-cache", "Do not use cache.") :action "store_false" :target "cache" parser:mutex(cache_opt, no_cache_opt) local lanes_notice = "" if not multithreading.has_lanes then lanes_notice = "\nWarning: LuaLanes not found, parallel checking disabled." end parser:group("Performance optimization options", cache_opt, no_cache_opt, parser:option( "-j --jobs", "Check files in parallel (default: " .. tostring(multithreading.default_jobs) .. ")." .. lanes_notice):convert(tonumber)) parser:group("Output formatting options", parser:option("--formatter" , "Use custom formatter. must be a module name or one of:\n" .. " TAP - Test Anything Protocol formatter;\n" .. " JUnit - JUnit XML formatter;\n" .. " visual_studio - MSBuild/Visual Studio aware formatter;\n" .. " plain - simple warning-per-line formatter;\n" .. " default - standard formatter."), parser:flag("-q --quiet", "Suppress output for files without warnings.\n" .. "-qq: Suppress output of warnings.\n" .. "-qqq: Only print total number of warnings and errors.") :count "0-3", parser:flag("--codes", "Show warning codes."), parser:flag("--ranges", "Show ranges of columns related to warnings."), parser:flag("--no-color", "Do not color output.") :action "store_false" :target "color") parser:flag("--profile", "Show performance statistics."):hidden(true) parser:flag("-v --version", "Show version info and exit.") :action(function() print(version.string) os.exit(exit_codes.ok) end) return parser end local function main() local parser = get_parser() local ok, args = parser:pparse() if not ok then io.stderr:write(("%s\n\nError: %s\n"):format(parser:get_usage(), args)) os.exit(exit_codes.critical) end if args.profile then args.jobs = 1 profiler.init() end if args.quiet == 0 then args.quiet = nil end if args.cache then args.cache = args.cache[1] or true end local checker, err, is_invalid_args_error = runner.new(args) if not checker then if is_invalid_args_error then io.stderr:write(("%s\n\nError: %s\n"):format(parser:get_usage(), err)) os.exit(exit_codes.critical) else critical(err) end end local inputs = {} for _, file in ipairs(args.files) do local input = {filename = args.filename} if file == "-" then input.file = io.stdin elseif file:find("%.rockspec$") then input.rockspec_path = file else input.path = file end table.insert(inputs, input) end local report, check_err = checker:check(inputs) if not report then critical(check_err) end for _, file_report in ipairs(report) do if not file_report.filename then file_report.filename = "stdin" end end local output, format_err = checker:format(report) if not output then critical(format_err) end io.stdout:write(output) if args.profile then profiler.report() end if report.fatals > 0 then os.exit(exit_codes.fatals) elseif report.errors > 0 then os.exit(exit_codes.errors) elseif report.warnings > 0 then os.exit(exit_codes.warnings) else os.exit(exit_codes.ok) end end local _, error_wrapper = utils.try(main) local err = error_wrapper.err local traceback = error_wrapper.traceback if utils.is_instance(err, utils.InvalidPatternError) then critical(("Invalid pattern '%s'"):format(err.pattern)) elseif type(err) == "string" and err:find("interrupted!$") then critical("Interrupted") else local msg = ("Luacheck %s bug (please report at https://github.com/luarocks/luacheck/issues):\n%s\n%s"):format( luacheck._VERSION, err, traceback) critical(msg) end luacheck-0.25.0/src/luacheck/multithreading.lua000066400000000000000000000046141410451437000214520ustar00rootroot00000000000000local utils = require "luacheck.utils" local multithreading = {} local lanes_ok, lanes = pcall(require, "lanes") lanes_ok = lanes_ok and pcall(lanes.configure) multithreading.has_lanes = lanes_ok multithreading.lanes = lanes multithreading.default_jobs = 1 if not lanes_ok then return multithreading end local cpu_number_detection_commands = {} if utils.is_windows then cpu_number_detection_commands[1] = "echo %NUMBER_OF_PROCESSORS%" else cpu_number_detection_commands[1] = "getconf _NPROCESSORS_ONLN 2>&1" cpu_number_detection_commands[2] = "sysctl -n hw.ncpu 2>&1" cpu_number_detection_commands[3] = "psrinfo -p 2>&1" end for _, command in ipairs(cpu_number_detection_commands) do local handler = io.popen(command) if handler then local output = handler:read("*a") handler:close() if output then local cpu_number = tonumber(utils.strip(output)) if cpu_number then multithreading.default_jobs = math.floor(math.max(cpu_number, 1)) break end end end end -- Reads pairs {key, arg} from given linda slot until it gets nil as arg. -- Returns table with pairs [key] = func(arg). local function worker_task(linda, input_slot, func) local results = {} while true do local _, pair = linda:receive(nil, input_slot) local key, arg = pair[1], pair[2] if arg == nil then return results end results[key] = func(arg) end end local function protected_worker_task(...) return true, utils.try(worker_task, ...) end local worker_gen = lanes.gen("*", protected_worker_task) -- Maps func over array, performing at most jobs calls in parallel. function multithreading.pmap(func, array, jobs) jobs = jobs or multithreading.default_jobs jobs = math.min(jobs, #array) if jobs < 2 then return utils.map(func, array) end local workers = {} local linda = lanes.linda() for i = 1, jobs do workers[i] = worker_gen(linda, 0, func) end for i, item in ipairs(array) do linda:send(nil, 0, {i, item}) end for _ = 1, jobs do linda:send(nil, 0, {}) end local results = {} for _, worker in ipairs(workers) do local _, ok, worker_results = assert(worker:join()) if ok then utils.update(results, worker_results) else error(worker_results, 0) end end return results end return multithreading luacheck-0.25.0/src/luacheck/options.lua000066400000000000000000000272321410451437000201260ustar00rootroot00000000000000local options = {} local builtin_standards = require "luacheck.builtin_standards" local standards = require "luacheck.standards" local utils = require "luacheck.utils" local boolean = utils.has_type("boolean") local number_or_false = utils.has_type_or_false("number") local array_of_strings = utils.array_of("string") -- Validates std string. -- Returns an array of std names with `add` field if there is `+` at the beginning of the string. -- On validation error returns `nil` and an error message. local function split_std(std, stds) local parts = utils.split(std, "+") if parts[1]:match("^%s*$") then parts.add = true table.remove(parts, 1) end for i, part in ipairs(parts) do parts[i] = utils.strip(part) if not stds[parts[i]] then return nil, ("unknown std '%s'"):format(parts[i]) end end return parts end local function std_or_array_of_strings(x, stds) if type(x) == "string" then local ok, err = split_std(x, stds) return not not ok, err elseif type(x) == "table" then return standards.validate_std_table(x) else return false, "string or table expected, got " .. type(x) end end local function field_map(x) if type(x) == "table" then return standards.validate_globals_table(x) else return false, "table expected, got " .. type(x) end end options.nullary_inline_options = { global = boolean, unused = boolean, redefined = boolean, unused_args = boolean, unused_secondaries = boolean, self = boolean, compat = boolean, allow_defined = boolean, allow_defined_top = boolean, module = boolean } options.variadic_inline_options = { globals = field_map, read_globals = field_map, new_globals = field_map, new_read_globals = field_map, not_globals = array_of_strings, ignore = array_of_strings, enable = array_of_strings, only = array_of_strings } options.all_options = { std = std_or_array_of_strings, max_line_length = number_or_false, max_code_line_length = number_or_false, max_string_line_length = number_or_false, max_comment_line_length = number_or_false, max_cyclomatic_complexity = number_or_false } utils.update(options.all_options, options.nullary_inline_options) utils.update(options.all_options, options.variadic_inline_options) -- Returns true if opts is valid option_set or is nil. -- Otherwise returns false and an error message. function options.validate(option_set, opts, stds) if opts == nil then return true end if type(opts) ~= "table" then return false, "option table expected, got " .. type(opts) end stds = stds or builtin_standards for option, validator in utils.sorted_pairs(option_set) do if opts[option] ~= nil then local ok, err = validator(opts[option], stds) if not ok then return false, ("invalid value of option '%s': %s"):format(option, err) end end end return true end -- Option stack is an array of options with options closer to end -- overriding options closer to beginning. -- Extracts sequence of active std tables from an option stack. local function get_std_tables(opts_stack, stds) local base_std local add_stds = {} local no_compat = false for _, opts in utils.ripairs(opts_stack) do if opts.compat and not no_compat then base_std = stds.max break elseif opts.compat == false then no_compat = true end if opts.std then if type(opts.std) == "table" then base_std = opts.std break else local parts = split_std(opts.std, stds) for _, part in ipairs(parts) do table.insert(add_stds, stds[part]) end if not parts.add then base_std = {} break end end end end table.insert(add_stds, 1, base_std or stds.max) return add_stds end -- Returns index of the last option table in a stack that uses given option, -- or zero if the option isn't used anywhere. local function index_of_last_option_usage(opts_stack, option_name) for index, opts in utils.ripairs(opts_stack) do if opts[option_name] then return index end end return 0 end local function split_field(field_name) return utils.split(field_name, "%.") end local function field_comparator(field1, field2) local parts1 = field1[1] local parts2 = field2[1] for i = 1, math.max(#parts1, #parts2) do local part1 = parts1[i] local part2 = parts2[i] if not part1 then return true elseif not part2 then return false end if part1 ~= part2 then return part1 < part2 end end return false end -- Combine all stds and global related options into one final definition table. -- A definition table may have fields `read_only` (boolean), `other_fields` (boolean), -- and `fields` (maps field names to definition tables). -- Std table format is similar, except at the top level there are two fields -- `globals` and `read_globals` mapping to top-level field tables. Also in field tables -- it's possible to use field names in array part as a shortcut: -- `{fields = {"foo"}}` is equivalent to `{fields = {foo = {}}}` or `{fields = {foo = {other_fields = true}}}` -- in top level fields tables. local function get_final_std(opts_stack, stds) local final_std = {} local std_tables = get_std_tables(opts_stack, stds) for _, std_table in ipairs(std_tables) do standards.add_std_table(final_std, std_table) end local last_new_globals = index_of_last_option_usage(opts_stack, "new_globals") local last_new_read_globals = index_of_last_option_usage(opts_stack, "new_read_globals") for index, opts in ipairs(opts_stack) do local globals = (index >= last_new_globals) and (opts.new_globals or opts.globals) local read_globals = (index >= last_new_read_globals) and (opts.new_read_globals or opts.read_globals) local new_fields = {} if globals then for _, global in ipairs(globals) do table.insert(new_fields, {split_field(global), false}) end end if read_globals then for _, read_global in ipairs(read_globals) do table.insert(new_fields, {split_field(read_global), true}) end end if globals and read_globals then -- If there are both globals and read-only globals defined in one options table, -- it's important that more general definitions are applied first, -- otherwise they will completely overwrite more specific definitions. -- E.g. `globals x` should be applied before `read globals x.y`. table.sort(new_fields, field_comparator) end for _, field in ipairs(new_fields) do standards.overwrite_field(final_std, field[1], field[2]) end standards.add_std_table(final_std, {globals = globals, read_globals = read_globals}, true, true) if opts.not_globals then for _, not_global in ipairs(opts.not_globals) do standards.remove_field(final_std, split_field(not_global)) end end end standards.finalize(final_std) return final_std end local function get_scalar_opt(opts_stack, option, default) for _, opts in utils.ripairs(opts_stack) do if opts[option] ~= nil then return opts[option] end end return default end local line_length_suboptions = {"max_code_line_length", "max_string_line_length", "max_comment_line_length"} local function get_max_line_opts(opts_stack) local res = {max_line_length = 120} for _, opt_name in ipairs(line_length_suboptions) do res[opt_name] = res.max_line_length end for _, opts in ipairs(opts_stack) do if opts.max_line_length ~= nil then res.max_line_length = opts.max_line_length for _, opt_name in ipairs(line_length_suboptions) do res[opt_name] = opts.max_line_length end end for _, opt_name in ipairs(line_length_suboptions) do if opts[opt_name] ~= nil then res[opt_name] = opts[opt_name] end end end return res end local function anchor_pattern(pattern, only_start) if not pattern then return end if pattern:sub(1, 1) == "^" or pattern:sub(-1) == "$" then return pattern else return "^" .. pattern .. (only_start and "" or "$") end end -- Returns {pair of normalized patterns for code and name}. -- `pattern` can be: -- string containing '/': first part matches warning code, second - variable name; -- string containing letters: matches variable name; -- otherwise: matches warning code. -- Unless anchored by user, pattern for name is anchored from both sides -- and pattern for code is only anchored at the beginning. local function normalize_pattern(pattern) local code_pattern, name_pattern local slash_pos = pattern:find("/") if slash_pos then code_pattern = pattern:sub(1, slash_pos - 1) name_pattern = pattern:sub(slash_pos + 1) elseif pattern:find("[_a-zA-Z]") then name_pattern = pattern else code_pattern = pattern end return {anchor_pattern(code_pattern, true), anchor_pattern(name_pattern)} end -- From most specific to less specific, pairs {option, pattern}. -- Applying macros in order is required to get deterministic results -- and get sensible results when intersecting macros are used. -- E.g. unused = false, unused_args = true should leave unused args enabled. local macros = { {"unused_args", "21[23]"}, {"global", "1"}, {"unused", "[23]"}, {"redefined", "4"} } -- Returns array of rules which should be applied in order. -- A rule is a table {{pattern*}, type}. -- `pattern` is a non-normalized pattern. -- `type` can be "enable", "disable" or "only". local function get_rules(opts_stack) local rules = {} local used_macros = {} for _, opts in utils.ripairs(opts_stack) do for _, macro_info in ipairs(macros) do local option, pattern = macro_info[1], macro_info[2] if not used_macros[option] then if opts[option] ~= nil then table.insert(rules, {{pattern}, opts[option] and "enable" or "disable"}) used_macros[option] = true end end end if opts.ignore then table.insert(rules, {opts.ignore, "disable"}) end if opts.only then table.insert(rules, {opts.only, "only"}) end if opts.enable then table.insert(rules, {opts.enable, "enable"}) end end return rules end local function normalize_patterns(rules) local res = {} for i, rule in ipairs(rules) do res[i] = {{}, rule[2]} for j, pattern in ipairs(rule[1]) do res[i][1][j] = normalize_pattern(pattern) end end return res end local scalar_options = { unused_secondaries = true, self = true, module = false, allow_defined = false, allow_defined_top = false, max_cyclomatic_complexity = false } -- Returns normalized options. -- Normalized options have fields: -- std: normalized std table, see `luacheck.standards` module; -- unused_secondaries, self, module, allow_defined, allow_defined_top: booleans; -- max_line_length: number or false; -- rules: see get_rules. function options.normalize(opts_stack, stds) local res = {} stds = stds or builtin_standards res.std = get_final_std(opts_stack, stds) for option, default in pairs(scalar_options) do res[option] = get_scalar_opt(opts_stack, option, default) end local max_line_opts = get_max_line_opts(opts_stack) utils.update(res, max_line_opts) res.rules = normalize_patterns(get_rules(opts_stack)) return res end return options luacheck-0.25.0/src/luacheck/parser.lua000066400000000000000000000726201410451437000177300ustar00rootroot00000000000000local lexer = require "luacheck.lexer" local utils = require "luacheck.utils" local parser = {} -- A table with range info, or simply range, has `line`, `offset`, and `end_offset` fields. -- `line` is the line of the first character. -- Parser state table has range info for the current token, and all AST -- node tables have range info for themself, including parens around expressions -- that are otherwise not reflected in the AST structure. parser.SyntaxError = utils.class() function parser.SyntaxError:__init(msg, range, prev_range) self.msg = msg self.line = range.line self.offset = range.offset self.end_offset = range.end_offset if prev_range then self.prev_line = prev_range.line self.prev_offset = prev_range.offset self.prev_end_offset = prev_range.end_offset end end function parser.syntax_error(msg, range, prev_range) error(parser.SyntaxError(msg, range, prev_range), 0) end local function mark_line_endings(state, token_type) for line = state.line, state.lexer.line - 1 do state.line_endings[line] = token_type end end local function skip_token(state) while true do local token, token_value, line, offset, error_end_offset = lexer.next_token(state.lexer) state.token = token state.token_value = token_value state.line = line state.offset = offset state.end_offset = error_end_offset or (state.lexer.offset - 1) if not token then parser.syntax_error(token_value, state) end if token == "short_comment" then state.comments[#state.comments + 1] = { contents = token_value, line = line, offset = offset, end_offset = state.end_offset } state.line_endings[line] = "comment" elseif token == "long_comment" then mark_line_endings(state, "comment") else if token ~= "eof" then mark_line_endings(state, "string") state.code_lines[line] = true state.code_lines[state.lexer.line] = true end return end end end local function token_name(token) return token == "name" and "identifier" or (token == "eof" and "" or ("'" .. token .. "'")) end local function parse_error(state, msg, prev_range, token_prefix, message_suffix) local token_repr if state.token == "eof" then token_repr = "" else token_repr = lexer.get_quoted_substring_or_line(state.lexer, state.line, state.offset, state.end_offset) end if token_prefix then token_repr = token_prefix .. " " .. token_repr end msg = msg .. " near " .. token_repr if message_suffix then msg = msg .. " " .. message_suffix end parser.syntax_error(msg, state, prev_range) end local function check_token(state, token) if state.token ~= token then parse_error(state, "expected " .. token_name(token)) end end local function check_and_skip_token(state, token) check_token(state, token) skip_token(state) end local function test_and_skip_token(state, token) if state.token == token then skip_token(state) return true end end local function copy_range(range) return { line = range.line, offset = range.offset, end_offset = range.end_offset } end local new_state local parse_block local missing_closing_token_error -- Attempt to guess a better location for missing `end` and `until` errors (usually they uselessly point to eof). -- Guessed error token should be selected in such a way that inserting previously missing closing token -- in front of it should fix the error or at least move its opening token forward. -- The idea is to track the stack of opening tokens and their indentations. -- For the first statement or closing token with the same or smaller indentation than the opening token -- on the top of the stack: -- * If it has the same indentation but is not the appropriate closing token for the opening one, pick it -- as the guessed error location. -- * If it has a lower indentation level, pick it as the guessed error location even it closes the opening token. -- Example: -- local function f() -- -- -- if cond then <- `if` is the guessed opening token -- -- -- <- first token on this line is the guessed error location -- end -- Another one: -- local function g() -- -- -- if cond then <- `if` is the guessed opening token -- -- -- end <- `end` is the guessed error location local opening_token_to_closing = { ["("] = ")", ["["] = "]", ["{"] = "}", ["do"] = "end", ["if"] = "end", ["else"] = "end", ["elseif"] = "end", ["while"] = "end", ["repeat"] = "until", ["for"] = "end", ["function"] = "end" } local function get_indentation(state, line) local ws_start, ws_end = state.lexer.src:find("^[ \t\v\f]*", state.lexer.line_offsets[line]) return ws_end - ws_start end local UnpairedTokenGuesser = utils.class() function UnpairedTokenGuesser:__init(state, error_opening_range, error_closing_token) self.old_state = state self.error_offset = state.offset self.error_opening_range = error_opening_range self.error_closing_token = error_closing_token self.opening_tokens_stack = utils.Stack() end function UnpairedTokenGuesser:guess() -- Need to reinitialize lexer (e.g. to skip shebang again). self.state = new_state(self.old_state.lexer.src) self.state.unpaired_token_guesser = self skip_token(self.state) parse_block(self.state) error("No syntax error in second parse", 0) end function UnpairedTokenGuesser:on_block_start(opening_token_range, opening_token) local token_wrapper = copy_range(opening_token_range) token_wrapper.token = opening_token token_wrapper.closing_token = opening_token_to_closing[opening_token] token_wrapper.eligible = token_wrapper.closing_token == self.error_closing_token token_wrapper.indentation = get_indentation(self.state, opening_token_range.line) self.opening_tokens_stack:push(token_wrapper) end function UnpairedTokenGuesser:set_guessed() -- Keep the first detected location. if self.guessed then return end self.guessed = self.opening_tokens_stack.top self.guessed.error_token = self.state.token self.guessed.error_range = copy_range(self.state) end function UnpairedTokenGuesser:check_token() local top = self.opening_tokens_stack.top if top and top.eligible and self.state.line > top.line then local token_indentation = get_indentation(self.state, self.state.line) if token_indentation < top.indentation then self:set_guessed() elseif token_indentation == top.indentation then local token = self.state.token if token ~= top.closing_token and ((top.token ~= "if" and top.token ~= "elseif") or (token ~= "elseif" and token ~= "else")) then self:set_guessed() end end end if self.state.offset == self.error_offset then if self.guessed and self.guessed.error_range.offset ~= self.state.offset then self.state.line = self.guessed.error_range.line self.state.offset = self.guessed.error_range.offset self.state.end_offset = self.guessed.error_range.end_offset self.state.token = self.guessed.error_token missing_closing_token_error(self.state, self.guessed, self.guessed.token, self.guessed.closing_token, true) end end end function UnpairedTokenGuesser:on_block_end() self:check_token() self.opening_tokens_stack:pop() if not self.opening_tokens_stack.top then -- Inserting an end token into a balanced sequence of tokens adds an error earlier than original one. self.guessed = nil end end function UnpairedTokenGuesser:on_statement() self:check_token() end function missing_closing_token_error(state, opening_range, opening_token, closing_token, is_guess) local msg = "expected " .. token_name(closing_token) if opening_range and opening_range.line ~= state.line then msg = msg .. " (to close " .. token_name(opening_token) .. " on line " .. tostring(opening_range.line) .. ")" end local token_prefix local message_suffix if is_guess then if state.token == closing_token then -- "expected 'end' near 'end'" seems confusing. token_prefix = "less indented" end message_suffix = "(indentation-based guess)" end parse_error(state, msg, opening_range, token_prefix, message_suffix) end local function check_closing_token(state, opening_range, opening_token) local closing_token = opening_token_to_closing[opening_token] or "eof" if state.token == closing_token then return end if (opening_token == "if" or opening_token == "elseif") and (state.token == "else" or state.token == "elseif") then return end if closing_token == "end" or closing_token == "until" then if not state.unpaired_token_guesser then UnpairedTokenGuesser(state, opening_range, closing_token):guess() end end missing_closing_token_error(state, opening_range, opening_token, closing_token) end local function check_and_skip_closing_token(state, opening_range, opening_token) check_closing_token(state, opening_range, opening_token) skip_token(state) end local function check_name(state) check_token(state, "name") return state.token_value end local function new_outer_node(range, tag, node) node = node or {} node.line = range.line node.offset = range.offset node.end_offset = range.end_offset node.tag = tag return node end local function new_inner_node(start_range, end_range, tag, node) node = node or {} node.line = start_range.line node.offset = start_range.offset node.end_offset = end_range.end_offset node.tag = tag return node end local parse_expression local function parse_expression_list(state, list) list = list or {} repeat list[#list + 1] = parse_expression(state) until not test_and_skip_token(state, ",") return list end local function parse_id(state, tag) local ast_node = new_outer_node(state, tag or "Id") ast_node[1] = check_name(state) -- Skip name. skip_token(state) return ast_node end local function atom(tag) return function(state) local ast_node = new_outer_node(state, tag) ast_node[1] = state.token_value skip_token(state) return ast_node end end local simple_expressions = {} simple_expressions.number = atom("Number") simple_expressions.string = atom("String") simple_expressions["nil"] = atom("Nil") simple_expressions["true"] = atom("True") simple_expressions["false"] = atom("False") simple_expressions["..."] = atom("Dots") simple_expressions["{"] = function(state) local start_range = copy_range(state) local ast_node = {} skip_token(state) repeat if state.token == "}" then break end local key_node, value_node local first_token_range = copy_range(state) if state.token == "name" then local name = state.token_value skip_token(state) -- Skip name. if test_and_skip_token(state, "=") then -- `name` = `expr`. key_node = new_outer_node(first_token_range, "String", {name}) value_node = parse_expression(state) else -- `name` is beginning of an expression in array part. -- Backtrack lexer to before name. state.lexer.line = first_token_range.line state.lexer.offset = first_token_range.offset skip_token(state) -- Load name again. value_node = parse_expression(state) end elseif state.token == "[" then -- [ `expr` ] = `expr`. skip_token(state) key_node = parse_expression(state) check_and_skip_closing_token(state, first_token_range, "[") check_and_skip_token(state, "=") value_node = parse_expression(state) else -- Expression in array part. value_node = parse_expression(state) end if key_node then -- Pair. ast_node[#ast_node + 1] = new_inner_node(first_token_range, value_node, "Pair", {key_node, value_node}) else -- Array part item. ast_node[#ast_node + 1] = value_node end until not (test_and_skip_token(state, ",") or test_and_skip_token(state, ";")) new_inner_node(start_range, state, "Table", ast_node) check_and_skip_closing_token(state, start_range, "{") return ast_node end -- Parses argument list and the statements. local function parse_function(state, function_range) local paren_range = copy_range(state) check_and_skip_token(state, "(") local args = {} -- Are there arguments? if state.token ~= ")" then repeat if state.token == "name" then args[#args + 1] = parse_id(state) elseif state.token == "..." then args[#args + 1] = simple_expressions["..."](state) break else parse_error(state, "expected argument") end until not test_and_skip_token(state, ",") end check_and_skip_closing_token(state, paren_range, "(") local body = parse_block(state, function_range, "function") local end_range = copy_range(state) -- Skip "function". skip_token(state) return new_inner_node(function_range, end_range, "Function", {args, body, end_range = end_range}) end simple_expressions["function"] = function(state) local function_range = copy_range(state) -- Skip "function". skip_token(state) return parse_function(state, function_range) end -- A call handler parses arguments of a call with given base node that determines resulting node start location, -- given tag, and array to which the arguments should be appended. local call_handlers = {} call_handlers["("] = function(state, base_node, tag, node) local paren_range = copy_range(state) -- Skip "(". skip_token(state) if state.token ~= ")" then parse_expression_list(state, node) end new_inner_node(base_node, state, tag, node) check_and_skip_closing_token(state, paren_range, "(") return node end call_handlers["{"] = function(state, base_node, tag, node) local arg_node = simple_expressions[state.token](state) node[#node + 1] = arg_node return new_inner_node(base_node, arg_node, tag, node) end call_handlers.string = call_handlers["{"] local suffix_handlers = {} suffix_handlers["."] = function(state, base_node) -- Skip ".". skip_token(state) local index_node = parse_id(state, "String") return new_inner_node(base_node, index_node, "Index", {base_node, index_node}) end suffix_handlers["["] = function(state, base_node) local bracket_range = copy_range(state) -- Skip "[". skip_token(state) local index_node = parse_expression(state) local ast_node = new_inner_node(base_node, state, "Index", {base_node, index_node}) check_and_skip_closing_token(state, bracket_range, "[") return ast_node end suffix_handlers[":"] = function(state, base_node) -- Skip ":". skip_token(state) local method_name = parse_id(state, "String") local call_handler = call_handlers[state.token] if not call_handler then parse_error(state, "expected method arguments") end return call_handler(state, base_node, "Invoke", {base_node, method_name}) end suffix_handlers["("] = function(state, base_node) return call_handlers[state.token](state, base_node, "Call", {base_node}) end suffix_handlers["{"] = suffix_handlers["("] suffix_handlers.string = suffix_handlers["("] local function parse_simple_expression(state, kind, no_literals) local expression if state.token == "(" then local paren_range = copy_range(state) skip_token(state) local inner_expression = parse_expression(state) expression = new_inner_node(paren_range, state, "Paren", {inner_expression}) check_and_skip_closing_token(state, paren_range, "(") elseif state.token == "name" then expression = parse_id(state) else local literal_handler = simple_expressions[state.token] if not literal_handler or no_literals then parse_error(state, "expected " .. (kind or "expression")) end return literal_handler(state) end while true do local suffix_handler = suffix_handlers[state.token] if suffix_handler then expression = suffix_handler(state, expression) else return expression end end end local unary_operators = { ["not"] = "not", ["-"] = "unm", ["~"] = "bnot", ["#"] = "len" } local unary_priority = 12 local binary_operators = { ["+"] = "add", ["-"] = "sub", ["*"] = "mul", ["%"] = "mod", ["^"] = "pow", ["/"] = "div", ["//"] = "idiv", ["&"] = "band", ["|"] = "bor", ["~"] = "bxor", ["<<"] = "shl", [">>"] = "shr", [".."] = "concat", ["~="] = "ne", ["=="] = "eq", ["<"] = "lt", ["<="] = "le", [">"] = "gt", [">="] = "ge", ["and"] = "and", ["or"] = "or" } local left_priorities = { add = 10, sub = 10, mul = 11, mod = 11, pow = 14, div = 11, idiv = 11, band = 6, bor = 4, bxor = 5, shl = 7, shr = 7, concat = 9, ne = 3, eq = 3, lt = 3, le = 3, gt = 3, ge = 3, ["and"] = 2, ["or"] = 1 } local right_priorities = { add = 10, sub = 10, mul = 11, mod = 11, pow = 13, div = 11, idiv = 11, band = 6, bor = 4, bxor = 5, shl = 7, shr = 7, concat = 8, ne = 3, eq = 3, lt = 3, le = 3, gt = 3, ge = 3, ["and"] = 2, ["or"] = 1 } local function parse_subexpression(state, limit, kind) local expression local unary_operator = unary_operators[state.token] if unary_operator then local operator_range = copy_range(state) -- Skip operator. skip_token(state) local operand = parse_subexpression(state, unary_priority) expression = new_inner_node(operator_range, operand, "Op", {unary_operator, operand}) else expression = parse_simple_expression(state, kind) end -- Expand while operators have priorities higher than `limit`. while true do local binary_operator = binary_operators[state.token] if not binary_operator or left_priorities[binary_operator] <= limit then break end -- Skip operator. skip_token(state) -- Read subexpression with higher priority. local subexpression = parse_subexpression(state, right_priorities[binary_operator]) expression = new_inner_node(expression, subexpression, "Op", {binary_operator, expression, subexpression}) end return expression end function parse_expression(state, kind) return parse_subexpression(state, 0, kind) end local statements = {} statements["if"] = function(state) local start_range = copy_range(state) -- Skip "if". skip_token(state) local ast_node = {} -- The loop is entered after skipping "if" or "elseif". -- Block start token info is set to the last skipped "if", "elseif", or "else" token. local block_start_token = "if" local block_start_range = start_range while true do ast_node[#ast_node + 1] = parse_expression(state, "condition") -- Add range of the "then" token to the block statement array. local branch_range = copy_range(state) check_and_skip_token(state, "then") ast_node[#ast_node + 1] = parse_block(state, block_start_range, block_start_token, branch_range) if state.token == "else" then branch_range = copy_range(state) block_start_token = "else" block_start_range = branch_range skip_token(state) ast_node[#ast_node + 1] = parse_block(state, block_start_range, block_start_token, branch_range) break elseif state.token == "elseif" then block_start_token = "elseif" block_start_range = copy_range(state) skip_token(state) else break end end new_inner_node(start_range, state, "If", ast_node) -- Skip "end". skip_token(state) return ast_node end statements["while"] = function(state) local start_range = copy_range(state) -- Skip "while". skip_token(state) local condition = parse_expression(state, "condition") check_and_skip_token(state, "do") local block = parse_block(state, start_range, "while") local ast_node = new_inner_node(start_range, state, "While", {condition, block}) -- Skip "end". skip_token(state) return ast_node end statements["do"] = function(state) local start_range = copy_range(state) -- Skip "do". skip_token(state) local block = parse_block(state, start_range, "do") local ast_node = new_inner_node(start_range, state, "Do", block) -- Skip "end". skip_token(state) return ast_node end statements["for"] = function(state) local start_range = copy_range(state) -- Skip "for". skip_token(state) local ast_node = {} local tag local first_var = parse_id(state) if state.token == "=" then -- Numeric "for" loop. tag = "Fornum" -- Skip "=". skip_token(state) ast_node[1] = first_var ast_node[2] = parse_expression(state) check_and_skip_token(state, ",") ast_node[3] = parse_expression(state) if test_and_skip_token(state, ",") then ast_node[4] = parse_expression(state) end check_and_skip_token(state, "do") ast_node[#ast_node + 1] = parse_block(state, start_range, "for") elseif state.token == "," or state.token == "in" then -- Generic "for" loop. tag = "Forin" local iter_vars = {first_var} while test_and_skip_token(state, ",") do iter_vars[#iter_vars + 1] = parse_id(state) end ast_node[1] = iter_vars check_and_skip_token(state, "in") ast_node[2] = parse_expression_list(state) check_and_skip_token(state, "do") ast_node[3] = parse_block(state, start_range, "for") else parse_error(state, "expected '=', ',' or 'in'") end new_inner_node(start_range, state, tag, ast_node) -- Skip "end". skip_token(state) return ast_node end statements["repeat"] = function(state) local start_range = copy_range(state) -- Skip "repeat". skip_token(state) local block = parse_block(state, start_range, "repeat") -- Skip "until". skip_token(state) local condition = parse_expression(state, "condition") return new_inner_node(start_range, condition, "Repeat", {block, condition}) end statements["function"] = function(state) local start_range = copy_range(state) -- Skip "function". skip_token(state) local lhs = parse_id(state) local implicit_self_range while (not implicit_self_range) and (state.token == "." or state.token == ":") do implicit_self_range = (state.token == ":") and copy_range(state) -- Skip "." or ":". skip_token(state) local index_node = parse_id(state, "String") lhs = new_inner_node(lhs, index_node, "Index", {lhs, index_node}) end local function_node = parse_function(state, start_range) if implicit_self_range then -- Insert implicit "self" argument. local self_arg = new_outer_node(implicit_self_range, "Id", {"self", implicit = true}) table.insert(function_node[1], 1, self_arg) end return new_inner_node(start_range, function_node, "Set", {{lhs}, {function_node}}) end statements["local"] = function(state) local start_range = copy_range(state) -- Skip "local". skip_token(state) if state.token == "function" then -- Local function. local function_range = copy_range(state) -- Skip "function". skip_token(state) local var = parse_id(state) local function_node = parse_function(state, function_range) return new_inner_node(start_range, function_node, "Localrec", {{var}, {function_node}}) end -- Local definition, potentially with assignment. local lhs = {} local rhs repeat lhs[#lhs + 1] = parse_id(state) -- Check if a Lua 5.4 attribute is present if state.token == "<" then -- For now, just consume and ignore it. skip_token(state) check_name(state) skip_token(state) check_and_skip_token(state, ">") end until not test_and_skip_token(state, ",") if test_and_skip_token(state, "=") then rhs = parse_expression_list(state) end return new_inner_node(start_range, rhs and rhs[#rhs] or lhs[#lhs], "Local", {lhs, rhs}) end statements["::"] = function(state) local start_range = copy_range(state) -- Skip "::". skip_token(state) local name = check_name(state) -- Skip label name. skip_token(state) local ast_node = new_inner_node(start_range, state, "Label", {name}) check_and_skip_token(state, "::") return ast_node end local closing_tokens = utils.array_to_set({"end", "eof", "else", "elseif", "until"}) statements["return"] = function(state) local start_range = copy_range(state) -- Skip "return". skip_token(state) if closing_tokens[state.token] or state.token == ";" then -- No return values. return new_outer_node(start_range, "Return") else local returns = parse_expression_list(state) return new_inner_node(start_range, returns[#returns], "Return", returns) end end statements["break"] = function(state) local ast_node = new_outer_node(state, "Break") -- Skip "break". skip_token(state) return ast_node end statements["goto"] = function(state) local start_range = copy_range(state) -- Skip "goto". skip_token(state) local name = check_name(state) local ast_node = new_outer_node(start_range, "Goto", {name}) -- Skip label name. skip_token(state) return ast_node end local function parse_expression_statement(state) local lhs local start_range = copy_range(state) -- Handle lhs of an assignment or a single expression. repeat local item_start_range = lhs and copy_range(state) or start_range local expected = lhs and "identifier or field" or "statement" local primary_expression = parse_simple_expression(state, expected, true) if primary_expression.tag == "Paren" then -- (expr) in lhs is invalid. parser.syntax_error("expected " .. expected .. " near '('", item_start_range) end if primary_expression.tag == "Call" or primary_expression.tag == "Invoke" then if lhs then -- The is an assingment, and a call is not valid in lhs. parse_error(state, "expected call or indexing") else -- This is a call. return primary_expression end end -- This is an assignment. lhs = lhs or {} lhs[#lhs + 1] = primary_expression until not test_and_skip_token(state, ",") check_and_skip_token(state, "=") local rhs = parse_expression_list(state) return new_inner_node(start_range, rhs[#rhs], "Set", {lhs, rhs}) end local function parse_statement(state) return (statements[state.token] or parse_expression_statement)(state) end function parse_block(state, opening_token_range, opening_token, block) local unpaired_token_guesser = state.unpaired_token_guesser if unpaired_token_guesser and opening_token then unpaired_token_guesser:on_block_start(opening_token_range, opening_token) end block = block or {} local after_statement = false while not closing_tokens[state.token] do local first_token = state.token if first_token == ";" then if not after_statement then table.insert(state.hanging_semicolons, copy_range(state)) end -- Skip ";". skip_token(state) -- Further semicolons are considered hanging. after_statement = false else if unpaired_token_guesser then unpaired_token_guesser:on_statement() end local statement = parse_statement(state) after_statement = true block[#block + 1] = statement if statement.tag == "Return" then -- "return" must be the last statement. -- However, one ";" after it is allowed. test_and_skip_token(state, ";") break end end end if unpaired_token_guesser and opening_token then unpaired_token_guesser:on_block_end() end check_closing_token(state, opening_token_range, opening_token) return block end function new_state(src, line_offsets, line_lengths) return { lexer = lexer.new_state(src, line_offsets, line_lengths), -- Set of line numbers containing code. code_lines = {}, -- Maps line numbers to "comment", "string", or nil based on whether the line ending is within a token line_endings = {}, -- Array of {contents = string} with range info. comments = {}, -- Array of ranges of semicolons not following a statement. hanging_semicolons = {} } end -- Parses source characters. -- Returns AST (in almost MetaLua format), array of comments - tables {contents = string} with range info, -- set of line numbers containing code, map of types of tokens wrapping line endings (nil, "string", or "comment"), -- array of ranges of hanging semicolons (not after statements), array of line start offsets, array of line lengths. -- The last two tables can be passed as arguments to be filled. -- On error throws an instance of parser.SyntaxError: table {msg = msg, prev_range = prev_range?} with range info, -- prev_range may refer to some extra relevant location. function parser.parse(src, line_offsets, line_lengths) local state = new_state(src, line_offsets, line_lengths) skip_token(state) local ast = parse_block(state) return ast, state.comments, state.code_lines, state.line_endings, state.hanging_semicolons, state.lexer.line_offsets, state.lexer.line_lengths end return parser luacheck-0.25.0/src/luacheck/profiler.lua000066400000000000000000000075501410451437000202560ustar00rootroot00000000000000-- Require luasocket only when needed. local socket local profiler = {} local metrics = { {name = "Wall", get = function() return socket.gettime() end}, {name = "CPU", get = os.clock}, {name = "Memory", get = function() return collectgarbage("count") end} } local functions = { {name = "sha1", module = "vendor.sha1"}, {name = "load", module = "cache"}, {name = "update", module = "cache"}, {name = "decode", module = "decoder"}, {name = "parse", module = "parser"}, {name = "dump_check_result", module = "serializer"}, {name = "load_check_result", module = "serializer"}, {name = "run", module = "stages.unwrap_parens"}, {name = "run", module = "stages.parse_inline_options"}, {name = "run", module = "stages.linearize"}, {name = "run", module = "stages.name_functions"}, {name = "run", module = "stages.resolve_locals"}, {name = "run", module = "stages.detect_bad_whitespace"}, {name = "run", module = "stages.detect_cyclomatic_complexity"}, {name = "run", module = "stages.detect_empty_blocks"}, {name = "run", module = "stages.detect_empty_statements"}, {name = "run", module = "stages.detect_globals"}, {name = "run", module = "stages.detect_reversed_fornum_loops"}, {name = "run", module = "stages.detect_unbalanced_assignments"}, {name = "run", module = "stages.detect_uninit_accesses"}, {name = "run", module = "stages.detect_unreachable_code"}, {name = "run", module = "stages.detect_unused_fields"}, {name = "run", module = "stages.detect_unused_locals"}, {name = "filter", module = "filter"}, {name = "normalize", module = "options"} } local stats = {} local start_values = {} local function start_phase(name) for _, metric in ipairs(metrics) do start_values[metric][name] = metric.get() end end local function stop_phase(name) for _, metric in ipairs(metrics) do local increment = metric.get() - start_values[metric][name] stats[metric][name] = (stats[metric][name] or 0) + increment end end local phase_stack = {} local function push_phase(name) local prev_name = phase_stack[#phase_stack] if prev_name then stop_phase(prev_name) end table.insert(phase_stack, name) start_phase(name) end local function pop_phase(name) assert(name == table.remove(phase_stack)) stop_phase(name) local prev_name = phase_stack[#phase_stack] if prev_name then start_phase(prev_name) end end local function continue_wrapper(name, ...) pop_phase(name) return ... end local function wrap(fn, name) return function(...) push_phase(name) return continue_wrapper(name, fn(...)) end end local function patch(fn) local mod = require("luacheck." .. fn.module) local orig = mod[fn.name] local new = wrap(orig, fn.module .. "." .. fn.name) mod[fn.name] = new end function profiler.init() socket = require "socket" collectgarbage("stop") for _, metric in ipairs(metrics) do stats[metric] = {} start_values[metric] = {} end for _, fn in ipairs(functions) do patch(fn) end push_phase("other") end function profiler.report() pop_phase("other") for _, metric in ipairs(metrics) do local names = {} local total = 0 for name, value in pairs(stats[metric]) do table.insert(names, name) total = total + value end table.sort(names, function(name1, name2) local stats1 = stats[metric][name1] local stats2 = stats[metric][name2] if stats1 ~= stats2 then return stats1 > stats2 else return name1 < name2 end end) print(metric.name) print() for _, name in ipairs(names) do print(("%s - %f (%f%%)"):format(name, stats[metric][name], stats[metric][name] / total * 100)) end print(("Total - %f"):format(total)) print() end end return profiler luacheck-0.25.0/src/luacheck/runner.lua000066400000000000000000000330601410451437000177400ustar00rootroot00000000000000local cache = require "luacheck.cache" local config = require "luacheck.config" local expand_rockspec = require "luacheck.expand_rockspec" local format = require "luacheck.format" local fs = require "luacheck.fs" local globbing = require "luacheck.globbing" local luacheck = require "luacheck" local multithreading = require "luacheck.multithreading" local options = require "luacheck.options" local utils = require "luacheck.utils" local runner = {} local Runner = utils.class() function Runner:__init(config_stack) self._config_stack = config_stack end local config_options = { config = utils.has_type_or_false("string"), default_config = utils.has_type_or_false("string") } function runner.new(opts) local ok, err = options.validate(config_options, opts) if not ok then error(("bad argument #1 to 'runner.new' (%s)"):format(err)) end local base_config, config_err = config.load_config(opts.config, opts.default_config) if not base_config then return nil, config_err end local override_config = config.table_to_config(opts) local config_stack config_stack, err = config.stack_configs({base_config, override_config}) if not config_stack then return nil, err end return Runner(config_stack) end local function validate_inputs(inputs) if type(inputs) ~= "table" then return nil, ("inputs table expected, got %s"):format(inputs) end for index, input in ipairs(inputs) do local context = ("invalid input table at index [%d]"):format(index) if type(input) ~= "table" then return nil, ("%s: table expected, got %s"):format(context, type(input)) end local specifies_source for _, field in ipairs({"file", "filename", "path", "rockspec_path", "string"}) do if input[field] ~= nil then if field == "file" then if io.type(input[field]) ~= "file" then return nil, ("%s: invalid field 'file': open file expected, got %s"):format( context, type(input[field])) end elseif type(input[field]) ~= "string" then return nil, ("%s: invalid field '%s': string expected, got %s"):format( context, field, type(input[field])) end if field ~= "filename" then specifies_source = true end end end if not specifies_source then return nil, ("%s: one of fields 'path', 'rockspec_path', 'file', or 'string' must be present"):format(context) end end return true end local function matches_any(globs, filename) for _, glob in ipairs(globs) do if globbing.match(glob, filename) then return true end end return false end function Runner:_is_filename_included(abs_filename) return not matches_any(self._top_opts.exclude_files, abs_filename) and ( #self._top_opts.include_files == 0 or matches_any(self._top_opts.include_files, abs_filename)) end -- Normalizes inputs and filters inputs using `exclude_files` and `include_files` options. -- Returns an array of prepared input tables. -- Differences between normal and prepated inputs: -- * Prepared inputs can't have `rockspec_path` field. -- * Prepared inputs can't have `path` pointing to a directory (unless it has an error). -- * Prepared inputs have `filename` field if possible (copied from `path` if not given). -- * Prepared inputs that have `path` field also have `abs_path` field. -- * Prepared inputs can have `fatal` field if the input can't be checked. The value is error type as a string. -- `fatal` is always accompanied by an error message in `msg` field. function Runner:_prepare_inputs(inputs) local current_dir = fs.get_current_dir() local dir_pattern = #self._top_opts.include_files > 0 and "" or "%.lua$" local res = {} local function add(input) if input.path then -- TODO: get rid of this, adjust fs.extract_files to avoid leading `./` instead. input.path = input.path:gsub("^%.[/\\]([^/])", "%1") input.abs_path = fs.normalize(fs.join(current_dir, input.path)) end local abs_filename if input.filename then abs_filename = fs.normalize(fs.join(current_dir, input.filename)) else input.filename = input.path abs_filename = input.abs_path end if not input.filename or self:_is_filename_included(abs_filename) then table.insert(res, input) end end for _, input in ipairs(inputs) do if input.path then if fs.is_dir(input.path) then local filenames, err_map = fs.extract_files(input.path, dir_pattern) for _, filename in ipairs(filenames) do local err = err_map[filename] if err then add({path = filename, fatal = "I/O", msg = err, filename = input.filename}) else add({path = filename, filename = input.filename}) end end else add({path = input.path, filename = input.filename}) end elseif input.rockspec_path then local filenames, fatal, err = expand_rockspec(input.rockspec_path) if filenames then for _, filename in ipairs(filenames) do add({path = filename, filename = input.filename}) end else add({path = input.rockspec_path, fatal = fatal, msg = err, filename = input.filename}) end elseif input.file then add({file = input.file, filename = input.filename}) elseif input.string then add({string = input.string, filename = input.filename}) else -- Validation should ensure this never happens. error("input doesn't specify source to check") end end return res end -- Loads cached reports for inputs with `path` field, assigns them to `cached_report` field. -- For each file on cache load error sets its `fatal` and `msg` fields. function Runner:_add_cached_reports(inputs) for _, input in ipairs(inputs) do if not input.fatal and input.path then local report, err = self._cache:get(input.path) if err then input.fatal = "I/O" input.msg = ("Couldn't load cache for %s from %s: malformed data"):format( self._top_opts.cache, input.path) else input.cached_report = report end end end end -- Adds report as `new_report` field to all inputs that don't have a fatal error or a cached report. -- Adds `fatal` and `msg` instead if there was an I/O error. function Runner:_add_new_reports(inputs) local sources = {} local original_indexes = {} for index, input in ipairs(inputs) do if not input.fatal and not input.cached_report then if input.string then table.insert(sources, input.string) table.insert(original_indexes, index) else local source, err = utils.read_file(input.path or input.file) if source then table.insert(sources, source) table.insert(original_indexes, index) else input.fatal = "I/O" input.msg = err end end end end local map = multithreading.has_lanes and multithreading.pmap or utils.map local reports = map(luacheck.get_report, sources, self._top_opts.jobs) for index, report in ipairs(reports) do inputs[original_indexes[index]].new_report = report end end -- Saves `new_report` for files eligible for caching to cache. -- Returns true on success or nil and an error message on failure. function Runner:_save_new_reports_to_cache(inputs) for _, input in ipairs(inputs) do if input.new_report and input.path then local ok = self._cache:put(input.path, input.new_report) if not ok then return nil, ("Couldn't save cache for %s from %s: I/O error"):format(input.path, self._top_opts.cache) end end end return true end -- Inputs are prepared here, see `Runner:_prepare_inputs`. -- Returns an array of reports, one per input, possibly annotated with fields `fatal`, `msg`, and `filename`. -- On critical error returns nil and an error message. function Runner:_get_reports(inputs) if self._top_opts.cache then local err self._cache, err = cache.new(self._top_opts.cache) if not self._cache then return nil, err end self:_add_cached_reports(inputs) end self:_add_new_reports(inputs) if self._top_opts.cache then local ok, err = self:_save_new_reports_to_cache(inputs) if not ok then return nil, err end end local res = {} for _, input in ipairs(inputs) do local report = input.cached_report or input.new_report if not report then report = {fatal = input.fatal, msg = input.msg} end report.filename = input.filename table.insert(res, report) end return res end function Runner:_get_final_report(reports) local processing_options = {} for index, report in ipairs(reports) do if not report.fatal then processing_options[index] = self._config_stack:get_options(report.filename) end end local final_report = luacheck.process_reports(reports, processing_options, self._config_stack:get_stds()) -- `luacheck.process_reports` doesn't preserve `filename` fields, re-add them. -- TODO: make it preserve them? for index, report in ipairs(reports) do final_report[index].filename = report.filename end return final_report end -- Inputs is an array of tables, each one specifies an input. -- Each input table must have one of the following fields: -- * `path`: string pointing to a file or directory to check. Checking directories requires LuaFileSystem, -- and recursively checks all files within the directory. If `include_files` option is not used, -- only files with `.lua` extensions within the directory are considered. -- * `rockspec_path`: string pointing to a rockspec, all files with `.lua` extension within its `build.modules`, -- `build.install.lua`, and `build.install.bin` tables are checked. -- * `file`: an open file object. It is read till EOF and closed, contents are checked. -- * `string`: Lua code to check as a string. -- Additionally, each input table can have `filename` field: a string used when applying `exclude_files` -- and `include_files` options to the input, and also when figuring out which per-path option overrides to use. -- By default, if `path` field is given, it is also used as `filename`, otherwise the input is considered unnamed. -- Unnamed files always pass `exclude_files` and `include_files` filters and don't have any per-path options applied. function Runner:check(inputs) local ok, err = validate_inputs(inputs) if not ok then error(("bad argument #1 to 'Runner:check' (%s)"):format(err)) end -- Path-related top options can depend on current directory. -- Assume it can't somehow change during `:check` call. self._top_opts = self._config_stack:get_top_options() local prepared_inputs = self:_prepare_inputs(inputs) local reports, reports_err = self:_get_reports(prepared_inputs) if not reports then return nil, reports_err end return self:_get_final_report(reports) end -- Formats given report (same format as returned by `Runner:check`). -- Optionally a table of options can be passed as `format_opts`, -- it can contain options `formatter`. `quiet`, `color`, `codes`, and `ranges`, -- with priority over options from initialization and config. -- Returns formatted report as a string. It always has a newline at the end unless it is empty. -- On error returns nil and an error message. function Runner:format(report, format_opts) if type(report) ~= "table" then error(("bad argument #1 to 'Runner:format' (report table expected, got %s"):format(type(report))) end local is_valid, err = options.validate(config.format_options, format_opts) if not is_valid then error(("bad argument #2 to 'Runner:format' (%s)"):format(err)) end local top_opts = self._config_stack:get_top_options() format_opts = format_opts or {} local combined_opts = {} for _, option in ipairs({"formatter", "quiet", "color", "codes", "ranges"}) do combined_opts[option] = top_opts[option] if format_opts[option] ~= nil then combined_opts[option] = format_opts[option] end end local filenames = {} for _, file_report in ipairs(report) do table.insert(filenames, file_report.filename or "") end local output if format.builtin_formatters[combined_opts.formatter] then output = format.format(report, filenames, combined_opts) else local formatter_func = combined_opts.formatter if type(combined_opts.formatter) == "string" then local require_ok local formatter_anchor_dir if not format_opts.formatter then formatter_anchor_dir = top_opts.formatter_anchor_dir end require_ok, formatter_func = config.relative_require(formatter_anchor_dir, combined_opts.formatter) if not require_ok then return nil, ("Couldn't load custom formatter '%s': %s"):format(combined_opts.formatter, formatter_func) end end local ok ok, output = pcall(formatter_func, report, filenames, combined_opts) if not ok then return nil, ("Couldn't run custom formatter '%s': %s"):format(tostring(combined_opts.formatter), output) end end if #output > 0 and output:sub(-1) ~= "\n" then output = output .. "\n" end return output end return runner luacheck-0.25.0/src/luacheck/serializer.lua000066400000000000000000000130171410451437000206000ustar00rootroot00000000000000local parse_inline_options = require "luacheck.stages.parse_inline_options" local stages = require "luacheck.stages" local utils = require "luacheck.utils" local serializer = {} local option_fields = { "ignore", "std", "globals", "unused_args", "self", "compat", "global", "unused", "redefined", "unused_secondaries", "allow_defined", "allow_defined_top", "module", "read_globals", "new_globals", "new_read_globals", "enable", "only", "not_globals", "max_line_length", "max_code_line_length", "max_string_line_length", "max_comment_line_length", "max_cyclomatic_complexity" } local function compress_table(t, fields) local res = {} for index, field in ipairs(fields) do local value = t[field] if value ~= nil then if field == "options" then value = compress_table(value, option_fields) end res[index] = value end end return res end local function compress_tables(tables, per_code_fields) local res = {} for _, t in ipairs(tables) do local fields = per_code_fields and stages.warnings[t.code].fields or parse_inline_options.inline_option_fields table.insert(res, compress_table(t, fields)) end return res end local function compress_result(result) local res = {} res[1] = compress_tables(result.warnings, true) res[2] = compress_tables(result.inline_options) res[3] = result.line_lengths res[4] = result.line_endings return res end local function decompress_table(t, fields) local res = {} for index, field in ipairs(fields) do local value = t[index] if value ~= nil then if field == "options" then value = decompress_table(value, option_fields) end res[field] = value end end return res end local function decompress_tables(tables, per_code_fields) local res = {} for _, t in ipairs(tables) do local fields if per_code_fields then fields = stages.warnings[t[1]].fields else fields = parse_inline_options.inline_option_fields end table.insert(res, decompress_table(t, fields)) end return res end local function decompress_result(compressed) local result = {} result.warnings = decompress_tables(compressed[1], true) result.inline_options = decompress_tables(compressed[2]) result.line_lengths = compressed[3] result.line_endings = compressed[4] return result end local function get_local_name(index) return string.char(index + (index > 26 and 70 or 64)) end local function max_n(t) local res = 0 for k in pairs(t) do res = math.max(res, k) end return res end -- Serializes a value into buffer. -- `strings` is a table mapping string values to where they first occured or to name of local -- variable used to represent it. -- Array part contains representations of values saved into locals. local function add_value(buffer, strings, value, level) if type(value) == "string" then local prev = strings[value] if type(prev) == "string" then -- There is a local with such value. table.insert(buffer, prev) elseif type(prev) == "number" and #strings < 52 then -- Value is used second time, put it into a local. table.insert(strings, ("%q"):format(value)) local local_name = get_local_name(#strings) buffer[prev] = local_name table.insert(buffer, local_name) strings[value] = local_name else table.insert(buffer, ("%q"):format(value)) strings[value] = #buffer end elseif type(value) == "table" then -- Level 1 has the result, level 2 has warning/inline option/line info arrays, -- level 3 has warnings/inline option containers, level 4 has inline options. local allow_sparse = level ~= 3 local nil_tail_start local is_sparse local put_one table.insert(buffer, "{") for index = 1, max_n(value) do local item = value[index] if item == nil then is_sparse = allow_sparse nil_tail_start = nil_tail_start or index else if put_one then table.insert(buffer, ",") end if is_sparse then table.insert(buffer, ("[%d]="):format(index)) elseif nil_tail_start then for _ = nil_tail_start, index - 1 do table.insert(buffer, "nil,") end nil_tail_start = nil end add_value(buffer, strings, item, level + 1) put_one = true end end table.insert(buffer, "}") else table.insert(buffer, tostring(value)) end end -- Serializes check result, returns a string. function serializer.dump_check_result(result) local strings = {} local buffer = {"", "return "} add_value(buffer, strings, compress_result(result), 1) if strings[1] then local names = {} for index in ipairs(strings) do table.insert(names, get_local_name(index)) end buffer[1] = "local " .. table.concat(names, ",") .. "=" .. table.concat(strings, ",") .. ";" end return table.concat(buffer) end -- Loads check result from a string, returns check result table or nothing on error. function serializer.load_check_result(dumped) local func = utils.load(dumped, {}) if not func then return end local ok, compressed_result = pcall(func) if not ok or type(compressed_result) ~= "table" then return end return decompress_result(compressed_result) end return serializer luacheck-0.25.0/src/luacheck/stages/000077500000000000000000000000001410451437000172105ustar00rootroot00000000000000luacheck-0.25.0/src/luacheck/stages/detect_bad_whitespace.lua000066400000000000000000000063431410451437000242130ustar00rootroot00000000000000local stage = {} stage.warnings = { ["611"] = {message_format = "line contains only whitespace", fields = {}}, ["612"] = {message_format = "line contains trailing whitespace", fields = {}}, ["613"] = {message_format = "trailing whitespace in a string", fields = {}}, ["614"] = {message_format = "trailing whitespace in a comment", fields = {}}, ["621"] = {message_format = "inconsistent indentation (SPACE followed by TAB)", fields = {}} } function stage.run(chstate) local num_lines = #chstate.line_offsets for line_number = 1, num_lines do local line_offset = chstate.line_offsets[line_number] local line_length = chstate.line_lengths[line_number] if line_length > 0 then local trailing_ws_pattern if line_number == num_lines then trailing_ws_pattern = "^[^\r\n]-()[ \t\f\v]+()[\r\n]?$" else trailing_ws_pattern = "^[^\r\n]-()[ \t\f\v]+()[\r\n]" end local line_start_byte, _, trailing_ws_start_byte, line_end_byte = chstate.source:find( trailing_ws_pattern, line_offset) local trailing_ws_code if trailing_ws_start_byte then if trailing_ws_start_byte == line_start_byte then -- Line contains only whitespace (thus never considered "code"). trailing_ws_code = "611" elseif not chstate.line_endings[line_number] then -- Trailing whitespace on code line or after long comment. trailing_ws_code = "612" elseif chstate.line_endings[line_number] == "string" then -- Trailing whitespace embedded in a string literal. trailing_ws_code = "613" elseif chstate.line_endings[line_number] == "comment" then -- Trailing whitespace at the end of a line comment or inside long comment. trailing_ws_code = "614" end -- The difference between the start and the end of the warning range -- is the same in bytes and in characters because whitespace characters are ASCII. -- Can calculate one based on the three others. local trailing_ws_end_byte = line_end_byte - 1 local trailing_ws_end_char = line_offset + line_length - 1 local trailing_ws_start_char = trailing_ws_end_char - (trailing_ws_end_byte - trailing_ws_start_byte) chstate:warn(trailing_ws_code, line_number, trailing_ws_start_char, trailing_ws_end_char) end -- Don't look for inconsistent whitespace in pure whitespace lines. if trailing_ws_code ~= "611" then local leading_ws_start_byte, leading_ws_end_byte = chstate.source:find( "^[ \t\f\v]- \t[ \t\f\v]*", line_offset) if leading_ws_start_byte then -- Inconsistent leading whitespace (SPACE followed by TAB). -- Calculate warning end in characters using same logic as above. local leading_ws_start_char = line_offset local leading_ws_end_char = leading_ws_start_char + (leading_ws_end_byte - leading_ws_start_byte) chstate:warn("621", line_number, line_offset, leading_ws_end_char) end end end end end return stage luacheck-0.25.0/src/luacheck/stages/detect_cyclomatic_complexity.lua000066400000000000000000000076371410451437000256640ustar00rootroot00000000000000local utils = require "luacheck.utils" local stage = {} local function cyclomatic_complexity_message_format(warning) local template = "cyclomatic complexity of %s is too high ({complexity} > {max_complexity})" local function_descr if warning.function_type == "main_chunk" then function_descr = "main chunk" elseif warning.function_name then function_descr = "{function_type} {function_name!}" else function_descr = "function" end return template:format(function_descr) end stage.warnings = { ["561"] = {message_format = cyclomatic_complexity_message_format, fields = {"complexity", "function_type", "function_name"}} } local function warn_cyclomatic_complexity(chstate, line, complexity) if line == chstate.top_line then chstate:warn("561", 1, 1, 1, { complexity = complexity, function_type = "main_chunk" }) else local node = line.node chstate:warn_range("561", node, { complexity = complexity, function_type = node[1][1] and node[1][1].implicit and "method" or "function", function_name = node.name }) end end local CyclomaticComplexityMetric = utils.class() function CyclomaticComplexityMetric:incr_decisions(count) self.count = self.count + count end function CyclomaticComplexityMetric:calc_expr(node) if node.tag == "Op" and (node[1] == "and" or node[1] == "or") then self:incr_decisions(1) end if node.tag ~= "Function" then self:calc_exprs(node) end end function CyclomaticComplexityMetric:calc_exprs(exprs) for _, expr in ipairs(exprs) do if type(expr) == "table" then self:calc_expr(expr) end end end function CyclomaticComplexityMetric:calc_item_Eval(item) self:calc_expr(item.node) end function CyclomaticComplexityMetric:calc_item_Local(item) if item.rhs then self:calc_exprs(item.rhs) end end function CyclomaticComplexityMetric:calc_item_Set(item) self:calc_exprs(item.rhs) end function CyclomaticComplexityMetric:calc_item(item) local f = self["calc_item_" .. item.tag] if f then f(self, item) end end function CyclomaticComplexityMetric:calc_items(items) for _, item in ipairs(items) do self:calc_item(item) end end -- stmt if: {condition, block; condition, block; ... else_block} function CyclomaticComplexityMetric:calc_stmt_If(node) for i = 1, #node - 1, 2 do self:incr_decisions(1) self:calc_stmts(node[i+1]) end if #node % 2 == 1 then self:calc_stmts(node[#node]) end end -- stmt while: {condition, block} function CyclomaticComplexityMetric:calc_stmt_While(node) self:incr_decisions(1) self:calc_stmts(node[2]) end -- stmt repeat: {block, condition} function CyclomaticComplexityMetric:calc_stmt_Repeat(node) self:incr_decisions(1) self:calc_stmts(node[1]) end -- stmt forin: {iter_vars, expression_list, block} function CyclomaticComplexityMetric:calc_stmt_Forin(node) self:incr_decisions(1) self:calc_stmts(node[3]) end -- stmt fornum: {first_var, expression, expression, expression[optional], block} function CyclomaticComplexityMetric:calc_stmt_Fornum(node) self:incr_decisions(1) self:calc_stmts(node[5] or node[4]) end function CyclomaticComplexityMetric:calc_stmt(node) local f = self["calc_stmt_" .. node.tag] if f then f(self, node) end end function CyclomaticComplexityMetric:calc_stmts(stmts) for _, stmt in ipairs(stmts) do self:calc_stmt(stmt) end end -- Cyclomatic complexity of a function equals to the number of decision points plus 1. function CyclomaticComplexityMetric:report(chstate, line) self.count = 1 self:calc_stmts(line.node[2]) self:calc_items(line.items) warn_cyclomatic_complexity(chstate, line, self.count) end function stage.run(chstate) local ccmetric = CyclomaticComplexityMetric() for _, line in ipairs(chstate.lines) do ccmetric:report(chstate, line) end end return stage luacheck-0.25.0/src/luacheck/stages/detect_empty_blocks.lua000066400000000000000000000013621410451437000237400ustar00rootroot00000000000000local core_utils = require "luacheck.core_utils" local stage = {} stage.warnings = { ["541"] = {message_format = "empty do..end block", fields = {}}, ["542"] = {message_format = "empty if branch", fields = {}} } local function check_block(chstate, block, code) if #block == 0 then chstate:warn_range(code, block) end end local function check_node(chstate, node) if node.tag == "Do" then check_block(chstate, node, "541") return end for index = 2, #node, 2 do check_block(chstate, node[index], "542") end if #node % 2 == 1 then check_block(chstate, node[#node], "542") end end function stage.run(chstate) core_utils.each_statement(chstate, {"Do", "If"}, check_node) end return stage luacheck-0.25.0/src/luacheck/stages/detect_empty_statements.lua000066400000000000000000000003741410451437000246540ustar00rootroot00000000000000local stage = {} stage.warnings = { ["551"] = {message_format = "empty statement", fields = {}} } function stage.run(chstate) for _, range in ipairs(chstate.useless_semicolons) do chstate:warn_range("551", range) end end return stage luacheck-0.25.0/src/luacheck/stages/detect_globals.lua000066400000000000000000000176261410451437000227020ustar00rootroot00000000000000local utils = require "luacheck.utils" local stage = {} local function prefix_if_indirect(message) return function(warning) if warning.indirect then return "indirectly " .. message else return message end end end local function setting_global_format_message(warning) -- `module` field is set during filtering. if warning.module then return "setting non-module global variable {name!}" else return "setting non-standard global variable {name!}" end end local global_warning_fields = {"name", "indexing", "previous_indexing_len", "top", "indirect"} stage.warnings = { ["111"] = {message_format = setting_global_format_message, fields = global_warning_fields}, ["112"] = {message_format = "mutating non-standard global variable {name!}", fields = global_warning_fields}, ["113"] = {message_format = "accessing undefined variable {name!}", fields = global_warning_fields}, -- The following warnings are added during filtering. ["121"] = {message_format = "setting read-only global variable {name!}", fields = {}}, ["122"] = {message_format = prefix_if_indirect("setting read-only field {field!} of global {name!}"), fields = {}}, ["131"] = {message_format = "unused global variable {name!}", fields = {}}, ["142"] = {message_format = prefix_if_indirect("setting undefined field {field!} of global {name!}"), fields = {}}, ["143"] = {message_format = prefix_if_indirect("accessing undefined field {field!} of global {name!}"), fields = {}} } local action_codes = { set = "1", mutate = "2", access = "3" } -- `index` describes an indexing, where `index[1]` is a global node -- and other items describe keys: each one is a string node, "not_string", -- or "unknown". `node` is literal base node that's indexed. -- E.g. in `local a = table.a; a.b = "c"` `node` is `a` node of the second -- statement and `index` describes `table.a.b`. -- `index.previous_indexing_len` is optional length of prefix of `index` array representing last assignment -- in the aliasing chain, e.g. `2` in the previous example (because last indexing is `table.a`). local function warn_global(chstate, node, index, is_lhs, is_top_line) local global = index[1] local action = is_lhs and (#index == 1 and "set" or "mutate") or "access" local indexing if #index > 1 then indexing = {} for i, field in ipairs(index) do if i > 1 then if field == "unknown" then indexing[i - 1] = true elseif field == "not_string" then indexing[i - 1] = false else indexing[i - 1] = field[1] end end end end chstate:warn_range("11" .. action_codes[action], node, { name = global[1], indexing = indexing, previous_indexing_len = index.previous_indexing_len, top = is_top_line and action == "set" or nil, indirect = node ~= global or nil }) end local function resolved_to_index(resolution) return resolution ~= "unknown" and resolution ~= "not_string" and resolution.tag ~= "String" end local literal_tags = utils.array_to_set({"Nil", "True", "False", "Number", "String", "Table", "Function"}) local deep_resolve -- Forward declaration. local function resolve_node(node, item) if node.tag == "Id" or node.tag == "Index" then deep_resolve(node, item) return node.resolution elseif literal_tags[node.tag] then return node.tag == "String" and node or "not_string" else return "unknown" end end -- Resolves value of an identifier or index node, tracking through simple -- assignments like `local foo = bar.baz`. -- Can be given an `Invoke` node to resolve the method field. -- Sets `node.resolution` to "unknown", "not_string", `string node`, or -- {previous_indexing_len = index, global_node, key...}. -- Each key can be "unknown", "not_string" or `string_node`. function deep_resolve(node, item) if node.resolution then return end -- Common case. -- Also protects against infinite recursion, if it's even possible. node.resolution = "unknown" local base = node local base_tag = node.tag == "Id" and "Id" or "Index" local keys = {} while base_tag == "Index" do table.insert(keys, 1, base[2]) base = base[1] base_tag = base.tag end if base_tag ~= "Id" then return end local var = base.var local base_resolution local previous_indexing_len if var then if not item.used_values[var] or #item.used_values[var] ~= 1 then -- Do not know where the value for the base local came from. return end local value = item.used_values[var][1] if not value.node then return end base_resolution = resolve_node(value.node, value.item) if resolved_to_index(base_resolution) then previous_indexing_len = #base_resolution end else base_resolution = {base} end if #keys == 0 then node.resolution = base_resolution elseif not resolved_to_index(base_resolution) then -- Indexing something unknown or indexing a literal. node.resolution = "unknown" else local resolution = utils.update({}, base_resolution) resolution.previous_indexing_len = previous_indexing_len for _, key in ipairs(keys) do local key_resolution = resolve_node(key, item) if resolved_to_index(key_resolution) then key_resolution = "unknown" end table.insert(resolution, key_resolution) end -- Assign resolution only after all the recursive calls. node.resolution = resolution end end local function detect_in_node(chstate, item, node, is_top_line, is_lhs) if node.tag == "Index" or node.tag == "Invoke" or node.tag == "Id" then if node.tag == "Id" and node.var then -- Do not warn about assignments to and accesses of local variables -- that resolve to globals or their fields. return end deep_resolve(node, item) local resolution = node.resolution -- Still need to recurse into base and key nodes. -- E.g. don't miss a global in `(global1())[global2()]. if node.tag == "Invoke" then for i = 3, #node do detect_in_node(chstate, item, node[i], is_top_line) end end if node.tag ~= "Id" then repeat detect_in_node(chstate, item, node[2], is_top_line) node = node[1] until node.tag ~= "Index" if node.tag ~= "Id" then detect_in_node(chstate, item, node, is_top_line) end end if resolved_to_index(resolution) then warn_global(chstate, node, resolution, is_lhs, is_top_line) end elseif node.tag ~= "Function" then for _, nested_node in ipairs(node) do if type(nested_node) == "table" then detect_in_node(chstate, item, nested_node, is_top_line) end end end end local function detect_in_nodes(chstate, item, nodes, is_top_line, is_lhs) for _, node in ipairs(nodes) do detect_in_node(chstate, item, node, is_top_line, is_lhs) end end local function detect_globals_in_line(chstate, line) local is_top_line = line == chstate.top_line for _, item in ipairs(line.items) do if item.tag == "Eval" then detect_in_node(chstate, item, item.node, is_top_line) elseif item.tag == "Local" then if item.rhs then detect_in_nodes(chstate, item, item.rhs, is_top_line) end elseif item.tag == "Set" then detect_in_nodes(chstate, item, item.lhs, is_top_line, true) detect_in_nodes(chstate, item, item.rhs, is_top_line) end end end -- Warns about assignments, field accesses, and mutations of global variables, -- tracing through localizing assignments such as `local t = table`. function stage.run(chstate) for _, line in ipairs(chstate.lines) do detect_globals_in_line(chstate, line) end end return stage luacheck-0.25.0/src/luacheck/stages/detect_reversed_fornum_loops.lua000066400000000000000000000015461410451437000256720ustar00rootroot00000000000000local core_utils = require "luacheck.core_utils" local stage = {} stage.warnings = { ["571"] = {message_format = "numeric for loop goes from #(expr) down to {limit} but loop step is not negative", fields = {"limit"}} } local function check_fornum(chstate, node) if node[2].tag ~= "Op" or node[2][1] ~= "len" then return end local limit, limit_repr = core_utils.eval_const_node(node[3]) if not limit or limit > 1 then return end local step = 1 if node[5] then step = core_utils.eval_const_node(node[4]) end if step and step >= 0 then chstate:warn_range("571", node, { limit = limit_repr }) end end -- Warns about loops trying to go from `#(expr)` to `1` with positive step. function stage.run(chstate) core_utils.each_statement(chstate, {"Fornum"}, check_fornum) end return stage luacheck-0.25.0/src/luacheck/stages/detect_unbalanced_assignments.lua000066400000000000000000000015521410451437000257550ustar00rootroot00000000000000local core_utils = require "luacheck.core_utils" local stage = {} stage.warnings = { ["531"] = {message_format = "right side of assignment has more values than left side expects", fields = {}}, ["532"] = {message_format = "right side of assignment has less values than left side expects", fields = {}} } local function is_unpacking(node) return node.tag == "Dots" or node.tag == "Call" or node.tag == "Invoke" end local function check_assignment(chstate, node) local rhs = node[2] if not rhs then return end local lhs = node[1] if #rhs > #lhs then chstate:warn_range("531", node) elseif #rhs < #lhs and node.tag == "Set" and not is_unpacking(rhs[#rhs]) then chstate:warn_range("532", node) end end function stage.run(chstate) core_utils.each_statement(chstate, {"Set", "Local"}, check_assignment) end return stage luacheck-0.25.0/src/luacheck/stages/detect_uninit_accesses.lua000066400000000000000000000042231410451437000244230ustar00rootroot00000000000000local stage = {} stage.warnings = { ["321"] = {message_format = "accessing uninitialized variable {name!}", fields = {"name"}}, ["341"] = {message_format = "mutating uninitialized variable {name!}", fields = {"name"}} } local function detect_uninit_access_in_line(chstate, line) for _, item in ipairs(line.items) do for _, action_key in ipairs({"accesses", "mutations"}) do local code = action_key == "accesses" and "321" or "341" local item_var_map = item[action_key] if item_var_map then for var, accessing_nodes in pairs(item_var_map) do -- If there are no values at all reaching this access, not even the empty one, -- this item (or a closure containing it) is not reachable from variable definition. -- It will be reported as unreachable code, no need to report uninitalized accesses in it. if item.used_values[var] then -- If this variable is has only one, empty value then it's already reported as never set, -- no need to report each access. if not (#var.values == 1 and var.values[1].empty) then local all_possible_values_empty = true for _, possible_value in ipairs(item.used_values[var]) do if not possible_value.empty then all_possible_values_empty = false break end end if all_possible_values_empty then for _, accessing_node in ipairs(accessing_nodes) do chstate:warn_range(code, accessing_node, { name = accessing_node[1] }) end end end end end end end end end -- Warns about accesses and mutations that don't resolve to any values except initial empty one. function stage.run(chstate) for _, line in ipairs(chstate.lines) do detect_uninit_access_in_line(chstate, line) end end return stage luacheck-0.25.0/src/luacheck/stages/detect_unreachable_code.lua000066400000000000000000000021221410451437000245030ustar00rootroot00000000000000local stage = {} stage.warnings = { ["511"] = {message_format = "unreachable code", fields = {}}, ["512"] = {message_format = "loop is executed at most once", fields = {}} } local function noop_callback() end local function detect_unreachable_code(chstate, line) local reachable_indexes = {} -- Mark all items reachable from the function start. line:walk(reachable_indexes, 1, noop_callback) -- All remaining items are unreachable. -- However, there is no point in reporting all of them. -- Only report those that are not reachable from any already reported ones. for item_index, item in ipairs(line.items) do if not reachable_indexes[item_index] then if item.node then chstate:warn_range(item.loop_end and "512" or "511", item.node) -- Mark all items reachable from the item just reported. line:walk(reachable_indexes, item_index, noop_callback) end end end end function stage.run(chstate) for _, line in ipairs(chstate.lines) do detect_unreachable_code(chstate, line) end end return stage luacheck-0.25.0/src/luacheck/stages/detect_unused_fields.lua000066400000000000000000000045631410451437000241040ustar00rootroot00000000000000local core_utils = require "luacheck.core_utils" local stage = {} local function unused_field_value_message_format(warning) local target = warning.index and "index" or "field" return "value assigned to " .. target .. " {field!} is overwritten on line {overwritten_line} before use" end stage.warnings = { ["314"] = {message_format = unused_field_value_message_format, fields = {"field", "index", "overwritten_line","overwritten_column", "overwritten_end_column"}} } local function warn_unused_field_value(chstate, node, field_repr, is_index, overwriting_node) chstate:warn_range("314", node, { field = field_repr, index = is_index, overwritten_line = overwriting_node.line, overwritten_column = chstate:offset_to_column(overwriting_node.line, overwriting_node.offset), overwritten_end_column = chstate:offset_to_column(overwriting_node.line, overwriting_node.end_offset) }) end local function check_table(chstate, node) local array_index = 1.0 local key_value_to_node = {} local key_node_to_repr = {} local index_key_nodes = {} for _, pair in ipairs(node) do local key_value local key_repr local key_node if pair.tag == "Pair" then key_node = pair[1] key_value, key_repr = core_utils.eval_const_node(key_node) else key_node = pair key_value = array_index key_repr = tostring(math.floor(key_value)) array_index = array_index + 1.0 end if key_value then local prev_key_node = key_value_to_node[key_value] local prev_key_repr = key_node_to_repr[prev_key_node] local prev_key_is_index = index_key_nodes[prev_key_node] if prev_key_node then warn_unused_field_value(chstate, prev_key_node, prev_key_repr, prev_key_is_index, key_node) end key_value_to_node[key_value] = key_node key_node_to_repr[key_node] = key_repr if pair.tag ~= "Pair" then index_key_nodes[key_node] = true end end end end local function check_nodes(chstate, nodes) for _, node in ipairs(nodes) do if type(node) == "table" then if node.tag == "Table" then check_table(chstate, node) end check_nodes(chstate, node) end end end function stage.run(chstate) check_nodes(chstate, chstate.ast) end return stage luacheck-0.25.0/src/luacheck/stages/detect_unused_locals.lua000066400000000000000000000271351410451437000241130ustar00rootroot00000000000000local utils = require "luacheck.utils" local stage = {} local function unused_local_message_format(warning) if warning.func then if warning.recursive then return "unused recursive function {name!}" elseif warning.mutually_recursive then return "unused mutually recursive function {name!}" else return "unused function {name!}" end else return "unused variable {name!}" end end local function unused_arg_message_format(warning) if warning.name == "..." then return "unused variable length argument" else return "unused argument {name!}" end end local function unused_or_overwritten_warning(message_format) return { message_format = function(warning) if warning.overwritten_line then return message_format .. " is overwritten on line {overwritten_line} before use" else return message_format .. " is unused" end end, fields = {"name", "secondary", "overwritten_line", "overwritten_column", "overwritten_end_column"} } end stage.warnings = { ["211"] = {message_format = unused_local_message_format, fields = {"name", "func", "secondary", "useless", "recursive", "mutually_recursive"}}, ["212"] = {message_format = unused_arg_message_format, fields = {"name", "self"}}, ["213"] = {message_format = "unused loop variable {name!}", fields = {"name"}}, ["221"] = {message_format = "variable {name!} is never set", fields = {"name", "secondary"}}, ["231"] = {message_format = "variable {name!} is never accessed", fields = {"name", "secondary"}}, ["232"] = {message_format = "argument {name!} is never accessed", fields = {"name"}}, ["233"] = {message_format = "loop variable {name!} is never accessed", fields = {"name"}}, ["241"] = {message_format = "variable {name!} is mutated but never accessed", fields = {"name", "secondary"}}, ["311"] = unused_or_overwritten_warning("value assigned to variable {name!}"), ["312"] = unused_or_overwritten_warning("value of argument {name!}"), ["313"] = unused_or_overwritten_warning("value of loop variable {name!}"), ["331"] = {message_format = "value assigned to variable {name!} is mutated but never accessed", fields = {"name", "secondary"}} } local function is_secondary(value) return value.secondaries and value.secondaries.used end local type_codes = { var = "1", func = "1", arg = "2", loop = "3", loopi = "3" } local function warn_unused_var(chstate, value, is_useless) chstate:warn_value("21" .. type_codes[value.var.type], value, { secondary = is_secondary(value) or nil, func = value.type == "func" or nil, self = value.var.self, useless = value.var.name == "_" and is_useless or nil }) end local function warn_unaccessed_var(chstate, var, is_mutated) -- Mark as secondary if all assigned values are secondary. -- It is guaranteed that there are at least two values. local secondary = true for _, value in ipairs(var.values) do if not value.empty and not is_secondary(value) then secondary = nil break end end chstate:warn_var("2" .. (is_mutated and "4" or "3") .. type_codes[var.type], var, { secondary = secondary }) end local function warn_unused_value(chstate, value, overwriting_node) local warning = chstate:warn_value("3" .. (value.mutated and "3" or "1") .. type_codes[value.type], value, { secondary = is_secondary(value) or nil }) if overwriting_node then warning.overwritten_line = overwriting_node.line warning.overwritten_column = chstate:offset_to_column(overwriting_node.line, overwriting_node.offset) warning.overwritten_end_column = chstate:offset_to_column(overwriting_node.line, overwriting_node.end_offset) end end -- Returns `true` if a variable should be reported as a function instead of simply local, -- `false` otherwise. -- A variable is considered a function if it has a single assignment and the value is a function, -- or if there is a forward declaration with a function assignment later. local function is_function_var(var) return (#var.values == 1 and var.values[1].type == "func") or ( #var.values == 2 and var.values[1].empty and var.values[2].type == "func") end local externally_accessible_tags = utils.array_to_set({"Id", "Index", "Call", "Invoke", "Op", "Paren", "Dots"}) local function is_externally_accessible(value) return value.type ~= "var" or (value.node and externally_accessible_tags[value.node.tag]) end local function get_overwriting_lhs_node(item, value) for _, node in ipairs(item.lhs) do if node.var == value.var then return node end end end local function get_second_overwriting_lhs_node(item, value) local after_value_node for _, node in ipairs(item.lhs) do if node.var == value.var then if after_value_node then return node elseif node == value.var_node then after_value_node = true end end end end local function detect_unused_local(chstate, var) if is_function_var(var) then local value = var.values[2] or var.values[1] if not value.used then warn_unused_var(chstate, value) end elseif #var.values == 1 then local value = var.values[1] if not value.used then if value.mutated then if not is_externally_accessible(value) then warn_unaccessed_var(chstate, var, true) end else warn_unused_var(chstate, value, value.empty) end elseif value.empty then chstate:warn_var("221", var) end elseif not var.accessed and not var.mutated then warn_unaccessed_var(chstate, var) else local no_values_externally_accessible = true for _, value in ipairs(var.values) do if is_externally_accessible(value) then no_values_externally_accessible = false end end if not var.accessed and no_values_externally_accessible then warn_unaccessed_var(chstate, var, true) end for _, value in ipairs(var.values) do if not value.empty then if not value.used then if not value.mutated then local overwriting_node if value.overwriting_item then if value.overwriting_item ~= value.item then overwriting_node = get_overwriting_lhs_node(value.overwriting_item, value) end else overwriting_node = get_second_overwriting_lhs_node(value.item, value) end warn_unused_value(chstate, value, overwriting_node) elseif not is_externally_accessible(value) then if var.accessed or not no_values_externally_accessible then warn_unused_value(chstate, value) end end end end end end end local function detect_unused_locals_in_line(chstate, line) for _, item in ipairs(line.items) do if item.tag == "Local" then for var in pairs(item.set_variables) do -- Do not check the implicit top level vararg. if var.node.line then detect_unused_local(chstate, var) end end end end end local function detect_unused_locals(chstate) for _, line in ipairs(chstate.lines) do detect_unused_locals_in_line(chstate, line) end end local function mark_reachable_lines(edges, marked, line) for connected_line in pairs(edges[line]) do if not marked[connected_line] then marked[connected_line] = true mark_reachable_lines(edges, marked, connected_line) end end end local function detect_unused_rec_funcs(chstate) -- Build a graph of usage relations of all closures. -- Closure A is used by closure B iff either B is parent -- of A and A is not assigned to a local/upvalue, or -- B uses local/upvalue value that is A. -- Closures not reachable from root closure are unused, -- report corresponding values/variables if not done already. local line = chstate.top_line -- Initialize edges maps. local forward_edges = {[line] = {}} local backward_edges = {[line] = {}} for _, nested_line in ipairs(line.lines) do forward_edges[nested_line] = {} backward_edges[nested_line] = {} end -- Add edges leading to each nested line. for _, nested_line in ipairs(line.lines) do if nested_line.node.value then for using_line in pairs(nested_line.node.value.using_lines) do forward_edges[using_line][nested_line] = true backward_edges[nested_line][using_line] = true end elseif nested_line.parent then forward_edges[nested_line.parent][nested_line] = true backward_edges[nested_line][nested_line.parent] = true end end -- Recursively mark all closures reachable from root closure and unused closures. -- Closures reachable from main chunk are used; closure reachable from unused closures -- depend on that closure; that is, fixing warning about parent unused closure -- fixes warning about the child one, so issuing a warning for the child is superfluous. local marked = {[line] = true} mark_reachable_lines(forward_edges, marked, line) for _, nested_line in ipairs(line.lines) do if nested_line.node.value and not nested_line.node.value.used then marked[nested_line] = true mark_reachable_lines(forward_edges, marked, nested_line) end end -- Deal with unused closures. for _, nested_line in ipairs(line.lines) do local value = nested_line.node.value if value and value.used and not marked[nested_line] then -- This closure is used by some closure, but is not marked as reachable -- from main chunk or any of reported closures. -- Find candidate group of mutually recursive functions containing this one: -- mark sets of closures reachable from it by forward and backward edges, -- intersect them. Ignore already marked closures in the process to avoid -- issuing superfluous, dependent warnings. local forward_marked = setmetatable({}, {__index = marked}) local backward_marked = setmetatable({}, {__index = marked}) mark_reachable_lines(forward_edges, forward_marked, nested_line) mark_reachable_lines(backward_edges, backward_marked, nested_line) -- Iterate over closures in the group. for mut_rec_line in pairs(forward_marked) do if rawget(backward_marked, mut_rec_line) then marked[mut_rec_line] = true value = mut_rec_line.node.value if value then -- Report this closure as self recursive or mutually recursive. local is_self_recursive = forward_edges[mut_rec_line][mut_rec_line] if is_function_var(value.var) then chstate:warn_value("211", value, { func = true, mutually_recursive = not is_self_recursive or nil, recursive = is_self_recursive or nil }) else chstate:warn_value("311", value) end end end end end end end -- Warns about unused local variables and their values as well as locals that -- are accessed but never set or set but never accessed. -- Warns about unused recursive functions. function stage.run(chstate) detect_unused_locals(chstate) detect_unused_rec_funcs(chstate) end return stage luacheck-0.25.0/src/luacheck/stages/init.lua000066400000000000000000000042231410451437000206570ustar00rootroot00000000000000local utils = require "luacheck.utils" local stages = {} -- Checking is organized into stages run one after another. -- Each stage is in its own module and provides `run` function operating on a check state, -- and optionally `warnings` table mapping issue codes to tables with fields `message_format` -- containing format string for the issue or a function returning it given the issue, -- and `fields` containing array of extra fields this warning can have. stages.names = { "parse", "unwrap_parens", "linearize", "parse_inline_options", "name_functions", "resolve_locals", "detect_bad_whitespace", "detect_cyclomatic_complexity", "detect_empty_blocks", "detect_empty_statements", "detect_globals", "detect_reversed_fornum_loops", "detect_unbalanced_assignments", "detect_uninit_accesses", "detect_unreachable_code", "detect_unused_fields", "detect_unused_locals" } stages.modules = {} for _, name in ipairs(stages.names) do table.insert(stages.modules, (require("luacheck.stages." .. name))) end stages.warnings = {} local base_fields = {"code", "line", "column", "end_column"} local function register_warnings(warnings) for code, warning in pairs(warnings) do assert(not stages.warnings[code]) assert(warning.message_format) assert(warning.fields) local full_fields = utils.concat_arrays({base_fields, warning.fields}) stages.warnings[code] = { message_format = warning.message_format, fields = full_fields, fields_set = utils.array_to_set(full_fields) } end end -- Issues that do not originate from normal check stages (excluding global related ones). register_warnings({ ["011"] = {message_format = "{msg}", fields = {"msg", "prev_line", "prev_column", "prev_end_column"}}, ["631"] = {message_format = "line is too long ({end_column} > {max_length})", fields = {}} }) for _, stage_module in ipairs(stages.modules) do if stage_module.warnings then register_warnings(stage_module.warnings) end end function stages.run(chstate) for _, stage_module in ipairs(stages.modules) do stage_module.run(chstate) end end return stages luacheck-0.25.0/src/luacheck/stages/linearize.lua000066400000000000000000000426451410451437000217100ustar00rootroot00000000000000local parser = require "luacheck.parser" local utils = require "luacheck.utils" local stage = {} local function redefined_warning(message_format) return { message_format = message_format, fields = {"name", "prev_line", "prev_column", "prev_end_column", "self"} } end stage.warnings = { ["411"] = redefined_warning("variable {name!} was previously defined on line {prev_line}"), ["412"] = redefined_warning("variable {name!} was previously defined as an argument on line {prev_line}"), ["413"] = redefined_warning("variable {name!} was previously defined as a loop variable on line {prev_line}"), ["421"] = redefined_warning("shadowing definition of variable {name!} on line {prev_line}"), ["422"] = redefined_warning("shadowing definition of argument {name!} on line {prev_line}"), ["423"] = redefined_warning("shadowing definition of loop variable {name!} on line {prev_line}"), ["431"] = redefined_warning("shadowing upvalue {name!} on line {prev_line}"), ["432"] = redefined_warning("shadowing upvalue argument {name!} on line {prev_line}"), ["433"] = redefined_warning("shadowing upvalue loop variable {name!} on line {prev_line}"), ["521"] = {message_format = "unused label {label!}", fields = {"label"}} } local type_codes = { var = "1", func = "1", arg = "2", loop = "3", loopi = "3" } local function warn_redefined(chstate, var, prev_var, is_same_scope) local code = "4" .. (is_same_scope and "1" or var.line == prev_var.line and "2" or "3") .. type_codes[prev_var.type] chstate:warn_var(code, var, { self = var.self and prev_var.self, prev_line = prev_var.node.line, prev_column = chstate:offset_to_column(prev_var.node.line, prev_var.node.offset), prev_end_column = chstate:offset_to_column(prev_var.node.line, prev_var.node.end_offset) }) end local function warn_unused_label(chstate, label) chstate:warn_range("521", label.range, { label = label.name }) end local pseudo_labels = utils.array_to_set({"do", "else", "break", "end", "return"}) local Line = utils.class() function Line:__init(node, parent, value) -- Maps variables to arrays of accessing items. self.accessed_upvalues = {} -- Maps variables to arrays of mutating items. self.mutated_upvalues = {} -- Maps variables to arays of setting items. self.set_upvalues = {} self.lines = {} self.node = node self.parent = parent self.value = value self.items = utils.Stack() end -- Calls callback with line, index, item, ... for each item reachable from starting item. -- `visited` is a set of already visited indexes. -- Callback can return true to stop walking from current item. function Line:walk(visited, index, callback, ...) if visited[index] then return end visited[index] = true local item = self.items[index] if callback(self, index, item, ...) then return end if not item then return elseif item.tag == "Jump" then return self:walk(visited, item.to, callback, ...) elseif item.tag == "Cjump" then self:walk(visited, item.to, callback, ...) end return self:walk(visited, index + 1, callback, ...) end local function new_scope(line) return { vars = {}, labels = {}, gotos = {}, line = line } end local function new_var(line, node, type_) return { name = node[1], node = node, type = type_, self = node.implicit, line = line, scope_start = line.items.size + 1, values = {} } end local function new_value(var_node, value_node, item, is_init) local value = { var = var_node.var, var_node = var_node, type = is_init and var_node.var.type or "var", node = value_node, using_lines = {}, empty = is_init and not value_node and (var_node.var.type == "var"), item = item } if value_node and value_node.tag == "Function" then value.type = "func" value_node.value = value end return value end local function new_label(line, name, range) return { name = name, range = range, index = line.items.size + 1 } end local function new_goto(name, jump, range) return { name = name, jump = jump, range = range } end local function new_jump_item(is_conditional) return { tag = is_conditional and "Cjump" or "Jump" } end local function new_eval_item(node) return { tag = "Eval", node = node, accesses = {}, used_values = {}, lines = {} } end local function new_noop_item(node, loop_end) return { tag = "Noop", node = node, loop_end = loop_end } end local function new_local_item(node) return { tag = "Local", node = node, lhs = node[1], rhs = node[2], accesses = node[2] and {}, used_values = node[2] and {}, lines = node[2] and {} } end local function new_set_item(node) return { tag = "Set", node = node, lhs = node[1], rhs = node[2], accesses = {}, mutations = {}, used_values = {}, lines = {} } end local function is_unpacking(node) return node.tag == "Dots" or node.tag == "Call" or node.tag == "Invoke" end local LinState = utils.class() function LinState:__init(chstate) self.chstate = chstate self.lines = utils.Stack() self.scopes = utils.Stack() end function LinState:enter_scope() self.scopes:push(new_scope(self.lines.top)) end function LinState:leave_scope() local left_scope = self.scopes:pop() local prev_scope = self.scopes.top for _, goto_ in ipairs(left_scope.gotos) do local label = left_scope.labels[goto_.name] if label then goto_.jump.to = label.index label.used = true else if not prev_scope or prev_scope.line ~= self.lines.top then if goto_.name == "break" then parser.syntax_error("'break' is not inside a loop", goto_.range) else parser.syntax_error(("no visible label '%s'"):format(goto_.name), goto_.range) end end table.insert(prev_scope.gotos, goto_) end end for name, label in pairs(left_scope.labels) do if not label.used and not pseudo_labels[name] then warn_unused_label(self.chstate, label) end end for _, var in pairs(left_scope.vars) do var.scope_end = self.lines.top.items.size end end function LinState:register_var(node, type_) local var = new_var(self.lines.top, node, type_) local prev_var = self:resolve_var(var.name) if prev_var then local is_same_scope = self.scopes.top.vars[var.name] if var.name ~= "..." then warn_redefined(self.chstate, var, prev_var, is_same_scope) end if is_same_scope then prev_var.scope_end = self.lines.top.items.size end end self.scopes.top.vars[var.name] = var node.var = var return var end function LinState:register_vars(nodes, type_) for _, node in ipairs(nodes) do self:register_var(node, type_) end end function LinState:resolve_var(name) for _, scope in utils.ripairs(self.scopes) do local var = scope.vars[name] if var then return var end end end function LinState:check_var(node) if not node.var then node.var = self:resolve_var(node[1]) end return node.var end function LinState:register_label(name, range) local prev_label = self.scopes.top.labels[name] if prev_label then assert(not pseudo_labels[name]) parser.syntax_error(("label '%s' already defined on line %d"):format( name, prev_label.range.line), range, prev_label.range) end self.scopes.top.labels[name] = new_label(self.lines.top, name, range) end function LinState:emit(item) self.lines.top.items:push(item) end function LinState:emit_goto(name, is_conditional, range) local jump = new_jump_item(is_conditional) self:emit(jump) table.insert(self.scopes.top.gotos, new_goto(name, jump, range)) end local tag_to_boolean = { Nil = false, False = false, True = true, Number = true, String = true, Table = true, Function = true } -- Emits goto that jumps to ::name:: if bool(cond_node) == false. function LinState:emit_cond_goto(name, cond_node) local cond_bool = tag_to_boolean[cond_node.tag] if cond_bool ~= true then self:emit_goto(name, cond_bool ~= false) end end function LinState:emit_noop(node, loop_end) self:emit(new_noop_item(node, loop_end)) end function LinState:emit_stmt(stmt) self["emit_stmt_" .. stmt.tag](self, stmt) end function LinState:emit_stmts(stmts) for _, stmt in ipairs(stmts) do self:emit_stmt(stmt) end end function LinState:emit_block(block) self:enter_scope() self:emit_stmts(block) self:leave_scope() end function LinState:emit_stmt_Do(node) self:emit_noop(node) self:emit_block(node) end function LinState:emit_stmt_While(node) self:emit_noop(node) self:enter_scope() self:register_label("do") self:emit_expr(node[1]) self:emit_cond_goto("break", node[1]) self:emit_block(node[2]) self:emit_noop(node, true) self:emit_goto("do") self:register_label("break") self:leave_scope() end function LinState:emit_stmt_Repeat(node) self:emit_noop(node) self:enter_scope() self:register_label("do") self:enter_scope() self:emit_stmts(node[1]) self:emit_expr(node[2]) self:leave_scope() self:emit_cond_goto("do", node[2]) self:register_label("break") self:leave_scope() end function LinState:emit_stmt_Fornum(node) self:emit_noop(node) self:emit_expr(node[2]) self:emit_expr(node[3]) if node[5] then self:emit_expr(node[4]) end self:enter_scope() self:register_label("do") self:emit_goto("break", true) self:enter_scope() self:emit(new_local_item({{node[1]}})) self:register_var(node[1], "loopi") self:emit_stmts(node[5] or node[4]) self:leave_scope() self:emit_noop(node, true) self:emit_goto("do") self:register_label("break") self:leave_scope() end function LinState:emit_stmt_Forin(node) self:emit_noop(node) self:emit_exprs(node[2]) self:enter_scope() self:register_label("do") self:emit_goto("break", true) self:enter_scope() self:emit(new_local_item({node[1]})) self:register_vars(node[1], "loop") self:emit_stmts(node[3]) self:leave_scope() self:emit_noop(node, true) self:emit_goto("do") self:register_label("break") self:leave_scope() end function LinState:emit_stmt_If(node) self:emit_noop(node) self:enter_scope() for i = 1, #node - 1, 2 do self:enter_scope() self:emit_expr(node[i]) self:emit_cond_goto("else", node[i]) self:emit_block(node[i + 1]) self:emit_goto("end") self:register_label("else") self:leave_scope() end if #node % 2 == 1 then self:emit_block(node[#node]) end self:register_label("end") self:leave_scope() end function LinState:emit_stmt_Label(node) self:register_label(node[1], node) end function LinState:emit_stmt_Goto(node) self:emit_noop(node) self:emit_goto(node[1], false, node) end function LinState:emit_stmt_Break(node) self:emit_goto("break", false, node) end function LinState:emit_stmt_Return(node) self:emit_noop(node) self:emit_exprs(node) self:emit_goto("return") end function LinState:emit_expr(node) local item = new_eval_item(node) self:scan_expr(item, node) self:emit(item) end function LinState:emit_exprs(exprs) for _, expr in ipairs(exprs) do self:emit_expr(expr) end end LinState.emit_stmt_Call = LinState.emit_expr LinState.emit_stmt_Invoke = LinState.emit_expr function LinState:emit_stmt_Local(node) local item = new_local_item(node) self:emit(item) if node[2] then self:scan_exprs(item, node[2]) end self:register_vars(node[1], "var") end function LinState:emit_stmt_Localrec(node) local item = new_local_item(node) self:register_var(node[1][1], "var") self:emit(item) self:scan_expr(item, node[2][1]) end function LinState:emit_stmt_Set(node) local item = new_set_item(node) self:scan_exprs(item, node[2]) for _, expr in ipairs(node[1]) do if expr.tag == "Id" then local var = self:check_var(expr) if var then self:register_upvalue_action(item, var, "set_upvalues") end else assert(expr.tag == "Index") self:scan_lhs_index(item, expr) end end self:emit(item) end function LinState:scan_expr(item, node) local scanner = self["scan_expr_" .. node.tag] if scanner then scanner(self, item, node) end end function LinState:scan_exprs(item, nodes) for _, node in ipairs(nodes) do self:scan_expr(item, node) end end function LinState:register_upvalue_action(item, var, key) for _, line in utils.ripairs(self.lines) do if line == var.line then break end if not line[key][var] then line[key][var] = {} end table.insert(line[key][var], item) end end function LinState:mark_access(item, node) node.var.accessed = true if not item.accesses[node.var] then item.accesses[node.var] = {} end table.insert(item.accesses[node.var], node) self:register_upvalue_action(item, node.var, "accessed_upvalues") end function LinState:mark_mutation(item, node) node.var.mutated = true if not item.mutations[node.var] then item.mutations[node.var] = {} end table.insert(item.mutations[node.var], node) self:register_upvalue_action(item, node.var, "mutated_upvalues") end function LinState:scan_expr_Id(item, node) if self:check_var(node) then self:mark_access(item, node) end end function LinState:scan_expr_Dots(item, node) local dots = self:check_var(node) if not dots or dots.line ~= self.lines.top then parser.syntax_error("cannot use '...' outside a vararg function", node) end self:mark_access(item, node) end function LinState:scan_lhs_index(item, node) if node[1].tag == "Id" then if self:check_var(node[1]) then self:mark_mutation(item, node[1]) end elseif node[1].tag == "Index" then self:scan_lhs_index(item, node[1]) else self:scan_expr(item, node[1]) end self:scan_expr(item, node[2]) end LinState.scan_expr_Index = LinState.scan_exprs LinState.scan_expr_Call = LinState.scan_exprs LinState.scan_expr_Invoke = LinState.scan_exprs LinState.scan_expr_Paren = LinState.scan_exprs LinState.scan_expr_Table = LinState.scan_exprs LinState.scan_expr_Pair = LinState.scan_exprs function LinState:scan_expr_Op(item, node) self:scan_expr(item, node[2]) if node[3] then self:scan_expr(item, node[3]) end end -- Puts tables {var = value{} into field `set_variables` of items in line which set values. -- Registers set values in field `values` of variables. function LinState:register_set_variables() local line = self.lines.top for _, item in ipairs(line.items) do if item.tag == "Local" or item.tag == "Set" then item.set_variables = {} local is_init = item.tag == "Local" local unpacking_item -- Rightmost item of rhs which may unpack into several lhs items. if item.rhs then local last_rhs_item = item.rhs[#item.rhs] if is_unpacking(last_rhs_item) then unpacking_item = last_rhs_item end end local secondaries -- Array of values unpacked from rightmost rhs item. if unpacking_item and (#item.lhs > #item.rhs) then secondaries = {} end for i, node in ipairs(item.lhs) do local value if node.var then value = new_value(node, item.rhs and item.rhs[i] or unpacking_item, item, is_init) item.set_variables[node.var] = value table.insert(node.var.values, value) end if secondaries and (i >= #item.rhs) then if value then value.secondaries = secondaries table.insert(secondaries, value) else -- If one of secondary values is assigned to a global or index, -- it is considered used. secondaries.used = true end end end end end end function LinState:build_line(node) self.lines:push(Line(node, self.lines.top)) self:enter_scope() self:emit(new_local_item({node[1]})) self:enter_scope() self:register_vars(node[1], "arg") self:emit_stmts(node[2]) self:leave_scope() self:register_label("return") self:leave_scope() self:register_set_variables() local line = self.lines:pop() for _, prev_line in ipairs(self.lines) do table.insert(prev_line.lines, line) end return line end function LinState:scan_expr_Function(item, node) local line = self:build_line(node) table.insert(item.lines, line) for _, nested_line in ipairs(line.lines) do table.insert(item.lines, nested_line) end end -- Builds linear representation (line) of AST and assigns it as `chstate.top_line`. -- Assings an array of all lines as `chstate.lines`. -- Adds warnings for redefined/shadowed locals and unused labels. function stage.run(chstate) local linstate = LinState(chstate) chstate.top_line = linstate:build_line({{{tag = "Dots", "..."}}, chstate.ast}) assert(linstate.lines.size == 0) assert(linstate.scopes.size == 0) chstate.lines = {chstate.top_line} for _, nested_line in ipairs(chstate.top_line.lines) do table.insert(chstate.lines, nested_line) end end return stage luacheck-0.25.0/src/luacheck/stages/name_functions.lua000066400000000000000000000040011410451437000227160ustar00rootroot00000000000000local stage = {} local function get_index_name(base_name, key_node) if key_node.tag == "String" then return base_name .. "." .. key_node[1] end end local function get_full_field_name(node) if node.tag == "Id" then return node[1] elseif node.tag == "Index" then local base_name = get_full_field_name(node[1]) return base_name and get_index_name(base_name, node[2]) end end local handle_node local function handle_nodes(nodes) for _, node in ipairs(nodes) do if type(node) == "table" then handle_node(node) end end end function handle_node(node, name) if node.tag == "Function" then node.name = name handle_nodes(node[2]) elseif node.tag == "Set" or node.tag == "Local" or node.tag == "Localrec" then local lhs = node[1] local rhs = node[2] -- No need to handle LHS if there is no RHS, it's always just a list of locals in that case. if rhs then handle_nodes(lhs) for index, rhs_node in ipairs(rhs) do local lhs_node = lhs[index] local field_name = lhs_node and get_full_field_name(lhs_node) handle_node(rhs_node, field_name) end end elseif node.tag == "Table" and name then for _, pair_node in ipairs(node) do if pair_node.tag == "Pair" then local key_node = pair_node[1] local value_node = pair_node[2] handle_node(key_node) handle_node(value_node, get_index_name(name, key_node)) else handle_node(pair_node) end end else handle_nodes(node) end end -- Adds `name` field to `Function` ast nodes when possible: -- * Function assigned to a variable (doesn't matter if local or global): "foo". -- * Function assigned to a field: "foo.bar.baz". -- Function can be in a table assigned to a variable or a field, e.g. `foo.bar = {baz = function() ... end}`. -- * Otherwise: `nil`. function stage.run(chstate) handle_nodes(chstate.ast) end return stage luacheck-0.25.0/src/luacheck/stages/parse.lua000066400000000000000000000011201410451437000210170ustar00rootroot00000000000000local decoder = require "luacheck.decoder" local parser = require "luacheck.parser" local stage = {} function stage.run(chstate) chstate.source = decoder.decode(chstate.source_bytes) chstate.line_offsets = {} chstate.line_lengths = {} local ast, comments, code_lines, line_endings, useless_semicolons = parser.parse( chstate.source, chstate.line_offsets, chstate.line_lengths) chstate.ast = ast chstate.comments = comments chstate.code_lines = code_lines chstate.line_endings = line_endings chstate.useless_semicolons = useless_semicolons end return stage luacheck-0.25.0/src/luacheck/stages/parse_inline_options.lua000066400000000000000000000272571410451437000241530ustar00rootroot00000000000000local options = require "luacheck.options" local utils = require "luacheck.utils" local stage = {} stage.warnings = { -- Also produced during filtering for options that did not pass validation. ["021"] = {message_format = "{msg}", fields = {"msg"}}, ["022"] = {message_format = "unpaired push directive", fields = {}}, ["023"] = {message_format = "unpaired pop directive", fields = {}} } stage.inline_option_fields = {"line", "pop_count", "options", "column", "end_column"} local limit_opts = utils.array_to_set({"max_line_length", "max_code_line_length", "max_string_line_length", "max_comment_line_length", "max_cyclomatic_complexity"}) local function is_valid_option_name(name) if name == "std" or options.variadic_inline_options[name] then return true end name = name:gsub("^no_", "") return options.nullary_inline_options[name] or limit_opts[name] end -- Splits a token array for an inline option invocation into -- option name and argument array, or nil if invocation is invalid. local function split_invocation(tokens) -- Name of the option can be split into several space separated tokens. -- Since some valid names are prefixes of some other names -- (e.g. `unused` and `unused arguments`), the longest prefix of token -- array that is a valid option name should be considered. local cur_name local last_valid_name local last_valid_name_end_index for i, token in ipairs(tokens) do cur_name = cur_name and (cur_name .. "_" .. token) or token if is_valid_option_name(cur_name) then last_valid_name = cur_name last_valid_name_end_index = i end end if not last_valid_name then return end local args = {} for i = last_valid_name_end_index + 1, #tokens do table.insert(args, tokens[i]) end return last_valid_name, args end local function unexpected_num_args(name, args, expected) return ("inline option '%s' expects %d argument%s, %d given"):format( name, expected, expected == 1 and "" or "s", #args) end -- Parses inline option body, returns options or nil and error message. local function parse_options(body) local opts = {} local parts = utils.split(body, ",") for _, name_and_args in ipairs(parts) do local tokens = utils.split(name_and_args) local name, args = split_invocation(tokens) if not name then if #tokens == 0 then return nil, (#parts == 1) and "empty inline option" or "empty inline option invocation" else return nil, ("unknown inline option '%s'"):format(table.concat(tokens, " ")) end end if name == "std" then if #args ~= 1 then return nil, unexpected_num_args(name, args, 1) end opts.std = args[1] elseif name == "ignore" and #args == 0 then opts.ignore = {".*"} elseif options.variadic_inline_options[name] then opts[name] = args else local full_name = name:gsub("_", " ") local subs name, subs = name:gsub("^no_", "") local flag = subs == 0 if options.nullary_inline_options[name] then if #args ~= 0 then return nil, unexpected_num_args(full_name, args, 0) end opts[name] = flag else assert(limit_opts[name]) if flag then if #args ~= 1 then return nil, unexpected_num_args(full_name, args, 1) end local value = tonumber(args[1]) if not value then return nil, ("inline option '%s' expects number as argument"):format(name) end opts[name] = value else if #args ~= 0 then return nil, unexpected_num_args(full_name, args, 0) end opts[name] = false end end end end return opts end -- Parses comment contents, returns up to two `options` values (tables or "push" or "pop"). -- On an invalid inline comment returns nil and an error message. local function parse_inline_comment(comment_contents) local body = utils.after(utils.strip(comment_contents), "^luacheck:") if not body then return end local opts1, opts2 -- Remove comments in balanced parens. body = utils.strip((body:gsub("%b()", " "))) local after_push = body:match("^push%s+(.*)") if after_push then opts2 = "push" body = after_push elseif body == "push" or body == "pop" then return body end local err_msg opts1, err_msg = parse_options(body) return opts1, err_msg or opts2 end -- Returns an array of tables with column range info and an `options` field -- containing a table of options or "push" or "pop". -- Warns about invalid inline option comments. local function parse_inline_comments(chstate) local res = {} for _, comment in ipairs(chstate.comments) do local opts1, opts2 = parse_inline_comment(comment.contents) if opts1 then table.insert(res, { line = comment.line, column = chstate:offset_to_column(comment.line, comment.offset), end_column = chstate:offset_to_column(comment.line, comment.end_offset), options = opts1 }) if opts2 then table.insert(res, { line = comment.line, column = chstate:offset_to_column(comment.line, comment.offset), end_column = chstate:offset_to_column(comment.line, comment.end_offset), options = opts2 }) end elseif opts2 then chstate:warn_range("021", comment, {msg = opts2}) end end return res end -- Adds a table with `line`, `column`, and `options` fields to given array. -- For each function a table with `options` set to "push" for the function start -- and a talbe with `options` set to "pop" for the function end are added. local function add_function_boundaries(inline_options_and_boundaries, chstate) for _, line in ipairs(chstate.top_line.lines) do local fn_node = line.node table.insert(inline_options_and_boundaries, { line = fn_node.line, column = chstate:offset_to_column(fn_node.line, fn_node.offset), options = "push" }) table.insert(inline_options_and_boundaries, { line = fn_node.end_range.line, column = chstate:offset_to_column(fn_node.end_range.line, fn_node.end_range.offset), options = "pop" }) end end local function get_order(t) if t.options == "push" then return 1 elseif t.options == "pop" then return 3 else return 2 end end local function options_and_boundaries_comparator(t1, t2) if t1.line ~= t2.line then return t1.line < t2.line end -- For options and boundaries on the same line, all pushes are applied before options before pops. -- (Valid pops will be moved to the start of the next line later.) local order1 = get_order(t1) local order2 = get_order(t2) if order1 ~= order2 then return order1 < order2 else return t1.column < t2.column end end -- Applies bounadaries withing `inline_options_and_boundaries` to replace them with pop count -- instructions in the resulting array. -- Comments on lines with code are popped at the end of line. -- Warns about unpaired push and pop directives. local function apply_boundaries(chstate, inline_options_and_boundaries) local res = {} local res_last -- While iterating over inline options and boundaries track push -- boundaries that were not popped yet plus the number of options -- that would be on the option stack after applying all already -- processed option table pushes and pops. local pushes = utils.Stack() local push_option_counts = utils.Stack() local option_count = 0 for _, item in ipairs(inline_options_and_boundaries) do if item.options == "push" then pushes:push(item) push_option_counts:push(option_count) elseif item.options == "pop" then -- Function boundaries are implicit, don't allow inline options to pop -- them, don't allow function boundaries to pop inline option pushes either. -- Inline options boundaries have end_column, function boundaries don't. if not pushes.top or (item.end_column and not pushes.top.end_column) then -- Inline option pop against nothing or a function push, mark as unpaired. chstate:warn_column_range("023", item) else if not item.end_column then -- Function pop, remove any unpaired inline option pushes. while pushes.top and pushes.top.end_column do chstate:warn_column_range("022", pushes.top) pushes:pop() push_option_counts:pop() end end pushes:pop() local prev_option_count = push_option_counts:pop() local pop_count = option_count - prev_option_count if pop_count > 0 then -- Place the pop instruction at the start of the next line so that getting option stack -- for a line amounts to applying both the pop instruction and the option push for the line. local line = item.line + 1 -- Collapse with a previous table if it's on the same line. It can only be a pop count table. if res_last and res_last.line == line then res_last.pop_count = res_last.pop_count + pop_count else res_last = { line = line, pop_count = pop_count } table.insert(res, res_last) end end -- Update option stack size for this pop. option_count = prev_option_count end else -- Inline options table. Check if there is a pop count table for this line already. if res_last and res_last.line == item.line then res_last.options = item.options res_last.column = item.column res_last.end_column = item.end_column else res_last = item table.insert(res, item) end if chstate.code_lines[item.line] then -- Inline comment on a line with some code, immediately pop it. res_last = { line = item.line + 1, pop_count = 1 } table.insert(res, res_last) else option_count = option_count + 1 end end end -- Any remaining pushes are unpaired inline comments from the main chunk. while pushes.top do chstate:warn_column_range("022", pushes:pop()) end return res end -- Warns about invalid inline options. -- Sets `chstate.inline_options` to an array of tables that describe the way inline option tables -- are pushed onto and popped from the option stack when iterating over lines. -- Each table has field `line` that the array is sorted by and also ether or both sets of fields: -- * `pop_count` - refers to a number of option tables that should be popped from the stack before processing -- warnings on this line. -- * `options`, `column`, `end_column` - refers to an option table that should be pushed onto the stack -- before processing warnings on this line but after popping tables if `pop_count` is present. function stage.run(chstate) local inline_options_and_boundaries = parse_inline_comments(chstate) add_function_boundaries(inline_options_and_boundaries, chstate) table.sort(inline_options_and_boundaries, options_and_boundaries_comparator) chstate.inline_options = apply_boundaries(chstate, inline_options_and_boundaries) end return stage luacheck-0.25.0/src/luacheck/stages/resolve_locals.lua000066400000000000000000000214021410451437000227260ustar00rootroot00000000000000local stage = {} -- The main part of analysis is connecting assignments to locals or upvalues -- with accesses that may use the assigned value. -- Accesses and assignments are split into two groups based on whether they happen -- in the closure that defines subject local variable (main assignment, main access) -- or in some nested closure (closure assignment, closure access). -- To avoid false positives, it's assumed that a closure may be called at any point -- starting from expression that creates it. -- Additionally, all operations on upvalues are considered in bulk, as in, -- when a closure is called, it's assumed that any subset of its upvalue assignments -- and accesses may happen, in any order. -- Assignments and accesses are connected based on whether they can reach each other. -- A main assignment is connected with a main access when the assignment can reach the access. -- A main assignment is connected with a closure access when the assignment can reach the closure creation -- or the closure creation can reach the assignment. -- A closure assignment is connected with a main access when the closure creation can reach the access. -- A closure assignment is connected with a closure access when either closure creation can reach the other one. -- To determine what flow graph nodes an assignment or a closure creation can reach, -- they are independently propagated along the graph. -- Closure creation propagation is not bounded. -- Main assignment propagation is bounded by entrance and exit conditions for each reached flow graph node. -- Entrance condition checks that target local variable is still in scope. If entrance condition fails, -- nothing in the node can refer to the variable, and the scope can't be reentered later. -- So, in this case, assignment does not reach the node, and propagation does not continue. -- Exit condition checks that target local variable is not overwritten by an assignment in the node. -- If it fails, the assignment still reaches the node (because all accesses in a node are evaluated before any -- assignments take effect), but propagation does not continue. local function register_value(values_per_var, var, value) if not values_per_var[var] then values_per_var[var] = {} end table.insert(values_per_var[var], value) end -- Called when assignment of `value` is connected to an access. -- `item` contains the access, and `line` contains the item. local function add_resolution(line, item, var, value, is_mutation) register_value(item.used_values, var, value) value[is_mutation and "mutated" or "used"] = true value.using_lines[line] = true if value.secondaries then value.secondaries.used = true end end -- Connects accesses in given items array with an assignment of `value`. -- `items` may be `nil` instead of empty. local function add_resolutions(line, items, var, value, is_mutation) if not items then return end for _, item in ipairs(items) do add_resolution(line, item, var, value, is_mutation) end end -- Connects all accesses (and mutations) in `access_line` with corresponding -- assignments in `set_line`. local function cross_resolve_closures(access_line, set_line) for var, setting_items in pairs(set_line.set_upvalues) do for _, setting_item in ipairs(setting_items) do add_resolutions(access_line, access_line.accessed_upvalues[var], var, setting_item.set_variables[var]) add_resolutions(access_line, access_line.mutated_upvalues[var], var, setting_item.set_variables[var], true) end end end local function in_scope(var, index) return (var.scope_start <= index) and (index <= var.scope_end) end -- Called when main assignment propagation reaches a line item. local function main_assignment_propagation_callback(line, index, item, var, value) -- Check entrance condition. if not in_scope(var, index) then -- Assignment reaches the end of variable scope, so it can't be dominated by any assignment. value.overwriting_item = false return true end -- Assignment reaches this item, apply its effect. -- Accesses (and mutations) of the variable can resolve to reaching assignment. if item.accesses and item.accesses[var] then add_resolution(line, item, var, value) end if item.mutations and item.mutations[var] then add_resolution(line, item, var, value, true) end -- Accesses (and mutations) of the variable inside closures created in this item -- can resolve to reaching assignment. if item.lines then for _, created_line in ipairs(item.lines) do add_resolutions(created_line, created_line.accessed_upvalues[var], var, value) add_resolutions(created_line, created_line.mutated_upvalues[var], var, value, true) end end -- Check exit condition. if item.set_variables and item.set_variables[var] then if value.overwriting_item ~= false then if value.overwriting_item and value.overwriting_item ~= item then value.overwriting_item = false else value.overwriting_item = item end end return true end end -- Connects main assignments with main accesses and closure accesses in reachable closures. -- Additionally, sets `overwriting_item` field of values to an item with an assignment overwriting -- the value, but only if the overwriting is not avoidable (i.e. it's impossible to reach end of function -- from the first assignment without going through the second one). Otherwise value of the field may be -- `false` or `nil`. local function propagate_main_assignments(line) for i, item in ipairs(line.items) do if item.set_variables then for var, value in pairs(item.set_variables) do if var.line == line then -- Assignments are not live at their own item, because assignments take effect only after all accesses -- are evaluated. Items with assignments can't be jumps, so they have a single following item -- with incremented index. line:walk({}, i + 1, main_assignment_propagation_callback, var, value) end end end end end -- Called when closure creation propagation reaches a line item. local function closure_creation_propagation_callback(line, _, item, propagated_line) if not item then return true end -- Closure creation reaches this item, apply its effects. -- Accesses (and mutations) of upvalues in the propagated closure -- can resolve to assignments in the item. if item.set_variables then for var, value in pairs(item.set_variables) do add_resolutions(propagated_line, propagated_line.accessed_upvalues[var], var, value) add_resolutions(propagated_line, propagated_line.mutated_upvalues[var], var, value, true) end end if item.lines then for _, created_line in ipairs(item.lines) do -- Accesses (and mutations) of upvalues in the propagated closure -- can resolve to assignments in closures created in the item. cross_resolve_closures(propagated_line, created_line) -- Accesses (and mutations) of upvalues in closures created in the item -- can resolve to assignments in the propagated closure. cross_resolve_closures(created_line, propagated_line) end end -- Accesses (and mutations) of locals in the item can resolve -- to assignments in the propagated closure. for var, setting_items in pairs(propagated_line.set_upvalues) do if item.accesses and item.accesses[var] then for _, setting_item in ipairs(setting_items) do add_resolution(line, item, var, setting_item.set_variables[var]) end end if item.mutations and item.mutations[var] then for _, setting_item in ipairs(setting_items) do add_resolution(line, item, var, setting_item.set_variables[var], true) end end end end -- Connects main assignments with closure accesses in reaching closures. -- Connects closure assignments with main accesses and with closure accesses in reachable closures. -- Connects closure accesses with closure assignments in reachable closures. local function propagate_closure_creations(line) for i, item in ipairs(line.items) do if item.lines then for _, created_line in ipairs(item.lines) do -- Closures are live at the item they are created, as they can be called immediately. line:walk({}, i, closure_creation_propagation_callback, created_line) end end end end local function analyze_line(line) propagate_main_assignments(line) propagate_closure_creations(line) end -- Finds reaching assignments for all local variable accesses. function stage.run(chstate) for _, line in ipairs(chstate.lines) do analyze_line(line) end end return stage luacheck-0.25.0/src/luacheck/stages/unwrap_parens.lua000066400000000000000000000031111410451437000225730ustar00rootroot00000000000000local stage = {} -- Mutates an array of nodes and non-tables, unwrapping Paren nodes. -- If list_start is given, tail Paren is not unwrapped if it's unpacking and past list_start index. local function handle_nodes(nodes, list_start) local num_nodes = #nodes for index = 1, num_nodes do local node = nodes[index] if type(node) == "table" then local tag = node.tag if tag == "Table" or tag == "Return" then handle_nodes(node, 1) elseif tag == "Call" then handle_nodes(node, 2) elseif tag == "Invoke" then handle_nodes(node, 3) elseif tag == "Forin" then handle_nodes(node[2], 1) handle_nodes(node[3]) elseif tag == "Local" then if node[2] then handle_nodes(node[2]) end elseif tag == "Set" then handle_nodes(node[1]) handle_nodes(node[2], 1) else handle_nodes(node) if tag == "Paren" and (not list_start or index < list_start or index ~= num_nodes) then local inner_node = node[1] if inner_node.tag ~= "Call" and inner_node.tag ~= "Invoke" and inner_node.tag ~= "Dots" then nodes[index] = inner_node end end end end end end -- Mutates AST, unwrapping Paren nodes. -- Paren nodes are preserved only when they matter: -- at the ends of expression lists with potentially multi-value inner expressions. function stage.run(chstate) handle_nodes(chstate.ast) end return stage luacheck-0.25.0/src/luacheck/standards.lua000066400000000000000000000225351410451437000204170ustar00rootroot00000000000000local standards = {} -- A standard (aka std) defines set of allowed globals, their fields, -- and whether they are mutable. -- -- A standard can be in several formats. Internal (normalized) format -- is a tree. Each node defines a global or its field. Each node may have -- boolean `read_only` and `other_fields`, and may contain definitions -- of nested fields in `fields` subtable, which maps field names -- to definition tables. For example, standard defining globals -- of some Lua version may start like this: -- { -- -- Most globals are read-only by default. -- read_only = true, -- fields = { -- -- The tree can't be recursive, just allow everything for `_G`. -- _G = {other_fields = true, read_only = false}, -- package = { -- fields = { -- -- `other_fields` is false by default, so that an empty table -- -- defines a field that can't be indexed further (a function in this case). -- loadlib = {}, -- -- Allow doing everything with `package.loaded`. -- loaded = {other_fields = true, read_only = false}, -- -- More fields here... -- } -- }, -- -- More globals here... -- } -- } -- -- A similar format is used to define standards in table form -- in config. There are two differences: -- first, top level table can have two fields, `globals` and `read_globals`, -- that map global names to definition tables. Default value of `read_only` field -- for the these tables depends on which table they come from (`true` for `read_globals` -- and `false` for `globals`). Additionally, all tables that map field or global names -- to definition tables may have non-string keys, their associated values are interpreted -- as names instead and their definition table allows indexing with any keys indefinitely. -- E.g. `{fields = {"foo"}}` is equivalent to `{fields = {foo = {other_fields = true}}}`. -- This feature makes it easier to create less strict standards that do not care about fields, -- to ease migration from the old format. -- -- Additionally, there are some predefined named standards in `luacheck.builtin_standards` module. -- In config and inline options its possible to use their names as strings to refer to them. -- Validates an optional table mapping field names to field definitions or non-string keys to names. -- `index` is an optional string specifying position of the field table in the root table. -- Returns a true if the table is valid, false, an error message, and index of the table with the error otherwise. local function validate_fields(fields, is_root, index) if fields == nil then return true end local field_type = is_root and "global" or "field" if type(fields) ~= "table" then return false, ("%ss table expected, got %s"):format(field_type, type(fields)), index end for key, value in pairs(fields) do if type(key) == "string" then local new_index = (index or "") .. "." .. key if type(value) ~= "table" then return false, ("%s description table expected, got %s"):format(field_type, type(value)), new_index end if value.read_only ~= nil and type(value.read_only) ~= "boolean" then local err = "invalid value of option 'read_only': boolean expected, got " .. type(value.read_only) return false, err, new_index end if value.other_fields ~= nil and type(value.other_fields) ~= "boolean" then local err = "invalid value of option 'other_fields': boolean expected, got " .. type(value.other_fields) return false, err, new_index end local ok, err, err_index = validate_fields(value.fields, false, new_index .. ".fields") if not ok then return false, err, err_index end elseif type(value) ~= "string" then local key_as_string = type(key) == "number" and ("%.20g"):format(key) or ("<%s>"):format(type(key)) local new_index = ("%s[%s]"):format(index or "", key_as_string) return false, ("string expected as %s name, got %s"):format(field_type, type(value)), new_index end end return true end -- Validates a field table. -- Returns true if the table is valid, false and an error message otherwise. function standards.validate_globals_table(globals_table) local ok, err, err_index = validate_fields(globals_table, true) if ok then return true end local err_prefix = err_index and ("in field %s: "):format(err_index) or "" return false, err_prefix .. err end -- Validates an std table in user-side format. -- Returns true if the table is valid, false and an error message otherwise. function standards.validate_std_table(std_table) local ok, err, err_index = validate_fields(std_table.globals, true, ".globals") if ok then ok, err, err_index = validate_fields(std_table.read_globals, true, ".read_globals") end if ok then return true end local err_prefix = ("in field %s: "):format(err_index) return false, err_prefix .. err end local infinitely_indexable_def = {other_fields = true} local function add_fields(def, fields, overwrite, ignore_array_part, default_read_only) if not fields then return end for field_name, field_def in pairs(fields) do if type(field_name) == "string" or not ignore_array_part then if type(field_name) ~= "string" then field_name = field_def field_def = infinitely_indexable_def end if not def.fields then def.fields = {} end if not def.fields[field_name] then def.fields[field_name] = {} end local existing_field_def = def.fields[field_name] local new_read_only = field_def.read_only if new_read_only == nil then new_read_only = default_read_only end if new_read_only ~= nil then if overwrite or new_read_only == false then existing_field_def.read_only = new_read_only end end if field_def.other_fields ~= nil then if overwrite or field_def.other_fields == true then existing_field_def.other_fields = field_def.other_fields end end add_fields(existing_field_def, field_def.fields, overwrite, false, nil) end end end -- Merges in an std table in user-side format. -- By default the new state of normalized std is a union of the standard tables being merged, -- e.g. if either table allows some field to be mutated, result should allow it, too. -- If `overwrite` is truthy, read-only statuses from the new std table overwrite existing values. -- If `ignore_top_array_part` is truthy, non-string keys in `globals` and `read_globals` tables -- in `std_table` are not processed. function standards.add_std_table(final_std, std_table, overwrite, ignore_top_array_part) add_fields(final_std, std_table.globals, overwrite, ignore_top_array_part, false) add_fields(final_std, std_table.read_globals, overwrite, ignore_top_array_part, true) end -- Overwrites or adds definition of a field with given read-only status and any nested keys. -- Field is specified as an array of field names. function standards.overwrite_field(final_std, field_names, read_only) local field_def = final_std for _, field_name in ipairs(field_names) do if not field_def.fields then field_def.fields = {} end if not field_def.fields[field_name] then field_def.fields[field_name] = {read_only = read_only} end field_def = field_def.fields[field_name] end for key in pairs(field_def) do field_def[key] = nil end field_def.read_only = read_only field_def.other_fields = true end -- Removes definition of a field from a normalized std table. -- Field is specified as an array of field names. function standards.remove_field(final_std, field_names) local field_def = final_std local parent_def for _, field_name in ipairs(field_names) do parent_def = field_def if not field_def.fields or not field_def.fields[field_name] then -- The field wasn't defined in the first place. return end field_def = field_def.fields[field_name] end if parent_def then parent_def.fields[field_names[#field_names]] = nil end end local function infer_deep_read_only_statuses(def, read_only) local deep_read_only = not def.other_fields or read_only if def.fields then for _, field_def in pairs(def.fields) do local field_read_only = read_only if field_def.read_only ~= nil then field_read_only = field_def.read_only end infer_deep_read_only_statuses(field_def, field_read_only) deep_read_only = deep_read_only and field_read_only and field_def.deep_read_only end end if deep_read_only then def.deep_read_only = true end end -- Finishes building a normalized std tables. -- Adds `deep_read_only` fields with `true` value to definition tables -- that do not have any writable fields, recursively. function standards.finalize(final_std) infer_deep_read_only_statuses(final_std, true) end local empty = {} -- Returns a definition table containing empty fields with given names. function standards.def_fields(...) local fields = {} for _, field in ipairs({...}) do fields[field] = empty end return {fields = fields} end return standards luacheck-0.25.0/src/luacheck/unicode.lua000066400000000000000000000024151410451437000200550ustar00rootroot00000000000000local unicode_printability_boundaries = require "luacheck.unicode_printability_boundaries" local unicode = {} -- unicode_printability_boundaries is an array of first codepoints of -- each continuous block of codepoints that are all printable or all not printable. function unicode.is_printable(codepoint) -- Binary search for index of the first boundary less than or equal to given codepoint. local floor_boundary_index -- Target index is always in [begin_index..end_index). local begin_index = 1 local end_index = #unicode_printability_boundaries + 1 while end_index - begin_index > 1 do local mid_index = math.floor((begin_index + end_index) / 2) local mid_codepoint = unicode_printability_boundaries[mid_index] if codepoint < mid_codepoint then end_index = mid_index elseif codepoint > mid_codepoint then begin_index = mid_index else floor_boundary_index = mid_index break end end floor_boundary_index = floor_boundary_index or begin_index -- floor_boundary_index is the number of the block containing codepoint. -- Printable and not printable blocks alternate and the first one is not printable (zero is not printable). return floor_boundary_index % 2 == 0 end return unicode luacheck-0.25.0/src/luacheck/unicode_printability_boundaries.lua000066400000000000000000000170571410451437000250720ustar00rootroot00000000000000-- Autogenerated using data from https://www.unicode.org/Public/11.0.0/ucd/UnicodeData.txt return {0,32,127,160,173,174,888,890,896,900,907,908,909,910,930,931,1328,1329,1367,1369,1419,1421,1424,1425,1480,1488,1515,1519,1525,1542,1564,1566,1757,1758,1806,1808,1867,1869,1970,1984,2043,2045,2094,2096,2111,2112,2140,2142,2143,2144,2155,2208,2229,2230,2238,2259,2274,2275,2436,2437,2445,2447,2449,2451,2473,2474,2481,2482,2483,2486,2490,2492,2501,2503,2505,2507,2511,2519,2520,2524,2526,2527,2532,2534,2559,2561,2564,2565,2571,2575,2577,2579,2601,2602,2609,2610,2612,2613,2615,2616,2618,2620,2621,2622,2627,2631,2633,2635,2638,2641,2642,2649,2653,2654,2655,2662,2679,2689,2692,2693,2702,2703,2706,2707,2729,2730,2737,2738,2740,2741,2746,2748,2758,2759,2762,2763,2766,2768,2769,2784,2788,2790,2802,2809,2816,2817,2820,2821,2829,2831,2833,2835,2857,2858,2865,2866,2868,2869,2874,2876,2885,2887,2889,2891,2894,2902,2904,2908,2910,2911,2916,2918,2936,2946,2948,2949,2955,2958,2961,2962,2966,2969,2971,2972,2973,2974,2976,2979,2981,2984,2987,2990,3002,3006,3011,3014,3017,3018,3022,3024,3025,3031,3032,3046,3067,3072,3085,3086,3089,3090,3113,3114,3130,3133,3141,3142,3145,3146,3150,3157,3159,3160,3163,3168,3172,3174,3184,3192,3213,3214,3217,3218,3241,3242,3252,3253,3258,3260,3269,3270,3273,3274,3278,3285,3287,3294,3295,3296,3300,3302,3312,3313,3315,3328,3332,3333,3341,3342,3345,3346,3397,3398,3401,3402,3408,3412,3428,3430,3456,3458,3460,3461,3479,3482,3506,3507,3516,3517,3518,3520,3527,3530,3531,3535,3541,3542,3543,3544,3552,3558,3568,3570,3573,3585,3643,3647,3676,3713,3715,3716,3717,3719,3721,3722,3723,3725,3726,3732,3736,3737,3744,3745,3748,3749,3750,3751,3752,3754,3756,3757,3770,3771,3774,3776,3781,3782,3783,3784,3790,3792,3802,3804,3808,3840,3912,3913,3949,3953,3992,3993,4029,4030,4045,4046,4059,4096,4294,4295,4296,4301,4302,4304,4681,4682,4686,4688,4695,4696,4697,4698,4702,4704,4745,4746,4750,4752,4785,4786,4790,4792,4799,4800,4801,4802,4806,4808,4823,4824,4881,4882,4886,4888,4955,4957,4989,4992,5018,5024,5110,5112,5118,5120,5789,5792,5881,5888,5901,5902,5909,5920,5943,5952,5972,5984,5997,5998,6001,6002,6004,6016,6110,6112,6122,6128,6138,6144,6158,6160,6170,6176,6265,6272,6315,6320,6390,6400,6431,6432,6444,6448,6460,6464,6465,6468,6510,6512,6517,6528,6572,6576,6602,6608,6619,6622,6684,6686,6751,6752,6781,6783,6794,6800,6810,6816,6830,6832,6847,6912,6988,6992,7037,7040,7156,7164,7224,7227,7242,7245,7305,7312,7355,7357,7368,7376,7418,7424,7674,7675,7958,7960,7966,7968,8006,8008,8014,8016,8024,8025,8026,8027,8028,8029,8030,8031,8062,8064,8117,8118,8133,8134,8148,8150,8156,8157,8176,8178,8181,8182,8191,8192,8203,8208,8232,8239,8288,8304,8306,8308,8335,8336,8349,8352,8384,8400,8433,8448,8588,8592,9255,9280,9291,9312,11124,11126,11158,11160,11209,11210,11263,11264,11311,11312,11359,11360,11508,11513,11558,11559,11560,11565,11566,11568,11624,11631,11633,11647,11671,11680,11687,11688,11695,11696,11703,11704,11711,11712,11719,11720,11727,11728,11735,11736,11743,11744,11855,11904,11930,11931,12020,12032,12246,12272,12284,12288,12352,12353,12439,12441,12544,12549,12592,12593,12687,12688,12731,12736,12772,12784,12831,12832,13055,13056,19894,19904,40944,40960,42125,42128,42183,42192,42540,42560,42744,42752,42938,42999,43052,43056,43066,43072,43128,43136,43206,43214,43226,43232,43348,43359,43389,43392,43470,43471,43482,43486,43519,43520,43575,43584,43598,43600,43610,43612,43715,43739,43767,43777,43783,43785,43791,43793,43799,43808,43815,43816,43823,43824,43878,43888,44014,44016,44026,44032,55204,55216,55239,55243,55292,63744,64110,64112,64218,64256,64263,64275,64280,64285,64311,64312,64317,64318,64319,64320,64322,64323,64325,64326,64450,64467,64832,64848,64912,64914,64968,65008,65022,65024,65050,65056,65107,65108,65127,65128,65132,65136,65141,65142,65277,65281,65471,65474,65480,65482,65488,65490,65496,65498,65501,65504,65511,65512,65519,65532,65534,65536,65548,65549,65575,65576,65595,65596,65598,65599,65614,65616,65630,65664,65787,65792,65795,65799,65844,65847,65935,65936,65948,65952,65953,66000,66046,66176,66205,66208,66257,66272,66300,66304,66340,66349,66379,66384,66427,66432,66462,66463,66500,66504,66518,66560,66718,66720,66730,66736,66772,66776,66812,66816,66856,66864,66916,66927,66928,67072,67383,67392,67414,67424,67432,67584,67590,67592,67593,67594,67638,67639,67641,67644,67645,67647,67670,67671,67743,67751,67760,67808,67827,67828,67830,67835,67868,67871,67898,67903,67904,67968,68024,68028,68048,68050,68100,68101,68103,68108,68116,68117,68120,68121,68150,68152,68155,68159,68169,68176,68185,68192,68256,68288,68327,68331,68343,68352,68406,68409,68438,68440,68467,68472,68498,68505,68509,68521,68528,68608,68681,68736,68787,68800,68851,68858,68904,68912,68922,69216,69247,69376,69416,69424,69466,69632,69710,69714,69744,69759,69821,69822,69826,69840,69865,69872,69882,69888,69941,69942,69959,69968,70007,70016,70094,70096,70112,70113,70133,70144,70162,70163,70207,70272,70279,70280,70281,70282,70286,70287,70302,70303,70314,70320,70379,70384,70394,70400,70404,70405,70413,70415,70417,70419,70441,70442,70449,70450,70452,70453,70458,70459,70469,70471,70473,70475,70478,70480,70481,70487,70488,70493,70500,70502,70509,70512,70517,70656,70746,70747,70748,70749,70751,70784,70856,70864,70874,71040,71094,71096,71134,71168,71237,71248,71258,71264,71277,71296,71352,71360,71370,71424,71451,71453,71468,71472,71488,71680,71740,71840,71923,71935,71936,72192,72264,72272,72324,72326,72355,72384,72441,72704,72713,72714,72759,72760,72774,72784,72813,72816,72848,72850,72872,72873,72887,72960,72967,72968,72970,72971,73015,73018,73019,73020,73022,73023,73032,73040,73050,73056,73062,73063,73065,73066,73103,73104,73106,73107,73113,73120,73130,73440,73465,73728,74650,74752,74863,74864,74869,74880,75076,77824,78895,82944,83527,92160,92729,92736,92767,92768,92778,92782,92784,92880,92910,92912,92918,92928,92998,93008,93018,93019,93026,93027,93048,93053,93072,93760,93851,93952,94021,94032,94079,94095,94112,94176,94178,94208,100338,100352,101107,110592,110879,110960,111356,113664,113771,113776,113789,113792,113801,113808,113818,113820,113824,118784,119030,119040,119079,119081,119155,119163,119273,119296,119366,119520,119540,119552,119639,119648,119673,119808,119893,119894,119965,119966,119968,119970,119971,119973,119975,119977,119981,119982,119994,119995,119996,119997,120004,120005,120070,120071,120075,120077,120085,120086,120093,120094,120122,120123,120127,120128,120133,120134,120135,120138,120145,120146,120486,120488,120780,120782,121484,121499,121504,121505,121520,122880,122887,122888,122905,122907,122914,122915,122917,122918,122923,124928,125125,125127,125143,125184,125259,125264,125274,125278,125280,126065,126133,126464,126468,126469,126496,126497,126499,126500,126501,126503,126504,126505,126515,126516,126520,126521,126522,126523,126524,126530,126531,126535,126536,126537,126538,126539,126540,126541,126544,126545,126547,126548,126549,126551,126552,126553,126554,126555,126556,126557,126558,126559,126560,126561,126563,126564,126565,126567,126571,126572,126579,126580,126584,126585,126589,126590,126591,126592,126602,126603,126620,126625,126628,126629,126634,126635,126652,126704,126706,126976,127020,127024,127124,127136,127151,127153,127168,127169,127184,127185,127222,127232,127245,127248,127340,127344,127405,127462,127491,127504,127548,127552,127561,127568,127570,127584,127590,127744,128725,128736,128749,128752,128762,128768,128884,128896,128985,129024,129036,129040,129096,129104,129114,129120,129160,129168,129198,129280,129292,129296,129343,129344,129393,129395,129399,129402,129403,129404,129443,129456,129466,129472,129475,129488,129536,129632,129646,131072,173783,173824,177973,177984,178206,178208,183970,183984,191457,194560,195102,917760,918000,} luacheck-0.25.0/src/luacheck/utils.lua000066400000000000000000000201761410451437000175730ustar00rootroot00000000000000-- luacheck: push compat local unpack = table.unpack or unpack local pack = table.pack or function(...) return {n = select("#", ...), ...} end -- luacheck: pop local utils = {} utils.dir_sep = package.config:sub(1,1) utils.is_windows = utils.dir_sep == "\\" local bom = "\239\187\191" -- Returns all contents of file (path or file handler) or nil + error message. function utils.read_file(file) local handler if type(file) == "string" then local open_err handler, open_err = io.open(file, "rb") if not handler then open_err = utils.unprefix(open_err, file .. ": ") return nil, "couldn't read: " .. open_err end else handler = file end local res, read_err = handler:read("*a") handler:close() if not res then return nil, "couldn't read: " .. read_err end -- Use :len() instead of # operator because in some environments -- string library is patched to handle UTF. if res:sub(1, bom:len()) == bom then res = res:sub(bom:len() + 1) end return res end -- luacheck: push -- luacheck: compat if _VERSION:find "5.1" then -- Loads Lua source string in an environment, returns function or nil, error. function utils.load(src, env, chunkname) local func, err = loadstring(src, chunkname) if func then if env then setfenv(func, env) end return func else return nil, err end end else -- Loads Lua source string in an environment, returns function or nil, error. function utils.load(src, env, chunkname) return load(src, chunkname, "t", env or _ENV) end end -- luacheck: pop -- Loads config containing assignments to global variables from path. -- Returns config table and return value of config or nil and error type -- ("I/O" or "syntax" or "runtime") and error message. function utils.load_config(path, env) env = env or {} local src, read_err = utils.read_file(path) if not src then return nil, "I/O", read_err end local func, load_err = utils.load(src, env, "chunk") if not func then return nil, "syntax", "line " .. utils.unprefix(load_err, "[string \"chunk\"]:") end local ok, res = pcall(func) if not ok then return nil, "runtime", "line " .. utils.unprefix(res, "[string \"chunk\"]:") end return env, res end function utils.array_to_set(array) local set = {} for index, value in ipairs(array) do set[value] = index end return set end function utils.concat_arrays(array) local res = {} for _, subarray in ipairs(array) do for _, item in ipairs(subarray) do table.insert(res, item) end end return res end function utils.update(t1, t2) for k, v in pairs(t2) do t1[k] = v end return t1 end local class_metatable = {} function class_metatable.__call(class, ...) local obj = setmetatable({}, class) if class.__init then local init_returns = pack(class.__init(obj, ...)) if init_returns.n > 0 then return unpack(init_returns, 1, init_returns.n) end end return obj end function utils.class() local class = setmetatable({}, class_metatable) class.__index = class return class end function utils.is_instance(object, class) return rawequal(debug.getmetatable(object), class) end utils.Stack = utils.class() function utils.Stack:__init() self.size = 0 end function utils.Stack:push(value) self.size = self.size + 1 self[self.size] = value self.top = value end function utils.Stack:pop() local value = self[self.size] self[self.size] = nil self.size = self.size - 1 self.top = self[self.size] return value end local ErrorWrapper = utils.class() function ErrorWrapper:__init(err, traceback) self.err = err self.traceback = traceback end function ErrorWrapper:__tostring() return tostring(self.err) .. "\n" .. self.traceback end local function error_handler(err) if utils.is_instance(err, ErrorWrapper) then return err else return ErrorWrapper(err, debug.traceback()) end end -- Like pcall, but wraps errors in {err = err, traceback = traceback} -- tables unless already wrapped. function utils.try(f, ...) local args = {...} local num_args = select("#", ...) local function task() return f(unpack(args, 1, num_args)) end return xpcall(task, error_handler) end local function ripairs_iterator(array, i) if i == 1 then return nil else i = i - 1 return i, array[i] end end function utils.ripairs(array) return ripairs_iterator, array, #array + 1 end function utils.sorted_pairs(t) local keys = {} for key in pairs(t) do table.insert(keys, key) end table.sort(keys) local index = 1 return function() local key = keys[index] if key == nil then return end index = index + 1 return key, t[key] end end function utils.unprefix(str, prefix) if str:sub(1, #prefix) == prefix then return str:sub(#prefix + 1) else return str end end function utils.after(str, pattern) local _, last_matched_index = str:find(pattern) if last_matched_index then return str:sub(last_matched_index + 1) end end function utils.strip(str) local _, last_start_space = str:find("^%s*") local first_end_space = str:find("%s*$") return str:sub(last_start_space + 1, first_end_space - 1) end -- `sep` must be nil or a single character. Behaves like python's `str.split`. function utils.split(str, sep) local parts = {} local pattern if sep then pattern = sep .. "([^" .. sep .. "]*)" str = sep .. str else pattern = "%S+" end for part in str:gmatch(pattern) do table.insert(parts, part) end return parts end utils.InvalidPatternError = utils.class() function utils.InvalidPatternError:__init(err, pattern) self.err = err self.pattern = pattern end function utils.InvalidPatternError:__tostring() return self.err end -- Behaves like string.match, except it normally returns boolean and -- throws an instance of utils.InvalidPatternError on invalid pattern. -- The error object turns into original error when tostring is used on it, -- to ensure behaviour is predictable when luacheck is used as a module. function utils.pmatch(str, pattern) assert(type(str) == "string") assert(type(pattern) == "string") local ok, res = pcall(string.match, str, pattern) if not ok then error(utils.InvalidPatternError(res, pattern), 0) else return not not res end end -- Maps func over array. function utils.map(func, array) local res = {} for i, item in ipairs(array) do res[i] = func(item) end return res end -- Returns validator checking type. function utils.has_type(type_) return function(x) if type(x) == type_ then return true else return false, ("%s expected, got %s"):format(type_, type(x)) end end end -- Returns validator checking type and allowing false. function utils.has_type_or_false(type_) return function(x) if type(x) == type_ then return true elseif type(x) == "boolean" then if x then return false, ("%s or false expected, got true"):format(type_) else return true end else return false, ("%s or false expected, got %s"):format(type_, type(x)) end end end -- Returns validator checking two type possibilities. function utils.has_either_type(type1, type2) return function(x) if type(x) == type1 or type(x) == type2 then return true else return false, ("%s or %s expected, got %s"):format(type1, type2, type(x)) end end end -- Returns validator checking that value is an array with elements of type. function utils.array_of(type_) return function(x) if type(x) ~= "table" then return false, ("array of %ss expected, got %s"):format(type_, type(x)) end for index, item in ipairs(x) do if type(item) ~= type_ then return false, ("array of %ss expected, got %s at index [%d]"):format(type_, type(item), index) end end return true end end return utils luacheck-0.25.0/src/luacheck/vendor/000077500000000000000000000000001410451437000172175ustar00rootroot00000000000000luacheck-0.25.0/src/luacheck/vendor/sha1/000077500000000000000000000000001410451437000200535ustar00rootroot00000000000000luacheck-0.25.0/src/luacheck/vendor/sha1/LICENSE000066400000000000000000000021521410451437000210600ustar00rootroot00000000000000Copyright (c) 2013 Enrique García Cota, Eike Decker, Jeffrey Friedl Copyright (c) 2018 Peter Melnichenko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. luacheck-0.25.0/src/luacheck/vendor/sha1/bit32_ops.lua000066400000000000000000000010711410451437000223610ustar00rootroot00000000000000local bit32 = require "bit32" local ops = {} local band = bit32.band local bor = bit32.bor local bxor = bit32.bxor ops.uint32_lrot = bit32.lrotate ops.byte_xor = bxor ops.uint32_xor_3 = bxor ops.uint32_xor_4 = bxor function ops.uint32_ternary(a, b, c) -- c ~ (a & (b ~ c)) has less bitwise operations than (a & b) | (~a & c). return bxor(c, band(a, bxor(b, c))) end function ops.uint32_majority(a, b, c) -- (a & (b | c)) | (b & c) has less bitwise operations than (a & b) | (a & c) | (b & c). return bor(band(a, bor(b, c)), band(b, c)) end return ops luacheck-0.25.0/src/luacheck/vendor/sha1/bit_ops.lua000066400000000000000000000010511410451437000222120ustar00rootroot00000000000000local bit = require "bit" local ops = {} local band = bit.band local bor = bit.bor local bxor = bit.bxor ops.uint32_lrot = bit.rol ops.byte_xor = bxor ops.uint32_xor_3 = bxor ops.uint32_xor_4 = bxor function ops.uint32_ternary(a, b, c) -- c ~ (a & (b ~ c)) has less bitwise operations than (a & b) | (~a & c). return bxor(c, band(a, bxor(b, c))) end function ops.uint32_majority(a, b, c) -- (a & (b | c)) | (b & c) has less bitwise operations than (a & b) | (a & c) | (b & c). return bor(band(a, bor(b, c)), band(b, c)) end return ops luacheck-0.25.0/src/luacheck/vendor/sha1/common.lua000066400000000000000000000006531410451437000220520ustar00rootroot00000000000000local common = {} -- Merges four bytes into a uint32 number. function common.bytes_to_uint32(a, b, c, d) return a * 0x1000000 + b * 0x10000 + c * 0x100 + d end -- Splits a uint32 number into four bytes. function common.uint32_to_bytes(a) local a4 = a % 256 a = (a - a4) / 256 local a3 = a % 256 a = (a - a3) / 256 local a2 = a % 256 local a1 = (a - a2) / 256 return a1, a2, a3, a4 end return common luacheck-0.25.0/src/luacheck/vendor/sha1/init.lua000066400000000000000000000135151410451437000215260ustar00rootroot00000000000000local common = require "luacheck.vendor.sha1.common" local sha1 = { -- Meta fields retained for compatibility. _VERSION = "sha.lua 0.6.0", _URL = "https://github.com/mpeterv/sha1", _DESCRIPTION = [[ SHA-1 secure hash and HMAC-SHA1 signature computation in Lua, using bit and bit32 modules and Lua 5.3 operators when available and falling back to a pure Lua implementation on Lua 5.1. Based on code orignally by Jeffrey Friedl and modified by Eike Decker and Enrique García Cota.]], _LICENSE = [[ MIT LICENSE Copyright (c) 2013 Enrique García Cota, Eike Decker, Jeffrey Friedl Copyright (c) 2018 Peter Melnichenko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.]] } sha1.version = "0.6.0" local function choose_ops() if _VERSION:find("5%.3") then return "lua53_ops" elseif pcall(require, "bit") then return "bit_ops" elseif pcall(require, "bit32") then return "bit32_ops" else return "pure_lua_ops" end end local ops = require("luacheck.vendor.sha1." .. choose_ops()) local uint32_lrot = ops.uint32_lrot local byte_xor = ops.byte_xor local uint32_xor_3 = ops.uint32_xor_3 local uint32_xor_4 = ops.uint32_xor_4 local uint32_ternary = ops.uint32_ternary local uint32_majority = ops.uint32_majority local bytes_to_uint32 = common.bytes_to_uint32 local uint32_to_bytes = common.uint32_to_bytes local sbyte = string.byte local schar = string.char local sformat = string.format local srep = string.rep local function hex_to_binary(hex) return (hex:gsub("..", function(hexval) return schar(tonumber(hexval, 16)) end)) end -- Calculates SHA1 for a string, returns it encoded as 40 hexadecimal digits. function sha1.sha1(str) -- Input preprocessing. -- First, append a `1` bit and seven `0` bits. local first_append = schar(0x80) -- Next, append some zero bytes to make the length of the final message a multiple of 64. -- Eight more bytes will be added next. local non_zero_message_bytes = #str + 1 + 8 local second_append = srep(schar(0), -non_zero_message_bytes % 64) -- Finally, append the length of the original message in bits as a 64-bit number. -- Assume that it fits into the lower 32 bits. local third_append = schar(0, 0, 0, 0, uint32_to_bytes(#str * 8)) str = str .. first_append .. second_append .. third_append assert(#str % 64 == 0) -- Initialize hash value. local h0 = 0x67452301 local h1 = 0xEFCDAB89 local h2 = 0x98BADCFE local h3 = 0x10325476 local h4 = 0xC3D2E1F0 local w = {} -- Process the input in successive 64-byte chunks. for chunk_start = 1, #str, 64 do -- Load the chunk into W[0..15] as uint32 numbers. local uint32_start = chunk_start for i = 0, 15 do w[i] = bytes_to_uint32(sbyte(str, uint32_start, uint32_start + 3)) uint32_start = uint32_start + 4 end -- Extend the input vector. for i = 16, 79 do w[i] = uint32_lrot(uint32_xor_4(w[i - 3], w[i - 8], w[i - 14], w[i - 16]), 1) end -- Initialize hash value for this chunk. local a = h0 local b = h1 local c = h2 local d = h3 local e = h4 -- Main loop. for i = 0, 79 do local f local k if i <= 19 then f = uint32_ternary(b, c, d) k = 0x5A827999 elseif i <= 39 then f = uint32_xor_3(b, c, d) k = 0x6ED9EBA1 elseif i <= 59 then f = uint32_majority(b, c, d) k = 0x8F1BBCDC else f = uint32_xor_3(b, c, d) k = 0xCA62C1D6 end local temp = (uint32_lrot(a, 5) + f + e + k + w[i]) % 0x100000000 e = d d = c c = uint32_lrot(b, 30) b = a a = temp end -- Add this chunk's hash to result so far. h0 = (h0 + a) % 0x100000000 h1 = (h1 + b) % 0x100000000 h2 = (h2 + c) % 0x100000000 h3 = (h3 + d) % 0x100000000 h4 = (h4 + e) % 0x100000000 end return sformat("%08x%08x%08x%08x%08x", h0, h1, h2, h3, h4) end function sha1.binary(str) return hex_to_binary(sha1.sha1(str)) end -- Precalculate replacement tables. local xor_with_0x5c = {} local xor_with_0x36 = {} for i = 0, 0xff do xor_with_0x5c[schar(i)] = schar(byte_xor(0x5c, i)) xor_with_0x36[schar(i)] = schar(byte_xor(0x36, i)) end -- 512 bits. local BLOCK_SIZE = 64 function sha1.hmac(key, text) if #key > BLOCK_SIZE then key = sha1.binary(key) end local key_xord_with_0x36 = key:gsub('.', xor_with_0x36) .. srep(schar(0x36), BLOCK_SIZE - #key) local key_xord_with_0x5c = key:gsub('.', xor_with_0x5c) .. srep(schar(0x5c), BLOCK_SIZE - #key) return sha1.sha1(key_xord_with_0x5c .. sha1.binary(key_xord_with_0x36 .. text)) end function sha1.hmac_binary(key, text) return hex_to_binary(sha1.hmac(key, text)) end setmetatable(sha1, {__call = function(_, str) return sha1.sha1(str) end}) return sha1 luacheck-0.25.0/src/luacheck/vendor/sha1/lua53_ops.lua000066400000000000000000000011501410451437000223650ustar00rootroot00000000000000local ops = {} function ops.uint32_lrot(a, bits) return ((a << bits) & 0xFFFFFFFF) | (a >> (32 - bits)) end function ops.byte_xor(a, b) return a ~ b end function ops.uint32_xor_3(a, b, c) return a ~ b ~ c end function ops.uint32_xor_4(a, b, c, d) return a ~ b ~ c ~ d end function ops.uint32_ternary(a, b, c) -- c ~ (a & (b ~ c)) has less bitwise operations than (a & b) | (~a & c). return c ~ (a & (b ~ c)) end function ops.uint32_majority(a, b, c) -- (a & (b | c)) | (b & c) has less bitwise operations than (a & b) | (a & c) | (b & c). return (a & (b | c)) | (b & c) end return ops luacheck-0.25.0/src/luacheck/vendor/sha1/pure_lua_ops.lua000066400000000000000000000132311410451437000232530ustar00rootroot00000000000000local common = require "luacheck.vendor.sha1.common" local ops = {} local bytes_to_uint32 = common.bytes_to_uint32 local uint32_to_bytes = common.uint32_to_bytes function ops.uint32_lrot(a, bits) local power = 2 ^ bits local inv_power = 0x100000000 / power local lower_bits = a % inv_power return (lower_bits * power) + ((a - lower_bits) / inv_power) end -- Build caches for bitwise `and` and `xor` over bytes to speed up uint32 operations. -- Building the cache by simply applying these operators over all pairs is too slow and -- duplicates a lot of work over different bits of inputs. -- Instead, when building a cache over bytes, for each pair of bytes split both arguments -- into two 4-bit numbers, calculate values over these two halves, then join the results into a byte again. -- While there are 256 * 256 = 65536 pairs of bytes, there are only 16 * 16 = 256 pairs -- of 4-bit numbers, so that building an 8-bit cache given a 4-bit cache is rather efficient. -- The same logic is applied recursively to make a 4-bit cache from a 2-bit cache and a 2-bit -- cache from a 1-bit cache, which is calculated given the 1-bit version of the operator. -- Returns a cache containing all values of a bitwise operator over numbers with given number of bits, -- given an operator over single bits. -- Value of `op(a, b)` is stored in `cache[a * (2 ^ bits) + b]`. local function make_op_cache(bit_op, bits) if bits == 1 then return {[0] = bit_op(0, 0), bit_op(0, 1), bit_op(1, 0), bit_op(1, 1)} end local half_bits = bits / 2 local size = 2 ^ bits local half_size = 2 ^ half_bits local half_cache = make_op_cache(bit_op, half_bits) local cache = {} -- The implementation used is an optimized version of the following reference one, -- with intermediate calculations reused and moved to the outermost loop possible. -- It's possible to reorder the loops and move the calculation of one of the -- half-results one level up, but then the cache is not filled in a proper array order -- and its access performance suffers. -- for a1 = 0, half_size - 1 do -- for a2 = 0, half_size - 1 do -- for b1 = 0, half_size - 1 do -- for b2 = 0, half_size - 1 do -- local a = a1 * half_size + a2 -- local b = b1 * half_size + b2 -- local v1 = half_cache[a1 * half_size + b1] -- local v2 = half_cache[a2 * half_size + b2] -- local v = v1 * half_size + v2 -- cache[a * size + b] = v -- end -- end -- end -- end for a1 = 0, half_size - 1 do local a1_half_size = a1 * half_size for a2 = 0, half_size - 1 do local a2_size = a2 * half_size local a_size = (a1_half_size + a2) * size for b1 = 0, half_size - 1 do local a_size_plus_b1_half_size = a_size + b1 * half_size local v1_half_size = half_cache[a1_half_size + b1] * half_size for b2 = 0, half_size - 1 do cache[a_size_plus_b1_half_size + b2] = v1_half_size + half_cache[a2_size + b2] end end end end return cache end local byte_and_cache = make_op_cache(function(a, b) return a * b end, 8) local byte_xor_cache = make_op_cache(function(a, b) return a == b and 0 or 1 end, 8) function ops.byte_xor(a, b) return byte_xor_cache[a * 256 + b] end function ops.uint32_xor_3(a, b, c) local a1, a2, a3, a4 = uint32_to_bytes(a) local b1, b2, b3, b4 = uint32_to_bytes(b) local c1, c2, c3, c4 = uint32_to_bytes(c) return bytes_to_uint32( byte_xor_cache[a1 * 256 + byte_xor_cache[b1 * 256 + c1]], byte_xor_cache[a2 * 256 + byte_xor_cache[b2 * 256 + c2]], byte_xor_cache[a3 * 256 + byte_xor_cache[b3 * 256 + c3]], byte_xor_cache[a4 * 256 + byte_xor_cache[b4 * 256 + c4]] ) end function ops.uint32_xor_4(a, b, c, d) local a1, a2, a3, a4 = uint32_to_bytes(a) local b1, b2, b3, b4 = uint32_to_bytes(b) local c1, c2, c3, c4 = uint32_to_bytes(c) local d1, d2, d3, d4 = uint32_to_bytes(d) return bytes_to_uint32( byte_xor_cache[a1 * 256 + byte_xor_cache[b1 * 256 + byte_xor_cache[c1 * 256 + d1]]], byte_xor_cache[a2 * 256 + byte_xor_cache[b2 * 256 + byte_xor_cache[c2 * 256 + d2]]], byte_xor_cache[a3 * 256 + byte_xor_cache[b3 * 256 + byte_xor_cache[c3 * 256 + d3]]], byte_xor_cache[a4 * 256 + byte_xor_cache[b4 * 256 + byte_xor_cache[c4 * 256 + d4]]] ) end function ops.uint32_ternary(a, b, c) local a1, a2, a3, a4 = uint32_to_bytes(a) local b1, b2, b3, b4 = uint32_to_bytes(b) local c1, c2, c3, c4 = uint32_to_bytes(c) -- (a & b) + (~a & c) has less bitwise operations than (a & b) | (~a & c). return bytes_to_uint32( byte_and_cache[b1 * 256 + a1] + byte_and_cache[c1 * 256 + 255 - a1], byte_and_cache[b2 * 256 + a2] + byte_and_cache[c2 * 256 + 255 - a2], byte_and_cache[b3 * 256 + a3] + byte_and_cache[c3 * 256 + 255 - a3], byte_and_cache[b4 * 256 + a4] + byte_and_cache[c4 * 256 + 255 - a4] ) end function ops.uint32_majority(a, b, c) local a1, a2, a3, a4 = uint32_to_bytes(a) local b1, b2, b3, b4 = uint32_to_bytes(b) local c1, c2, c3, c4 = uint32_to_bytes(c) -- (a & b) + (c & (a ~ b)) has less bitwise operations than (a & b) | (a & c) | (b & c). return bytes_to_uint32( byte_and_cache[a1 * 256 + b1] + byte_and_cache[c1 * 256 + byte_xor_cache[a1 * 256 + b1]], byte_and_cache[a2 * 256 + b2] + byte_and_cache[c2 * 256 + byte_xor_cache[a2 * 256 + b2]], byte_and_cache[a3 * 256 + b3] + byte_and_cache[c3 * 256 + byte_xor_cache[a3 * 256 + b3]], byte_and_cache[a4 * 256 + b4] + byte_and_cache[c4 * 256 + byte_xor_cache[a4 * 256 + b4]] ) end return ops luacheck-0.25.0/src/luacheck/version.lua000066400000000000000000000015211410451437000201110ustar00rootroot00000000000000local argparse = require "argparse" local lfs = require "lfs" local luacheck = require "luacheck" local multithreading = require "luacheck.multithreading" local utils = require "luacheck.utils" local version = {} version.luacheck = luacheck._VERSION if rawget(_G, "jit") then version.lua = rawget(_G, "jit").version elseif _VERSION:find("^Lua ") then version.lua = "PUC-Rio " .. _VERSION else version.lua = _VERSION end version.argparse = argparse.version version.lfs = utils.unprefix(lfs._VERSION, "LuaFileSystem ") if multithreading.has_lanes then version.lanes = multithreading.lanes.ABOUT.version else version.lanes = "Not found" end version.string = ([[ Luacheck: %s Lua: %s Argparse: %s LuaFileSystem: %s LuaLanes: %s]]):format(version.luacheck, version.lua, version.argparse, version.lfs, version.lanes) return version