https://github.com/chrsm/yuecheck
yuescript linter, formatter, and ast library
https://github.com/chrsm/yuecheck
formatter linter yue yuescript
Last synced: 4 months ago
JSON representation
yuescript linter, formatter, and ast library
- Host: GitHub
- URL: https://github.com/chrsm/yuecheck
- Owner: chrsm
- License: mit
- Created: 2025-08-23T23:49:57.000Z (10 months ago)
- Default Branch: mother
- Last Pushed: 2026-02-13T20:33:27.000Z (4 months ago)
- Last Synced: 2026-02-14T03:35:44.539Z (4 months ago)
- Topics: formatter, linter, yue, yuescript
- Language: MoonScript
- Homepage:
- Size: 169 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-yue - yuecheck - linter and formatter for YueScript (Tools / emacs)
README
yuecheck
===
Tools for writing [Yuescript][1].
- linter (`yuecheck`)
- formatter (`yuefmt`)
`yuecheck` came about due to the fact that I love writing code in Yuescript, but
there aren't really any tools for it. It started as a standard linter I am attempting
to expand that to a formatter (`yuefmt`).
`yuefmt`, much like `yuecheck`, is based on my opinions of how code should be
written and formatted. `yuecheck` is configurable at this point but `yuefmt` lacks
that. There may be things I will make configurable but ultimately it was a huge
PITA to put together. If you have any suggestions, please feel free to file an
issue.
----
Current TODOs:
- [ ] General cleanup. Rules are ugly for implementation
* [x] categorized, but still more to do
- [ ] More fleshed out LSP implementation. Unlikely this will be very advanced,
and less likely will it work with non-Yue code. Possibly could redirect to
luals.
* [installation](#installation)
* [yuecheck usage](#usage-of-yuecheck)
* [yuefmt usage](#usage-of-yuefmt)
* [configuration](#configuration)
* [enabling and disabling checks](#enabling-and-disabling-checks)
* [built-in lint rules](#built-in-linters)
* [writing custom rules](#writing-custom-rules)
* [contributing](#contributing)
* [example neovim setup](#example-neovim-setup)
* [changelog](#changelog)
# installation
There is a rockspec, but yuecheck is not yet available on luarocks. It is the
recommended way to install, however, to provide the `yuecheck` command.
```bash
git clone git@github.com:chrsm/yuecheck.git
cd yuecheck
luarocks make
# or luarocks --local make
```
# usage of yuecheck
To lint a specific file or directory, pass it as an argument to `yuecheck`,
ex `yuecheck .` (all yue files in cwd) or `yuecheck specific.yue` for a single file.
See below for other options.
```
Usage: yuecheck [-h] [--ignore-config] [-j]
([-d ] | [-f ] | [] | [--stdin])
[-x [] ...]
Options:
-h, --help Show this help message and exit.
-d ,
--directory
directory containing files to check
-f , specific file to check
--file
--stdin parse stdin
--ignore-config ignore repo or user config
-j, --json output as json
-x [] ...,
--exclude [] ...
pattern for paths to exclude
```
Simple example: `sample.yue`
```moonscript
x = "abcd"
```
```
$ yuecheck sample.yue
sample.yue:1:3: double-quoted string where single-quoted would suffice
1 issues
```
# usage of yuefmt
To format a specific file, pass it as an argument to `yuefmt`, eg `yuefmt file.yue`.
You can also pass via `-f file.yue` or pump to `yuefmt` via stdin (`--stdin`).
See below for other options.
```
Usage: yuefmt [-h] [-w] ([-f ] | [] | [--stdin])
[-x [] ...]
tool for formatting yue code
Arguments:
dir_or_file
Options:
-h, --help Show this help message and exit.
-f , file to format
--file
--stdin parse stdin
-x [] ...,
--exclude [] ...
pattern for paths to exclude
-w, --write write changes to disk
see https://github.com/chrsm/yuecheck
```
## example neovim setup
If you're a neovim user, you can refer to my dotfiles for how I hooked this up via [nvim-lint][2]:
[nvim_lint config][3].
# configuration
All rules are enabled by default. For further customization, it is recommended
to write a `.yuecheck` file with your desired settings. This file can be placed
in two locations:
- `(root dir with .git)/.yuecheck`
* TODO: make this so that yuecheck must trust it first, since it can run code.
- `$HOME/.yuecheck`
Repo-specific configuration files take precedence over the user-level file.
This file is a yue script itself, so you can write it however you prefer, so long
as it returns a table matching the spec below. Note that this file is not
required. Check the repo's `.yuecheck` file for a full example.
```moonscript
export default {
-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-- NOTE! Set this to true only if you trust the code you're linting.
-- yuecheck currently doesn't certain things (const checks), and uses
-- the yue compiler to try to check for additional errors.
--
-- the code itself is not executed, but macros are run by the compiler,
-- and therefore side effects can occur.
--
-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
enable_compilation: false
-- if a rule is missing or set to false, it is disabled.
enabled_rules:
-- by default, all rules are set to enabled at runtime.
cond_identical_exprs: true
cond_impossible: true
unreachable: true
basic_nilness: true
global_writes: true
stdlib_usage: true
stdlib_match: true
style_comment_space: true
style_zero_index: true
style_import_as_ident: true
style_unnecessary_doublestring: true
style_conditionals: true
style_simplify_if_to_switch: true
style_discourage_require: true
style_discourage_unnecessary_sb: true
-- per-rule configuration, where possible
rules:
global_writes:
patterns: [ '^_ENV', '^package' ]
}
```
Another example - adding more checks to `stdlib_usage`.
```moonscript
import 'yuecheck.rules'
custom_rules =
stdlib_usage:
definitions:
love:
filesystem:
append:
args:
* name: 'name', optional: false
* name: 'data', optional: false
* name: 'size', optional: true
-- if you want to keep the current definitions, merge
for k, v in pairs rules.stdlib_usage.config.definitions
custom_rules.stdlib_usage.definitions[k] = v
cfg =
rules: custom_rules
enabled_rules:
stdlib_usage: true
export default cfg
```
## enabling and disabling checks
### per project
Configuration can be set up at the root of the repository. The config is Yuescript,
so you can write it however you want. Some rules allow additional configuration.
Example below.
```moonscript
export default {
enabled_rules:
stdlib_usage: true
global_writes: false
cond_identical_exprs: true
cond_impossible: true
style_comment_space: true
rules:
global_writes:
patterns: { '^_ENV', '^package' }
}
```
### per file
Linters can be disabled by having a comment at the top of the file, even if
the config file or command line arguments enable them.
```moonscript
-- yuecheck:ignore=stdlib_usage,smell_global_writes
_G['a'] = 'ignored'
os.clock 'ignored'
```
```moonscript
-- yuecheck:ignore
_G['is also'] = 'acceptable'
```
### per line
```moonscript
_G['a'] = 'ignored' -- yuecheck:ignore
os.clock 'not ignored'
```
# built-in lint rules
More to come, most likely. Most of these are style/preference, or ahead-of-time checks
before running.
| Rule | Description |
| :---- | ----------- |
| style_comment_space | Comments must have a space between `--` or `--[[` and content |
| style_zero_index | Discourages use of t[0], as tables usually start at 1; Using 0 is likely unintentional. |
| style_import_as_ident | Warns on redundant import name, i.e. `import 'x' as x` |
| style_unnecessary_doublestring | Warns on unnecessary use of `"` for strings where `'` is fine |
| style_discourage_require | Discourages use of `require` in favor of `import` |
| style_discourage_unnecessary_sb | Discourages use of square brackets in table literals, eg `{ ['a']: true }` can be `{ a: true }` |
| ... | ... |
| style_conditionals | Warns on odd conditionals, eg `if true`, `unless false` always execute |
| style_simplify_if_switch | Warns on chain if-elseif that could be switch instead |
| ... | ... |
| stdlib_usage | Finds issues with standard library functions. Configurable. |
| stdlib_match | Finds issues with patterns supplied to string.(match,find,gmatch,gsub) and capture groups |
| ... | ... |
| global_writes | Warns on writes to global variables (`_G`, `_ENV`, etc). Configurable. |
| ... | ... |
| cond_identical_exprs | Warns on conditions that compare a value to itself |
| cond_impossible | Warns on some types of impossible conditions |
| unreachable | Warns on code that is unreachable |
| basic_nilness | Warns on certain types of nil checks that aren't necessary |
| nil_comparisons | Warns about syntax errors from conditions against nil |
# writing custom rules
Custom checks can be added to the linter very easily. The best way to add one
is by adding it to your `.yuecheck` file.
```moonscript
import 'yuecheck.types'
import 'yuecheck.linter'
config =
enabled_rules:
my_check: true
my_check =
type: 'wat'
name: 'my_check'
on:
* types.Value
check: (node) ->
unless some_condition node
return
{
type: 'WARN'
message: "value found"
line: node\g_src_line!
col: node\g_src_col!
}
linter.define_rule my_check
export default config
```
Return values from a check should be:
```moonscript
{
type: string HINT|INFO|WARN|ERROR
message: string
line: int
col: int
}
-- or a table of tables
{
result1, result2, ...
}
```
While `type` string value is not enforced, it is useful to be consistent for
integrations with other tools.
NOTE:
Currently there is some work in making rules easier to declare, such that
if you are looking for specific things, there will be an easier way to
retrieve information than looking at the full node tree everywhere.
You can look at `src/yuecheck/rules.yue` `stdlib_match`, which uses the premade
`FuncRule`.
# important notes
## AST simplification
`yue.to_ast` even with flatten=0 does simplify some parts, requiring specific
handling within `linter.build_ast`.
- `TableLit` with empty `.values` is returned with `{ }`
- `FnArgDefList` will have an empty '' when `f = () ->` is used instead of `f = ->`
- `Return` will have a 'return' when `f = -> return` (vs `f = ->`)
- `DefaultValue` will have '' sometimes (I didn't document why, sorry!)
- `DoubleString` sometimes has '' instead of `DoubleString>DoubleStringContent>DoubleStringInner ''`
- `x^2` `^` is lost, resulting in only `Callable + Value`, unlike other operators
- `r[#r]` is returned as literal `[#r]` instead of `ReversedIndex`.
There are likely more simplifications that are not listed. I am adding cases as I find them.
## type definitions
`src/types.yue` is a generated file. To regenerate it, run `make generate`.
This calls two scripts:
- `bin/fetch_ast.yue`
* fetches `yue_ast.h` header from [Yuescript][1] repo
* does some simple parsing of types
* has a few manual overrides because I'm lazy and haven't made it parse better
* generates `gen/types_raw.yue` (ignored in .gitignore)
- `bin/generate_types.yue`
* imports `gen/types_raw.yue` and generates `src/types.yue`
* has all types specified as actual `class` instances
* allows walking ast and comparing types
## comments
Comments from `yue.to_ast` are only preserved on `Statement`.
```moonscript
-- a
-- b
c = 1
-- .comments[] { '-- a', '-- b' }
```
Comments that are not a immediately preceding a statement are lost as expected.
```moonscript
-- a
-- b
print 1 -- .comments[] {}
print 1 -- comment
--^ .comments[] {} <- not present, too
```
Multiline comments swallow all surrounding whitespace.
And despite `Statement<.comments>`, `to_ast`
simplifies to `YueLineComment|MultilineCommentInner`.
```moonscript
-- a
-- b
--[[ c ]]
d = 1
-- .comments[] { LineComment'-- a', LineComment'-- b', MultilineComment'c' }
-- same result regardless of whitespace
-- [[
c
]]
```
While this library does handle constructing these when they are added to
statements, it also has a crude comment 'parser' itself that maps lines
to comments.
```moonscript
src = "-- comment1\nb = 1 -- comment 2\n\n-- comment 3\n...etc"
-- returns t{ indices: { line# that has comment, ... }, [line#]: { YueLineComment|YueMultilineComment, ... }
-- indices are useful if you want to just know which lines have comments or iter, as table holes will prevent
-- full iteration.
comments = linter.find_comments src
for idx in *comments.indices
for v in *comments[idx]
-- depending on YueMultilineComment or YueLineComment val
print v.inner?.v ?? v.v
```
# contributing
- **don't be an ass**
- write decent commit messages
[1]: https://github.com/IppClub/YueScript
[2]: https://github.com/mfussenegger/nvim-lint
[3]: https://github.com/chrsm/dotfiles/blob/master/neovim/.config/nvim/lua/plug/nvim_lint.yue#L14
# changelog
2026-02
* implemented `yuefmt`, a tool for formatting yue code
* WONTFIX'd .yuecheck comp protection; so little use of yue that this seems unnecessary and annoying
2026-01
* updated to YueScript 0.32.4; possible breaking changes