Skip to content

chrsm/yuecheck

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

yuecheck

Lint tool for Yuescript.

Current TODOs:

  • Only use repo .yuecheck if trusted (due to macro code execution)
  • General cleanup. Rules are ugly for implementation
  • Configuration-driven format rules
    • This may require just writing the whole file from AST each time.
    • For the record I have taken a pass at this but got frustrated by comment placement. I will probably come back to it.
    • There is yue.format, too, but since it is based on the AST it does not preserve all comments.
  • 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

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.

git clone git@github.com:chrsm/yuecheck.git
cd yuecheck
luarocks make
# or luarocks --local make

usage

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 <directory>] | [-f <file>] | [<dir_or_file>] | [--stdin])
       [-x [<exclude>] ...]

Options:
   -h, --help            Show this help message and exit.
            -d <directory>,
   --directory <directory>
                         directory containing files to check
       -f <file>,        specific file to check
   --file <file>
   --stdin               parse stdin
   --ignore-config       ignore repo or user config
   -j, --json            output as json
          -x [<exclude>] ...,
   --exclude [<exclude>] ...
                         pattern for paths to exclude

Simple example: sample.yue

x = "abcd"
$ yuecheck sample.yue
sample.yue:1:3: double-quoted string where single-quoted would suffice

1 issues

example neovim setup

If you're a neovim user, you can refer to my dotfiles for how I hooked this up via nvim-lint: nvim_lint config.

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.

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.

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.

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.

-- yuecheck:ignore=stdlib_usage,smell_global_writes

_G['a'] = 'ignored'
os.clock 'ignored'
-- yuecheck:ignore

_G['is also'] = 'acceptable'

per line

_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.

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:

{
  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 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.

-- a
-- b
c = 1
-- .comments[] { '-- a', '-- b' }

Comments that are not a immediately preceding a statement are lost as expected.

-- a
-- b

print 1 -- .comments[] {}

print 1 -- comment
--^ .comments[] {} <- not present, too

Multiline comments swallow all surrounding whitespace. And despite Statement<.comments<YueLineComment,YueMultilineComment>>, to_ast simplifies to YueLineComment|MultilineCommentInner.

-- 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.

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

About

yuescript linter

Topics

Resources

License

Stars

Watchers

Forks