- Rust 97.8%
- Nushell 1.8%
- Nix 0.4%
| .ast-grep | ||
| .cargo | ||
| .helix | ||
| .vscode | ||
| benches | ||
| refactor | ||
| refactor-tests | ||
| screenshots | ||
| scripts | ||
| src | ||
| .envrc | ||
| .gitattributes | ||
| .gitignore | ||
| .harper-dictionary.txt | ||
| .woodpecker.pr.yaml | ||
| .woodpecker.yaml | ||
| build.rs | ||
| Cargo.lock | ||
| Cargo.toml | ||
| cliff.toml | ||
| clippy.toml | ||
| CONTRIBUTING.md | ||
| flake.lock | ||
| flake.nix | ||
| LICENSE | ||
| README.md | ||
| rust-toolchain.toml | ||
| rustfmt.toml | ||
| sgconfig.yml | ||
Nu-Lint
Linter for the innovative Nu shell.
Learning to use a new shell is a radical change that can use some assistance. This project is aimed at helping new and intermediate users of the Nu shell. Nu shell has a lot of useful features not found in other scripting languages. This linter will give you hints to use all of them and even offer automatic fixes.
Usage
Lint all Nu files in working directory with:
nu-lint
To see all options and get help:
nu-lint --help
Screenshots
Following screenshots were taking in Helix with nu-lint set-up as LSP (nu-lint --lsp).
- Automatic fixes and ignore actions (with keybinding
space ain Helix):
- Explanations in popup (with keybinding with
space kin Helix):
- Inline hints (opt-in in most editors)

To have nu-lint in your terminal directly (not just scripts), use nushell-lsp:
- Real-time diagnostics while typing in Nushell prompt:

- Fixes (and ignore actions) offered in Nushell prompt

Rule example
The rule turn_positional_into_stream_input recommends to use pipelines instead of positional arguments:
def filter-positive [numbers] {
$numbers | where $it > 0
}
def filter-positive [] {
where $it > 0
}
This encourages lazy pipeline input: a positional list argument loads all data into memory at once, while implicit pipeline input processes elements one at a time.
Rules
All rules are optional and can be disabled with a configuration file or comment. The rule definitions are compatible with:
- The official Nu parser nu-parser.
- The Tree-sitter-based Nu formatter topiary-nushell.
- The official Nu style guide
Some of the rules need further testing and improvement. Please make an issue on the issue tracker to report any bugs. In early stages of development some rules may be replaced or renamed.
+150 rules are defined and most have automatic fixes available (list may be out-of-date):
idioms - Simplifications unique to the Nu language.
not_is_empty_to_is_not_empty(auto-fix): Simplifynot ... is-emptytois-not-emptycolumns_in_to_has(auto-fix): Use 'has' operator instead of 'in ($record | columns)'columns_not_in_to_not_has(auto-fix): Simplifynot-in columnstonot-hasdispatch_with_subcommands: Match dispatch replaceable with subcommandsget_optional_to_has(auto-fix): Simplifyget -o | is-not-emptytohasget_optional_to_not_has(auto-fix): Simplifyget -o | is-emptytonot-hashardcoded_math_constants(auto-fix): Hardcoded mathematical constants should use std/math constants insteadtranspose_items(auto-fix): Simplifytranspose | eachtoitemsmerge_get_cell_path(auto-fix): Combine chained 'get' commands into cell pathsmerge_multiline_print(auto-fix): Consecutive prints mergeable into onepositional_to_pipeline(auto-fix): Data parameter convertible to pipeline inputsource_to_use:sourcereplaceable withusecompound_assignment(auto-fix): Compound assignment operators simplify simple arithmetic.contains_to_regex_op(auto-fix): Use =~ and !~ operators instead of verbose 'str contains' checksansi_over_escape_codes(auto-fix): Raw ANSI escape replaceable withansiappend_to_concat_assign(auto-fix): Use ++= operator instead of verbose append in assignmentcustom_log_command(auto-fix): Custom log command shadows stdlib. Useuse std/loginsteadchained_append(auto-fix): Use spread syntax instead of chained 'append' commandsuse_record_spread(auto-fix): Use record spread for consecutive field assignmentsuse_load_env(auto-fix): Use load-env for multiple $env assignmentsremove_hat_not_builtin(auto-fix): Detect unnecessary '^' prefix on external commands
parsing - Better ways to parse and transform text data.
lines_instead_of_split(auto-fix): Use 'lines' command for splitting by newlinesnever_space_split(auto-fix): Unnecessary quotes around variablelines_each_to_parse(auto-fix): Remove redundant 'each' wrapper around 'parse'simplify_regex_parse(auto-fix): Simplify 'parse --regex' to 'parse' with pattern syntaxsplit_row_get_multistatement(auto-fix): Extract field directly with 'parse' instead of storing split resultsplit_first_to_parse(auto-fix): Extract first field with 'parse' patternsplit_row_get_inline(auto-fix): Extract field by name with 'parse' patternsplit_row_space_to_split_words(auto-fix): Use 'split words' for whitespace splitting
filesystem - Simplify file and path operations.
from_after_parsed_open(auto-fix):openalready parses known formats into structured dataopen_raw_from_to_open(auto-fix): Simplifyopen --raw | fromtoopenstring_param_as_path(auto-fix): Parameter typed as string but used as filesystem path
dead-code - Remove unused or redundant code
self_import: Circular import: script imports itselfunnecessary_accumulate: Redundant accumulator pattern: can be simplifiedassign_then_return(auto-fix): Redundant variable before returndo_not_compare_booleans(auto-fix): Redundant comparison with boolean literalif_null_to_default(auto-fix): Simplify if-null pattern to| defaultredundant_ignore(auto-fix): Commands producing output that is discarded with '| ignore'unnecessary_mut(auto-fix): Variable markedmutbut never reassignedunused_helper_functions(auto-fix): Function unreachable from entry pointsunused_parameter(auto-fix): Function parameter declared but never usedunused_variable(auto-fix): Variable declared but never usedscript_export_main(auto-fix): In scripts, 'def main' is the entry point and doesn't need 'export'string_may_be_bare(auto-fix): Quoted string can be bare wordsingle_call_command(auto-fix): Single-line command called only onceappend_to_concat_assign(auto-fix): Use ++= operator instead of verbose append in assignment
posix - Replace common bash/POSIX patterns.
ignore_over_dev_null(auto-fix): Use '| ignore' instead of redirecting to /dev/nullawk_to_pipeline(auto-fix):awkreplaceable with structured pipelinebat_to_open(auto-fix):batreplaceable withopenfor file viewingcat_to_open(auto-fix): Externalcatreplaceable withopendate_to_date_now(auto-fix): Externaldatereplaceable withdate nowdf_to_sys_disks(auto-fix):dfreplaceable withsys disksredundant_echo(auto-fix): Redundantecho(identity function)find_to_glob(auto-fix):findreplaceable withgloborlsfree_to_sys_mem(auto-fix):freereplaceable withsys memfor memory infogrep_to_find_or_where(auto-fix):grepreplaceable withfindorwherehead_to_first(auto-fix):headreplaceable withfirsthostname_to_sys_host(auto-fix):hostnamereplaceable withsys hostexternal_cd_to_builtin(auto-fix): Externalcdreplaceable with built-incdexternal_ls_to_builtin(auto-fix): Externallsreplaceable with built-inpager_to_explore(auto-fix): Pager replaceable withexploreread_to_input(auto-fix):readreplaceable withinputsed_to_str_transform(auto-fix):sedreplaceable withstr replaceexternal_sort_to_builtin(auto-fix): Externalsortreplaceable with built-intac_to_reverse(auto-fix):tacreplaceable withlines | reversetail_to_last(auto-fix):tailreplaceable withlastuname_to_sys_host(auto-fix):unamereplaceable withsys hostexternal_uniq_to_builtin(auto-fix): Externaluniqreplaceable with built-inuptime_to_sys_host(auto-fix):uptimereplaceable withsys hostusers_to_sys_users(auto-fix):usersreplaceable withsys usersw_to_sys_users(auto-fix):wreplaceable withsys userswc_to_length(auto-fix):wcreplaceable withlengthwho_to_sys_users(auto-fix):whoreplaceable withsys users
iteration - Better patterns for loops and iteration.
loop_counter_to_range: Loop counter to range iterationwhile_counter_to_range: Counter while-loop to range iteration
runtime-errors - Preventing unexpected runtime behaviour.
add_hat_external_commands(auto-fix): Always use the '^' prefix on external commandsfragile_last_exit_code(auto-fix): FragileLAST_EXIT_CODEcheckcheck_complete_exit_code: Unchecked exit code aftercompletedescriptive_error_messages: Error messages should be descriptive and actionableunescaped_interpolation: Unescaped braces in string interpolationexit_only_in_main: Avoid using 'exit' in functions other than 'main'check_typed_flag_before_use: Typed flag used without null checknon_final_failure_check: Non-final pipeline command exit code ignorederror_make_for_non_fatal(auto-fix): Use 'error make' for catchable errors in functions and try blockstry_instead_of_do: Use 'try' blocks instead of 'do' blocks for error-prone operationsunsafe_dynamic_record_access(auto-fix): Use 'get -o' for dynamic keys to handle missing keys safelymissing_stdin_in_shebang(auto-fix): Shebang missing--stdinfor inputdynamic_script_import: Dynamic import path not statically validatedcatch_builtin_error_try: Catch runtime errors from built-in commands using 'try' blocksunchecked_cell_path_index(auto-fix): Cell path numeric index access may panic on empty listsunchecked_get_index(auto-fix): Prefer optional cell path$list.0?overgetfor index accesswrap_external_with_complete: External command missingcompletewrappersource_to_use:sourcereplaceable withusespread_list_to_external(auto-fix): List variables passed to external commands should be spread with...glob_may_drop_quotes(auto-fix): Quoted glob pattern treated as literalrequire_main_with_stdin: Scripts using $in must define a main function
filtering - Better patterns for filtering and selecting data.
each_if_to_where(auto-fix): Use 'where' for filtering instead of 'each' with 'if'for_filter_to_where: Use 'where' filter instead of for loop with if and appendomit_it_in_row_condition(auto-fix): Field names in 'where' row conditions don't need$it.prefixslice_to_drop(auto-fix): Use 'drop' instead of 'slice ..-N' to drop last N-1 elementsslice_to_last(auto-fix): Use 'last' instead of 'slice (-N)..' to get last N elementsslice_to_skip(auto-fix): Use 'skip' instead of 'slice N..' to skip first N elementsslice_to_take(auto-fix): Use 'take' instead of 'slice 0..N' to take first N elementswhere_closure_drop_parameter(auto-fix): You can drop the closure and its parameter in 'where' and 'filter'.remove_redundant_in(auto-fix): Redundant$inat pipeline start
performance - Rules with potential performance impact
redundant_nu_subprocess: Redundantnu -csubprocess calldispatch_with_subcommands: Match dispatch replaceable with subcommandsself_import: Circular import: script imports itselfpositional_to_pipeline(auto-fix): Data parameter convertible to pipeline inputunnecessary_accumulate: Redundant accumulator pattern: can be simplifiedmerge_multiline_print(auto-fix): Consecutive prints mergeable into onechained_str_transform(auto-fix): Consecutivestr replacecombinablestreaming_hidden_by_complete(auto-fix): Streaming commands should not be wrapped with 'complete'chained_append(auto-fix): Use spread syntax instead of chained 'append' commands
type-safety - Annotate with type hints where possible.
external_script_as_argument: Script path passed as command argumentnothing_outside_signature(auto-fix):nothingtype used outside signatureadd_type_hints_arguments(auto-fix): Arguments of custom commands should have type annotationsstring_param_as_path(auto-fix): Parameter typed as string but used as filesystem pathmissing_output_type(auto-fix): Command missing output type annotationmissing_in_type(auto-fix): Command using$inmissing input typeredundant_nu_subprocess: Redundantnu -csubprocess calldynamic_script_import: Dynamic import path not statically validated
documentation - Improve usefullness user-facing messages.
add_doc_comment_exported_fn: Exported functions should have documentation commentsdescriptive_error_messages: Error messages should be descriptive and actionableadd_label_to_error: error make should include 'label'add_help_to_error:error makemissinghelpfieldadd_span_to_label: labels should include 'span' to highlight error location in user codeadd_url_to_error: error make should include 'url' field to link to documentationmain_positional_args_docs: Missing docs on main positional parametermain_named_args_docs: Missing docs on main flag parametermax_positional_params: Custom commands should have ≤ 2 positional parametersexplicit_long_flags(auto-fix): Replace short flags (-f) with long flags (--flag)list_param_to_variadic(auto-fix): Use variadic...argsinstead of a single list parameter
effects - Handle built-in and external commands with side-effects.
dangerous_file_operations: File operation on dangerous system patherrors_to_stderr: Error messages should go to stderr, not stdoutdont_mix_different_effects: Functions should not mix different types of I/O operations or effects.print_and_return_data: Function prints and returns dataeach_nothing_to_for_loop(auto-fix):eachmappings with no output should be written asforloops.silence_stderr_data: External commands that write data to stderr should not be silenced
external - Replace common external CLI tools.
curl_to_http(auto-fix):curlreplaceable withhttpcommandsfd_to_glob(auto-fix):fdreplaceable withgloborlsjq_to_nu_pipeline(auto-fix): Simplejqfilter replaceable with Nushell pipelinewget_to_http_get(auto-fix):wgetreplaceable withhttp getexternal_which_to_builtin(auto-fix): Externalwhichreplaceable with built-instructured_data_to_csv_tool(auto-fix): Table piped to CSV tool withoutto csvstructured_data_to_json_tool(auto-fix): Data piped to JSON tool withoutto json
formatting - Formatting according to style-guide.
ansi_over_escape_codes(auto-fix): Raw ANSI escape replaceable withansicollapsible_if(auto-fix): Nested if-statements collapsible withandforbid_excessive_nesting: Avoid excessive nesting (more than 4 levels deep)max_function_body_length: Function bodies should be short to maintain readabilityif_else_chain_to_match(auto-fix): Use 'match' for value-based branching instead of if-else-if chainsblock_brace_spacing(auto-fix): Block body needs spaces inside braces:{ body }not{body}closure_brace_pipe_spacing(auto-fix): Space between{and|in closureclosure_pipe_body_spacing(auto-fix): Closure body needs spaces:{|x| body }not{|x|body}no_trailing_spaces(auto-fix): Eliminate trailing spaces at the end of linesomit_list_commas(auto-fix): Omit commas between list items.pipe_spacing(auto-fix): Inconsistent spacing around|record_brace_spacing(auto-fix): Record braces should touch content:{a: 1}not{ a: 1 }reflow_wide_pipelines(auto-fix): Pipeline exceeds line length limitreflow_wide_lists(auto-fix): Wrap wide lists vertically across multiple lines.wrap_wide_records(auto-fix): Wrap records exceeding 80 chars or with deeply nested structures
naming - Follow official naming conventions
kebab_case_commands: Custom commands should use kebab-case naming conventionscreaming_snake_constants: Constants should use SCREAMING_SNAKE_CASE naming conventionsnake_case_variables(auto-fix): Variables should use snake_case naming conventionadd_label_to_error: error make should include 'label'
upstream - Forward warnings and errors of the Nu parser.
dynamic_script_import: Dynamic import path not statically validatednu_deprecated(auto-fix): Parser detected deprecated command or flag usagenu_parse_error: Parser encountered a syntax error
Installation
Recommended
The type installation that will always work on your system. From crates.io:
cargo install nu-lint
No system dependencies are required.
Source
Build from source:
cargo install --path .
From latest git main branch:
cargo install --git https://codeberg.org/wvhulle/nu-lint
Nix
Run without installing permanently (using flakes):
nix run git+https://codeberg.org/wvhulle/nu-lint
Pre-Compiled Binaries
Download the appropriate binary from the releases subpage. (Not always the most up-to-date, prefer Crates.io releases)
Editor extension
VS Code extension
Available at VS Code Marketplace.
Helix
Add to your ~/.config/helix/languages.toml:
[language-server.nu-lint]
command = "nu-lint"
args = ["--lsp"]
[[language]]
name = "nu"
language-servers = ["nu-lint"]
The official Nu LSP server offers completion and some other hints. It should be configured out of the box for new Helix installations and environments that have Nushell installed.
Neovim
Add to your Neovim configuration (Lua):
vim.lsp.config['nu-lint'] = {
cmd = { 'nu-lint', '--lsp' },
filetypes = { 'nu' },
root_markers = { '.git' }
}
vim.lsp.enable('nu-lint')
You may want to configure official Nu language server in addition to this linter, see nu --lsp command.
Emacs
Add to your Emacs configuration (with Eglot, built-in since Emacs 29):
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
'(nushell-mode "nu-lint" "--lsp")))
Kate
Add to your ~/.config/kate/lspclient/settings.json:
{
"servers": {
"nushell": {
"command": ["nu-lint", "--lsp"],
"highlightingModeRegex": "^Nushell$"
}
}
}
Zed
Available as Nu-Lint extension.
Other
You can also implement your own editor extensions using the --lsp flag as in: nu-lint --lsp. This will spawn a language server compliant with the Language Server Protocol.
Configuration
Some rules are deactivated by default. Usually because they are too noisy or annoy people. You should activate them with the config file and a level override.
A configuration file at the top of the workspace is optional and should be named .nu-lint.toml in your project root. It may look like this:
# Some rules are configurable
max_pipeline_length = 80
pipeline_placement = "start"
explicit_optional_access = true
# Set lint level of a set of rules at once.
[groups]
performance = "warning"
type-safety = "error"
# Override a single rule level
[rules]
dispatch_with_subcommands = "hint"
unchecked_cell_path_index = "off"
In this particular case, the user overrides the 'level' of certain groups and individual rules.
You can also turn of a rule violation on a particular lien by appending a comment to the line:
$data | each {|row|
$row.values.0 # nu-lint-ignore: unchecked_cell_path_index
}
There is a shortcut to do this by selecting the "ignore line violation" code action in the code action menu of your editor.
For any setting you don't set in the optional workspace configuration file, the defaults set in ./src/config.rs will be used. If you specify the option in the configuration file, it will override the defaults.