├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── formatting-issue.md ├── dependabot.yml └── workflows │ └── push.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .pre-commit-hooks.yaml ├── .vscode └── settings.json ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── bors.toml ├── ci.sh ├── default.nix ├── deploy.sh ├── docs ├── howto_rules.md └── releasing.md ├── flake.lock ├── flake.lock.nix ├── flake.nix ├── fuzz ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── artifacts │ └── fmt │ │ ├── crash-0bdada2399fbe428ba729b8a87268e6670e5c07b │ │ ├── crash-33dad1919de59d6e9ab676817db5db4642bcf212 │ │ └── crash-a37db01a12a3c74202ac13360069e732af701c7e └── fuzz_targets │ └── fmt.rs ├── rustfmt.toml ├── shell.nix ├── src ├── dsl.rs ├── engine.rs ├── engine │ ├── fixes.rs │ ├── fmt_model.rs │ ├── indentation.rs │ └── spacing.rs ├── lib.rs ├── main.rs ├── pattern.rs ├── rules.rs └── tree_utils.rs ├── test_data ├── attr_fn.bad.nix ├── attr_fn.good.nix ├── binop_wrap_before.bad.nix ├── binop_wrap_before.good.nix ├── comment_in_inherit.bad.nix ├── comment_in_inherit.good.nix ├── comment_indent.bad.nix ├── comment_indent.good.nix ├── concat_in_attr.bad.nix ├── concat_in_attr.good.nix ├── curried_fn.bad.nix ├── curried_fn.good.nix ├── curried_fn_no_indent.bad.nix ├── curried_fn_no_indent.good.nix ├── existing_blank_lines.bad.nix ├── existing_blank_lines.good.nix ├── fn_args_multiline.bad.nix ├── fn_args_multiline.good.nix ├── fn_args_multiple.bad.nix ├── fn_args_multiple.good.nix ├── fn_args_singleline.bad.nix ├── fn_args_singleline.good.nix ├── function_call.bad.nix ├── function_call.good.nix ├── if_then_else_indent.bad.nix ├── if_then_else_indent.good.nix ├── indent_assert_body.bad.nix ├── indent_assert_body.good.nix ├── indent_fn_body.bad.nix ├── indent_fn_body.good.nix ├── indent_lambda_top_level.bad.nix ├── indent_lambda_top_level.good.nix ├── indent_let.bad.nix ├── indent_let.good.nix ├── indent_or_default.bad.nix ├── indent_or_default.good.nix ├── indent_paren.bad.nix ├── indent_paren.good.nix ├── indent_string_literal.bad.nix ├── indent_string_literal.good.nix ├── indent_string_literal_interpolation.bad.nix ├── indent_string_literal_interpolation.good.nix ├── indent_tabs-1.bad.nix ├── indent_tabs-1.good.nix ├── indent_tabs-2.bad.nix ├── indent_tabs-2.good.nix ├── indent_tabs-3.bad.nix ├── indent_tabs-3.good.nix ├── indented_lambda.bad.nix ├── indented_lambda.good.nix ├── issue-125.bad.nix ├── issue-125.good.nix ├── issue-126.bad.nix ├── issue-126.good.nix ├── issue-132.bad.nix ├── issue-132.good.nix ├── issue-151.bad.nix ├── issue-151.good.nix ├── issue-152.bad.nix ├── issue-152.good.nix ├── issue-158.bad.nix ├── issue-158.good.nix ├── issue-161.bad.nix ├── issue-161.good.nix ├── issue-162.bad.nix ├── issue-162.good.nix ├── issue-178.bad.nix ├── issue-178.good.nix ├── issue-181.bad.nix ├── issue-181.good.nix ├── issue-185.bad.nix ├── issue-185.good.nix ├── issue-196.bad.nix ├── issue-196.good.nix ├── issue-199.bad.nix ├── issue-199.good.nix ├── issue-205.bad.nix ├── issue-205.good.nix ├── issue-83-1.bad.nix ├── issue-83-1.good.nix ├── issue-83-2.bad.nix ├── issue-83-2.good.nix ├── issue-83-3.bad.nix ├── issue-83-3.good.nix ├── leading_whitespace.bad.nix ├── leading_whitespace.good.nix ├── list_elements.bad.nix ├── list_elements.good.nix ├── list_multiline.bad.nix ├── list_multiline.good.nix ├── list_with_commented_out_item.bad.nix ├── list_with_commented_out_item.good.nix ├── nested_if_else.bad.nix ├── nested_if_else.good.nix ├── nested_indent.bad.nix ├── nested_indent.good.nix ├── nested_indent_after_binop.bad.nix ├── nested_indent_after_binop.good.nix ├── nixpkgs_repository │ ├── doc_shellnix.bad.nix │ ├── doc_shellnix.good.nix │ ├── flakenix.bad.nix │ ├── flakenix.good.nix │ ├── lib_attrset.bad.nix │ ├── lib_attrset.good.nix │ ├── lib_cli.bad.nix │ ├── lib_cli.good.nix │ ├── lib_customisation.bad.nix │ ├── lib_customisation.good.nix │ ├── lib_deprecated.bad.nix │ ├── lib_deprecated.good.nix │ ├── lib_generators.bad.nix │ ├── lib_generators.good.nix │ ├── lib_modules.bad.nix │ ├── lib_modules.good.nix │ ├── lib_strings.bad.nix │ ├── lib_strings.good.nix │ ├── lib_tests_modules_alias_priority_override.bad.nix │ ├── lib_tests_modules_alias_priority_override.good.nix │ ├── maintainers_maintainer_list.bad.nix │ ├── maintainers_maintainer_list.good.nix │ ├── maintainers_scripts_update.bad.nix │ ├── maintainers_scripts_update.good.nix │ ├── nixos_lib_build_vms.bad.nix │ ├── nixos_lib_build_vms.good.nix │ ├── nixos_lib_testing_python.bad.nix │ ├── nixos_lib_testing_python.good.nix │ ├── nixos_modules_config_console.bad.nix │ ├── nixos_modules_config_console.good.nix │ ├── nixpkgs_idempotent.bad.nix │ └── nixpkgs_idempotent.good.nix ├── operators_whitespace.bad.nix ├── operators_whitespace.good.nix ├── path-interpolation.bad.nix ├── path-interpolation.good.nix ├── reindents_block_comments.bad.nix ├── reindents_block_comments.good.nix ├── semicolon_in_set.bad.nix ├── semicolon_in_set.good.nix ├── set_multi_elem_value.bad.nix ├── set_multi_elem_value.good.nix ├── set_multiline.bad.nix ├── set_multiline.good.nix ├── set_nested_indent.bad.nix ├── set_nested_indent.good.nix ├── set_singleline.bad.nix ├── set_singleline.good.nix ├── syntax_errors │ ├── incomplete_set.bad.nix │ ├── incomplete_set.good.nix │ ├── issue-93.bad.nix │ └── issue-93.good.nix ├── top_level_assert.bad.nix ├── top_level_assert.good.nix ├── top_level_let.bad.nix ├── top_level_let.good.nix ├── top_level_with.bad.nix ├── top_level_with.good.nix ├── top_level_with2.bad.nix ├── top_level_with2.good.nix ├── trailing_comment.bad.nix └── trailing_comment.good.nix ├── tests └── integration_tests.rs ├── update-cargo-nix.sh └── wasm ├── .nojekyll ├── Cargo.toml ├── LICENSE ├── README.md ├── build.sh ├── index.html └── src └── lib.rs /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug, needs triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | 1. Run `nixpkgs-fmt ...' 17 | 2. ... 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **System information** 23 | 24 | * `nixpkgs-fmt --version` 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement, needs triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/formatting-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Formatting issue 3 | about: Use this template to discuss the formatting output 4 | title: '' 5 | labels: formatting, needs triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Input 11 | ```nix 12 | ``` 13 | 14 | ## Output 15 | ```nix 16 | ``` 17 | 18 | ## Desired output 19 | 20 | ```nix 21 | ``` 22 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | - staging 8 | - trying 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: cachix/install-nix-action@v26 15 | - uses: cachix/cachix-action@v14 16 | with: 17 | name: nix-community 18 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 19 | skipNixBuild: true 20 | - run: ./ci.sh 21 | - uses: actions/upload-artifact@v4 22 | with: 23 | name: wasm 24 | path: wasm 25 | deploy: 26 | if: github.ref == 'refs/heads/master' 27 | runs-on: ubuntu-latest 28 | needs: build 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: actions/download-artifact@v4 32 | with: 33 | name: wasm 34 | path: wasm 35 | - run: ./deploy.sh 36 | env: 37 | DEPLOY_SSH_KEY: '${{ secrets.DEPLOY_SSH_KEY }}' 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.rs.bk 2 | 3 | # Rust 4 | target 5 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/doublify/pre-commit-rust 5 | rev: ebc9050d3d3434417feff68e3d847ad4123f5ba8 6 | hooks: 7 | - id: fmt 8 | - id: cargo-check 9 | 10 | - repo: local 11 | hooks: 12 | - id: nixpkgs-fmt 13 | name: nixpkgs-fmt 14 | description: Format nix code with nixpkgs-fmt. 15 | entry: cargo run -- 16 | language: rust 17 | files: \.nix$ 18 | always_run: true 19 | minimum_pre_commit_version: 1.14.2 20 | exclude: > 21 | (?x)^( 22 | nix/sources.nix| 23 | test_data/.* 24 | )$ 25 | -------------------------------------------------------------------------------- /.pre-commit-hooks.yaml: -------------------------------------------------------------------------------- 1 | - id: nixpkgs-fmt 2 | name: nixpkgs-fmt 3 | description: Format nix code with nixpkgs-fmt. 4 | entry: nixpkgs-fmt 5 | language: rust 6 | files: \.nix$ 7 | minimum_pre_commit_version: 1.18.1 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "nixEnvSelector.nixFile": "${workspaceRoot}/shell.nix" 3 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 1.3.0 / 2022-06-15 3 | ================== 4 | 5 | ### Formatting Changes 6 | 7 | * Remove indentation for lambda function in top level 8 | * fix multiline comment (#245) 9 | * Improve formatting with tab characters (#275) 10 | * change NODE_LET_IN behavior to fix idempotent issue. 11 | * Update rnix to 0.10.2 (#297, #278) 12 | * add single space after variable declaration 13 | 14 | ### Other 15 | 16 | * improve error CLI ergonomics (#269) 17 | * Properly handle SIGPIPE (#256) 18 | 19 | * Add links to VSCode extensions to README (#259) 20 | * Bump crossbeam-channel from 0.3.9 to 0.4.4 (#293) 21 | * Bump regex from 1.5.4 to 1.5.6 (#294) 22 | * README: fix installation (#246) 23 | * build: replace flake-compat with flake.lock.nix 24 | * cargo update 25 | * cargo update (#271) 26 | * cargo: fix the rowan dependency 27 | * default.nix: composition > inheritance 28 | * default.nix: keep back-compat 29 | * devShell: add stdenv.cc to the environment 30 | * docs: clarify changelog generation (#277) 31 | * fix CLI option output-format (#242) 32 | * fix ordering error in CI 33 | * flake update 34 | * flake.lock.nix: work in pure mode 35 | * nix: make the shell buildable 36 | * nix: remove naersk (#272) 37 | * nix: replace nixpkgs-mozilla with fenix 38 | * refactor: avoid non_fmt_panics warning (#279) 39 | 40 | 1.2.0 / 2021-03-29 41 | ================== 42 | 43 | ### Formatting Changes 44 | 45 | nixpkgs-fmt is now fully idempotent over nixpkgs! 46 | 47 | * add format rule for NODE_OR_DEFAULT, adding nixpkgs repo test, and remove walk_non_whitespace function (#235) 48 | * add and fix new test_date to match the new block comment formatting (#233) 49 | 50 | ### Other 51 | 52 | * flake: use `lib` instead of `stdenv.lib` (PR #234) 53 | * refactor block comment formatting 54 | * update vscode's setting.json 55 | 56 | 1.1.0 / 2021-02-21 57 | ================== 58 | 59 | ### Formatting Changes 60 | 61 | * relax spacing rule for Newline type, simplify NODE_LET_IN spacing rule, clean up warnings (#220) 62 | * fix #205 - Add space between inherit (#219) 63 | 64 | ### Other 65 | 66 | * Merge pull request #230 from jD91mZM2/bump-rnix 67 | * Bump rust version in nix 68 | * Update rnix + rowan 69 | * deploy.sh: build wasm before deploying 70 | * flake: make defaultPackage an alias of nixpkgs-fmt 71 | * flake update (#227) 72 | * add dependabot for updating github actions (#226) 73 | * ci: update GH actions (#225) 74 | 75 | 76 | 1.0.0 / 2020-08-17 77 | ================== 78 | 79 | ### Formatting Changes 80 | * Add NODE_APPLY rule, remove top_level predicate from spacing and modify test data (#212) 81 | * Refactor node paren predicate to match node binop and if_else 82 | * Simplify parentheses rules and fix some test_data to match new rule (#212) 83 | * Simplify node if_else rules(#212) 84 | * Unified rules for node apply and remove node apply rule under node key value (#212) 85 | * Alternative interpolation indentation strategy (#214) 86 | * Remove unnecessary predicates for NODE_PAREN rules (#212) 87 | * Commit whitespace changes before computing indentation (#209) 88 | 89 | ### Other 90 | 91 | * Update flake to the new format 92 | * Use T! macro for symbolic tokens(#211) 93 | * Remove some commented code (#217) 94 | * Fix typo (#216) 95 | * Remove some dead code (#208) 96 | * Check idempotence before expected (#207) 97 | 98 | 0.9.0 / 2020-05-07 99 | ================== 100 | 101 | ### Formatting changes 102 | 103 | * Change lambda inside node pattern indentation rules (#204) 104 | * Change key value spacing rues (#204) 105 | * Change `${ .. }` formatting rules (#204) 106 | * Change `( .. )` spacing rules (#204) 107 | * Update test_data (#202, #204) 108 | * Change `assert` indentation rules (#202) 109 | * Change `inherit` spacing rules (#202) 110 | * Change function apply formatting rules (#202, #204) 111 | * Change `if .. then .. else` spacing rules (#202) 112 | 113 | ### Other 114 | 115 | * Remove debug print when running nixpkgs-fmt 116 | 117 | 0.8.0 / 2020-04-22 118 | ================== 119 | 120 | ### Formatting changes 121 | 122 | * Change multiline string formatting rules (#193) 123 | * Change `${ .. }` formatting rules (#187) 124 | * Change function apply function rules (#174) 125 | * Change `let .. in ..` formatting rules (#180) 126 | * Change binops formatting rules (#177) 127 | * Update test_data (#173, #174, #176, 177, #180, #182, #183, #187, #188, #193) 128 | * Change brackets' formatting rules (#188) 129 | * Change `( .. )` formatting rules (#177, #180, #182, #183) 130 | * Change `if .. then .. else` formatting rules (#176) 131 | * Change comment rules (#180, #193) 132 | * Change semicolon formatting rules (#172) 133 | * Change lambda function formatting rules (#173) 134 | * Change `{ .. }` formatting rules (#177) 135 | 136 | ### Other 137 | 138 | * Update README (#192) 139 | * Update naersk 140 | * Update flake.nix (#173, #188, 193) 141 | 142 | 0.7.0 / 2020-02-09 143 | ================== 144 | 145 | ### Formatting changes 146 | 147 | * Change the `let ... in ...` formatting rules (#169, #168, #167, #125) 148 | 149 | ### Other 150 | 151 | * Add flake support 152 | * Update naersk 153 | * CI: switch to GitHub actions 154 | 155 | 0.6.1 / 2019-11-05 156 | ================== 157 | 158 | ### Formatting changes 159 | 160 | * Support float scientific notation (#150) 161 | 162 | ### Other 163 | 164 | * Fix clippy lint warnings/errors (#149) 165 | 166 | 0.6.0 / 2019-09-16 167 | ================== 168 | 169 | ### Formatting changes 170 | 171 | NONE 172 | 173 | ### Features 174 | 175 | * print touched files to stdout (#148) 176 | * implement `nixpkgs-fmt --check` for CI (#148) 177 | 178 | ### Other 179 | 180 | * shell.nix: pin rust version and use extensions from the distribution (#148) 181 | * fix typo in README (#146) 182 | 183 | 0.5.0 / 2019-09-07 184 | ================== 185 | 186 | ### Formatting changes 187 | 188 | * convert tabs to spaces (#143) 189 | 190 | ### Features 191 | 192 | * add --explain mode to expose the engine rewrite decisions (#142) 193 | 194 | ### Other 195 | 196 | * replace #[macro_use] extern crate with modern syntax (#141) 197 | * incorporate recent rnix renamings (#144) 198 | * nix: use naersk so hashes are always up to date (#145) 199 | 200 | 0.4.0 / 2019-08-31 201 | ================== 202 | 203 | ### Formatting changes 204 | 205 | * Don't force newline before ++ anymore (#139) 206 | * Always indent concatenated lists 207 | * Add line break after comment in list 208 | 209 | ### Features 210 | 211 | * Add ability to print syntax tree in JSON format 212 | * Format directories out of the box. Eg: `nixpkgs-fmt .` 213 | * Refactor input handling, makes formatting 4x faster 214 | 215 | ### Changes 216 | 217 | * Add test to make sure the output is idempotent 218 | 219 | ### Other 220 | 221 | * BREAKING: Remove the --in-place flag 222 | 223 | 0.3.1 / 2019-08-23 224 | ================== 225 | 226 | * fix the release process 227 | 228 | 0.3.0 / 2019-08-23 229 | ================== 230 | 231 | * First lambda arg is on the line with brace 232 | 233 | 0.2.0 / 2019-08-23 234 | ================== 235 | 236 | First release! 237 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nixpkgs-fmt" 3 | version = "1.3.0" 4 | authors = [ 5 | "Aleksey Kladov ", 6 | "zimbatm " 7 | ] 8 | edition = "2018" 9 | license = "Apache-2.0" 10 | description = "Nix code formatter for nixpkgs" 11 | repository = "https://github.com/nix-community/nixpkgs-fmt" 12 | 13 | [workspace] 14 | members = [ "./wasm" ] 15 | 16 | [dependencies] 17 | rnix = "0.10.2" 18 | smol_str = "0.1.17" 19 | 20 | # Dependencies that are used in the binary only 21 | # Ideally, the feature should be enabled only for binary, 22 | # but Cargo can't express that nicely yet. 23 | crossbeam-channel = "0.4" 24 | ignore = "0.4.10" 25 | clap = "2.33.0" 26 | libc = "0.2.99" 27 | 28 | # Enable serialization support for rnix syntax trees. 29 | serde_json = "1.0" 30 | 31 | [dependencies.rowan] 32 | version = "0.12.6" 33 | features = [ "serde1" ] 34 | 35 | [dev-dependencies] 36 | unindent = "0.1.3" 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nixpkgs-fmt - Nix code formatter for nixpkgs 2 | 3 | [![CI](https://github.com/nix-community/nixpkgs-fmt/actions/workflows/push.yml/badge.svg?branch=master)](https://github.com/nix-community/nixpkgs-fmt/actions/workflows/push.yml) [![built with nix](https://builtwithnix.org/badge.svg)](https://builtwithnix.org) 4 | 5 | **STATUS: archived. Replaced by [nixfmt](https://github.com/NixOS/nixfmt).** 6 | 7 | This project's goal was to format the nix code in nixpkgs to increase the 8 | consistency of the code found there. Ideally automatically with pre-commit 9 | hooks and later ofborg enforcing the format. 10 | 11 | ## Demo 12 | 13 | You can try nixpkgs-fmt in your browser. The page also provides a way for you 14 | to submit code samples if you find the output not satisfying: 15 | https://nix-community.github.io/nixpkgs-fmt/ 16 | 17 | ## Design decisions 18 | 19 | You might ask yourself; why do we need yet another nix code formatter? 20 | 21 | The main goal of nixpkgs-fmt is to provide some overall consistency in the 22 | nix code submitted to [nixpkgs](https://github.com/NixOS/nixpkgs), our main 23 | package repository. 24 | 25 | At this point it's important to understand that there are multiple possible 26 | outputs for a code formatter. Those outputs will depend on multiple 27 | conflicting desires and depending on how much weight is being put on each 28 | requirement the output will change. 29 | 30 | For nixpkgs-fmt we have a few of these: 31 | 32 | 1. Minimize merge conflicts. nixpkgs is seen in a lot of pull-requests and we 33 | want to avoid them getting unnecessarily stale. 34 | 2. Only expand, don't collapse. It's up to the developer to choose if an 35 | element should be on a single line or multiple lines. 36 | 3. Respect the developer's expressivity. Empty lines can be useful as a way to 37 | separate blocks of code. 38 | 4. Only change the indent of one (+/-) per line. Not sure why but it seems 39 | like a good thing. 40 | 41 | Corollary rules: 42 | 43 | * because of (1). The format is quite close to what exists in nixpkgs already. 44 | * because of (1). Don't align values vertically, a single line change can 45 | introduce a very big diff. 46 | * because of (1). Avoid too many rules. More rules means more formatting 47 | changes that create merge conflicts. 48 | * because of (2). Don't enforce line lengths. Line length limits also create 49 | complicated heuristics. 50 | 51 | At the time where we started this project none of the other formatters were 52 | weighted that way. 53 | 54 | To implement this, we needed a whitespace and comment-preserving parser which 55 | [rnix](https://github.com/nix-community/rnix-parser) provides to us. Then create 56 | an engine that follows the AST and patches the tree with rewrite rules. The nice 57 | thing about this design is that it also works on incomplete or broken nix code. 58 | We are able to format up to the part that is missing/broken, which makes it 59 | great for potential editor integration. 60 | 61 | Most of the other formatters out there take a pretty-printing approach where 62 | the AST is parsed, and then a pretty-printer inspects and formats the AST back 63 | to code without taking spaces and newlines into account. The advantage is that 64 | it's initially easier to implement. The output is very strict and the same AST 65 | will always give the same output. One disadvantage is that the pretty-printer 66 | needs to handle all the possible combination of Nix code to make them look 67 | good. 68 | 69 | With nixpkgs-fmt the output will depend on how the code was formatted 70 | initially. The developer still has some input on how they want to format their 71 | code. If there is no rule for a complicated case, the code will be left alone. 72 | For nixpkgs this approach will be preferable since it minimizes the diff. 73 | 74 | Well done for reading all of this, I hope this clarifies a bit why nixpkgs-fmt 75 | exists and what role it can play. 76 | 77 | ## Usage 78 | 79 | 80 | ``` 81 | nixpkgs-fmt 1.2.0 82 | Format Nix code 83 | 84 | USAGE: 85 | nixpkgs-fmt [FLAGS] [OPTIONS] [FILE]... 86 | 87 | FLAGS: 88 | --check Only test if the formatter would produce differences 89 | --explain Show which rules are violated 90 | -h, --help Prints help information 91 | --parse Show syntax tree instead of reformatting 92 | -V, --version Prints version information 93 | 94 | OPTIONS: 95 | --output-format Set output format of --parse [default: rnix] [possible values: rnix, json] 96 | 97 | ARGS: 98 | ... File to reformat in place. If no file is passed, read from stdin. 99 | ``` 100 | ### Tree traversal 101 | 102 | When `nixpkgs-fmt` is given a folder as a file argument, it will traverse that 103 | using the same [ignore crate](https://crates.io/crates/ignore) as ripgrep, 104 | using 8 parallel threads. 105 | 106 | By default it will automatically ignore files reading `.ignore`, `.gitignore`, 107 | and `.git/info/exclude` files in that order. If additional files need to be 108 | ignored, it is also possible to add `--exclude ` to the call. 109 | 110 | ## Installation 111 | 112 | nixpkgs-fmt is available in nixpkgs master. `nix-env -i nixpkgs-fmt`. 113 | 114 | It's also possible to install it directly from this repository: 115 | 116 | `nix-env -f https://github.com/nix-community/nixpkgs-fmt/archive/master.tar.gz -iA nixpkgs-fmt` 117 | 118 | ### VSCode extensions 119 | 120 | There are a few VSCode extensions that make using `nixpkgs-fmt` convenient. 121 | Check out: 122 | 123 | - [B4dM4n.nixpkgs-fmt](https://marketplace.visualstudio.com/items?itemName=B4dM4n.nixpkgs-fmt) 124 | - [jnoortheen.nix-ide](https://marketplace.visualstudio.com/items?itemName=jnoortheen.nix-ide) 125 | 126 | ### pre-commit hook 127 | 128 | This project can also be installed as a [pre-commit](https://pre-commit.com/) 129 | hook. 130 | 131 | Add to your project's `.pre-commit-config.yaml`: 132 | 133 | ```yaml 134 | - repo: https://github.com/nix-community/nixpkgs-fmt 135 | rev: master 136 | hooks: 137 | - id: nixpkgs-fmt 138 | ``` 139 | 140 | Make sure to have rust available in your environment. 141 | 142 | Then run `pre-commit install-hooks` 143 | 144 | ## Development 145 | 146 | Install Rust and Cargo or run `nix-shell` to load the project dependencies. 147 | 148 | Install [pre-commit](https://pre-commit.com/) and run `pre-commit install` to 149 | setup the git hooks on the repository. This will allow to keep the code nicely 150 | formatted over time. 151 | 152 | Then use `cargo run` to build and run the software. 153 | 154 | ### Running Fuzzer 155 | 156 | ``` 157 | $ cargo install cargo-fuzz 158 | $ mkdir -p ./fuzz/corpus/fmt 159 | $ cp test_data/**.nix ./fuzz/corpus/fmt 160 | $ rustup run nightly -- cargo fuzz run fmt 161 | ``` 162 | 163 | or with nix: 164 | 165 | ``` 166 | $ nix-shell --run "cargo fuzz run fmt" 167 | ``` 168 | 169 | * `fmt` is the name of the target in `./fuzz/Cargo.toml` 170 | 171 | Fuzzer will run indefinitely or until it finds a crash. 172 | The crashing input is written to `fuzz/artifacts` directory. 173 | Commit this `crash-` file, and it will be automatically tested by a unit-test. 174 | 175 | ## Documentation 176 | 177 | * [HOWTO write new rules](docs/howto_rules.md) 178 | * [HOWTO WASM](wasm/README.md) 179 | * [How we do releases](docs/releasing.md) 180 | 181 | ## Related projects 182 | 183 | Feel free to submit your project! 184 | 185 | ### Using nixpkgs-fmt 186 | 187 | * [Emacs integration, including minor mode for format-on-save](https://github.com/purcell/emacs-nixpkgs-fmt) 188 | * [rnix-lsp](https://github.com/nix-community/rnix-lsp) - A Lambda Server for Nix 189 | 190 | 191 | ### Formatters 192 | 193 | * [alejandra](https://github.com/kamadorueda/alejandra) - Another rnix based formatter, using a rule based engine. 194 | * [canonix](https://github.com/hercules-ci/canonix/) - Nix formatter prototype written in Haskell using the tree-sitter-nix grammar. 195 | * [format-nix](https://github.com/justinwoo/format-nix/) - A nix formatter using tree-sitter-nix. 196 | * [nix-format](https://github.com/taktoa/nix-format) - Emacs-based Nix formatter. 197 | * [nix-lsp](https://gitlab.com/jD91mZM2/nix-lsp) - Nix language server using rnix. 198 | * [nixfmt](https://github.com/serokell/nixfmt) - A nix formatter written in Haskell. 199 | 200 | ### Linters 201 | 202 | * [nix-linter](https://github.com/Synthetica9/nix-linter) 203 | 204 | ### Parsers 205 | 206 | * [hnix](https://github.com/haskell-nix/hnix) - Haskell implementation of Nix including a parser. The parser is not comment-preserving. 207 | * [rnix](https://github.com/nix-community/rnix-parser) - Rust Nix parser based on [rowan](https://github.com/rust-analyzer/rowan) 208 | * [tree-sitter-nix](https://github.com/cstrahan/tree-sitter-nix) - Tree Sitter is a forgiving parser used by Atom for on-the-fly syntax highlighting and others. This is a implementation for Nix. 209 | 210 | ## Discussions 211 | 212 | * [nixpkgs style guide](https://nixos.org/nixpkgs/manual/#sec-syntax) 213 | * [On Nix expression formatting](https://discourse.nixos.org/t/on-nix-expression-formatting/1521/14) 214 | * [[Job] Implement a `nix-fmt` formatter](https://discourse.nixos.org/t/job-implement-a-nix-fmt-formatter/2819/12) 215 | 216 | ## Sponsors 217 | 218 | This work has been sponsored by [NumTide](https://numtide.com). 219 | 220 | NumTide Logo 221 | -------------------------------------------------------------------------------- /bors.toml: -------------------------------------------------------------------------------- 1 | status = [ 2 | "build", 3 | ] 4 | delete_merged_branches = true 5 | -------------------------------------------------------------------------------- /ci.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-shell 2 | #!nix-shell -i bash 3 | # 4 | # This is the script executed by CI 5 | set -euo pipefail 6 | 7 | ## Functions ## 8 | 9 | run() { 10 | echo >&2 11 | echo "$ ${*@Q}" >&2 12 | "$@" 13 | } 14 | 15 | ## Main ## 16 | 17 | mkdir -p "${TMPDIR}" 18 | 19 | # build nixpkgs-fmt with nix 20 | run nix-build . 21 | 22 | # run after build, pre-commit needs nixpkgs-fmt 23 | run pre-commit run --all-files 24 | 25 | # run the tests 26 | run cargo test --verbose 27 | 28 | # generate the webassembly page 29 | run ./wasm/build.sh 30 | 31 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { system ? builtins.currentSystem 2 | , inputs ? import ./flake.lock.nix { } 3 | }: 4 | let 5 | nixpkgs = import inputs.nixpkgs { 6 | inherit system; 7 | config = { }; 8 | overlays = [ 9 | (final: prev: { 10 | fenix = import inputs.fenix { 11 | pkgs = prev; 12 | rust-analyzer-src = throw "not used"; 13 | }; 14 | }) 15 | ]; 16 | }; 17 | 18 | rustToolchain = with nixpkgs.fenix; 19 | combine [ 20 | minimal.rustc 21 | minimal.cargo 22 | targets."wasm32-unknown-unknown".latest.rust-std 23 | ]; 24 | 25 | # This is a magic shell that can be both built and loaded as a nix-shell. 26 | mkShell = { name ? "shell", packages ? [ ], shellHook ? "" }: 27 | let 28 | drv = nixpkgs.buildEnv { 29 | inherit name; 30 | # TODO: also add the shellHook as an activation script? 31 | paths = packages; 32 | }; 33 | in 34 | drv.overrideAttrs (old: { 35 | nativeBuildInputs = old.nativeBuildInputs ++ packages; 36 | inherit shellHook; 37 | }); 38 | 39 | cargoToml = with builtins; (fromTOML (readFile ./Cargo.toml)); 40 | in 41 | rec { 42 | inherit nixpkgs; 43 | 44 | nixpkgs-fmt = nixpkgs.pkgs.rustPlatform.buildRustPackage { 45 | inherit (cargoToml.package) name version; 46 | 47 | src = nixpkgs.lib.cleanSource ./.; 48 | 49 | doCheck = true; 50 | 51 | cargoLock.lockFile = ./Cargo.lock; 52 | }; 53 | 54 | # This used to be the output when we were using flake-compat. 55 | defaultNix = nixpkgs-fmt; 56 | 57 | devShell = mkShell { 58 | packages = [ 59 | nixpkgs.cargo-fuzz 60 | nixpkgs.gitAndTools.git-extras 61 | nixpkgs.gitAndTools.pre-commit 62 | nixpkgs.mdsh 63 | nixpkgs.openssl 64 | nixpkgs.pkgconfig 65 | nixpkgs.stdenv.cc 66 | nixpkgs.wasm-pack 67 | rustToolchain 68 | ] 69 | ++ nixpkgs.lib.optionals nixpkgs.stdenv.isDarwin [ 70 | nixpkgs.darwin.apple_sdk.frameworks.Security 71 | ] 72 | ; 73 | 74 | shellHook = '' 75 | export PATH=$PWD/target/debug:$PATH 76 | ''; 77 | }; 78 | } 79 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Make sure to run wasm/build.sh before running this script 4 | set -euo pipefail 5 | 6 | ## Functions ## 7 | 8 | run() { 9 | echo 10 | echo "$ ${*@Q}" >&2 11 | "$@" 12 | } 13 | 14 | ## Main ## 15 | 16 | origin_url=git@github.com:nix-community/nixpkgs-fmt.git 17 | workdir=$(mktemp -d) 18 | cleanup() { 19 | rm -rf "$workdir" 20 | } 21 | trap cleanup EXIT 22 | 23 | if [[ -n "${DEPLOY_SSH_KEY:-}" ]]; then 24 | echo "DEPLOY_SSH_KEY found" 25 | ssh_key_path=$workdir/ssh_key 26 | echo "$DEPLOY_SSH_KEY" | base64 -d > "$ssh_key_path" 27 | run chmod 0600 "$ssh_key_path" 28 | export GIT_SSH_COMMAND="ssh -i '$ssh_key_path'" 29 | fi 30 | 31 | run git clone --depth=1 --branch=gh-pages "$origin_url" "$workdir/repo" 32 | 33 | run rsync -rl --exclude .git --delete wasm/ "$workdir/repo" 34 | 35 | run rm -f "$workdir/repo/pkg/.gitignore" 36 | 37 | run cd "$workdir/repo" 38 | 39 | run git add -A . 40 | 41 | if run git diff-index --quiet --cached HEAD -- ; then 42 | echo "Found no changes to publish, exiting" 43 | # no staged changes found 44 | exit 45 | fi 46 | 47 | run git config user.name "CI" 48 | run git config user.email "ci@ci.com" 49 | run git commit --message "." 50 | run git show --stat-count=10 HEAD 51 | run git push -f origin gh-pages 52 | -------------------------------------------------------------------------------- /docs/howto_rules.md: -------------------------------------------------------------------------------- 1 | # HOWTO write new rules 2 | 3 | This document explains the overall process one ususually go through for adding 4 | new rules. 5 | 6 | ## Create the nix code fixtures 7 | 8 | In the `test_data` folder, create two new fixtures for the expected input and 9 | outputs of the tool. 10 | 11 | Eg: `test_data/fn_args_singleline.bad.nix` and `test_data/fn_args_singleline.good.nix` 12 | 13 | Put the content in there and format the files by hand. 14 | 15 | Running `cargo test` should fail if the fixtures aren't supported by the 16 | current rewriting rules. 17 | 18 | At this point, a PR can already be sent to demonstrate the failure. 19 | 20 | ## Create the new rule 21 | 22 | Run the tool with `--parse` to learn the names of nodes were are interested in: 23 | 24 | ``` 25 | $ cargo run -- --parse test_data/fn_args_singleline.bad.nix 26 | 27 | NODE_ROOT 0..25 { 28 | NODE_LAMBDA 0..24 { 29 | NODE_PATTERN 0..17 { <- we need this one 30 | TOKEN_CURLY_B_OPEN("{") 0..1 31 | NODE_PAT_ENTRY 1..7 { 32 | NODE_IDENT 1..7 { 33 | TOKEN_IDENT("stdenv") 1..7 34 | } 35 | } 36 | .... 37 | ``` 38 | 39 | Then add the spacing rules in `rules.rs`, together with the inline test: 40 | 41 | ``` 42 | // {arg}: 92 => { arg }: 92 43 | .inside(NODE_PATTERN).after(T!['{']).single_space_or_newline() 44 | .inside(NODE_PATTERN).before(T!['}']).single_space_or_newline() 45 | ``` 46 | 47 | At this point is makes sense to learn a bit more about rowan, the internals of 48 | the project, and this should definitely be covered in another document. Until 49 | then, please ping @matklad with any questions you might have. 50 | 51 | Iterate until it works. 52 | 53 | Push to the PR with the fixes. 54 | 55 | ## Success 56 | 57 | Thanks for helping out! 58 | -------------------------------------------------------------------------------- /docs/releasing.md: -------------------------------------------------------------------------------- 1 | # Releasing 2 | 3 | This document describes the steps taken to release new versions of this 4 | project. 5 | 6 | 1. Fill the CHANGELOG.md file. 7 | - Use `git-changelog` to automatically generate the changelog from commit messages. 8 | You can install this from the [`git-extras`](https://github.com/tj/git-extras) package. 9 | 2. Bump the version in Cargo.toml 10 | 3. Run `./update-cargo-nix.sh` to update the Cargo.lock and associated nix 11 | files 12 | 4. Create a release commit: `git commit -a -m "Release v"` 13 | 5. Tag the release: `git tag v` 14 | 6. Push all of this: `git push --follow-tags` 15 | 7. Run `cargo publish` 16 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "fenix": { 4 | "inputs": { 5 | "nixpkgs": [ 6 | "nixpkgs" 7 | ], 8 | "rust-analyzer-src": "rust-analyzer-src" 9 | }, 10 | "locked": { 11 | "lastModified": 1637475807, 12 | "narHash": "sha256-E3nzOvlzZXwyo8Stp5upKsTCDcqUTYAFj4EC060A31c=", 13 | "owner": "nix-community", 14 | "repo": "fenix", 15 | "rev": "960e7fef45692a4fffc6df6d6b613b0399bbdfd5", 16 | "type": "github" 17 | }, 18 | "original": { 19 | "owner": "nix-community", 20 | "repo": "fenix", 21 | "type": "github" 22 | } 23 | }, 24 | "flake-utils": { 25 | "locked": { 26 | "lastModified": 1637014545, 27 | "narHash": "sha256-26IZAc5yzlD9FlDT54io1oqG/bBoyka+FJk5guaX4x4=", 28 | "owner": "numtide", 29 | "repo": "flake-utils", 30 | "rev": "bba5dcc8e0b20ab664967ad83d24d64cb64ec4f4", 31 | "type": "github" 32 | }, 33 | "original": { 34 | "owner": "numtide", 35 | "repo": "flake-utils", 36 | "type": "github" 37 | } 38 | }, 39 | "nixpkgs": { 40 | "locked": { 41 | "lastModified": 1637502770, 42 | "narHash": "sha256-C28tuj+AgsRh67iB/Lg9oladquLoC8eamraqndeaO4A=", 43 | "owner": "NixOS", 44 | "repo": "nixpkgs", 45 | "rev": "f508ae889415b51263ea1c20b6b4c0e0ecbfc0bd", 46 | "type": "github" 47 | }, 48 | "original": { 49 | "id": "nixpkgs", 50 | "type": "indirect" 51 | } 52 | }, 53 | "root": { 54 | "inputs": { 55 | "fenix": "fenix", 56 | "flake-utils": "flake-utils", 57 | "nixpkgs": "nixpkgs" 58 | } 59 | }, 60 | "rust-analyzer-src": { 61 | "flake": false, 62 | "locked": { 63 | "lastModified": 1637439871, 64 | "narHash": "sha256-2awQ/obzl7zqYgLwbQL0zT58gN8Xq7n+81GcMiS595I=", 65 | "owner": "rust-analyzer", 66 | "repo": "rust-analyzer", 67 | "rev": "4566414789310acb2617543f4b50beab4bb48e06", 68 | "type": "github" 69 | }, 70 | "original": { 71 | "owner": "rust-analyzer", 72 | "ref": "nightly", 73 | "repo": "rust-analyzer", 74 | "type": "github" 75 | } 76 | } 77 | }, 78 | "root": "root", 79 | "version": 7 80 | } 81 | -------------------------------------------------------------------------------- /flake.lock.nix: -------------------------------------------------------------------------------- 1 | # Adapted from https://github.com/edolstra/flake-compat/blob/master/default.nix 2 | # 3 | # This version only gives back the inputs. In that mode, flake becomes little 4 | # more than a niv replacement. 5 | { src ? ./. }: 6 | let 7 | lockFilePath = src + "/flake.lock"; 8 | 9 | lockFile = builtins.fromJSON (builtins.readFile lockFilePath); 10 | 11 | # Emulate builtins.fetchTree 12 | # 13 | # TODO: only implement polyfill if the builtin doesn't exist? 14 | fetchTree = 15 | info: 16 | if info.type == "github" then 17 | { 18 | outPath = fetchTarball { 19 | url = "https://api.${info.host or "github.com"}/repos/${info.owner}/${info.repo}/tarball/${info.rev}"; 20 | sha256 = info.narHash; 21 | }; 22 | rev = info.rev; 23 | shortRev = builtins.substring 0 7 info.rev; 24 | lastModified = info.lastModified; 25 | narHash = info.narHash; 26 | } 27 | else if info.type == "git" then 28 | { 29 | outPath = 30 | builtins.fetchGit 31 | ({ url = info.url; sha256 = info.narHash; } 32 | // (if info ? rev then { inherit (info) rev; } else { }) 33 | // (if info ? ref then { inherit (info) ref; } else { }) 34 | ); 35 | lastModified = info.lastModified; 36 | narHash = info.narHash; 37 | } // (if info ? rev then { 38 | rev = info.rev; 39 | shortRev = builtins.substring 0 7 info.rev; 40 | } else { }) 41 | else if info.type == "path" then 42 | { 43 | outPath = builtins.path { path = info.path; }; 44 | narHash = info.narHash; 45 | } 46 | else if info.type == "tarball" then 47 | { 48 | outPath = fetchTarball { 49 | url = info.url; 50 | sha256 = info.narHash; 51 | }; 52 | narHash = info.narHash; 53 | } 54 | else if info.type == "gitlab" then 55 | { 56 | inherit (info) rev narHash lastModified; 57 | outPath = fetchTarball { 58 | url = "https://${info.host or "gitlab.com"}/api/v4/projects/${info.owner}%2F${info.repo}/repository/archive.tar.gz?sha=${info.rev}"; 59 | sha256 = info.narHash; 60 | }; 61 | shortRev = builtins.substring 0 7 info.rev; 62 | } 63 | else 64 | # FIXME: add Mercurial, tarball inputs. 65 | throw "flake input has unsupported input type '${info.type}'"; 66 | 67 | allNodes = 68 | builtins.mapAttrs 69 | (key: node: 70 | let 71 | sourceInfo = 72 | if key == lockFile.root 73 | then { } 74 | else fetchTree (node.info or { } // removeAttrs node.locked [ "dir" ]); 75 | 76 | inputs = builtins.mapAttrs 77 | (inputName: inputSpec: allNodes.${resolveInput inputSpec}) 78 | (node.inputs or { }); 79 | 80 | # Resolve a input spec into a node name. An input spec is 81 | # either a node name, or a 'follows' path from the root 82 | # node. 83 | resolveInput = inputSpec: 84 | if builtins.isList inputSpec 85 | then getInputByPath lockFile.root inputSpec 86 | else inputSpec; 87 | 88 | # Follow an input path (e.g. ["dwarffs" "nixpkgs"]) from the 89 | # root node, returning the final node. 90 | getInputByPath = nodeName: path: 91 | if path == [ ] 92 | then nodeName 93 | else 94 | getInputByPath 95 | # Since this could be a 'follows' input, call resolveInput. 96 | (resolveInput lockFile.nodes.${nodeName}.inputs.${builtins.head path}) 97 | (builtins.tail path); 98 | 99 | result = sourceInfo // { inherit inputs; inherit sourceInfo; }; 100 | in 101 | if node.flake or true then 102 | result 103 | else 104 | sourceInfo 105 | ) 106 | lockFile.nodes; 107 | 108 | result = 109 | if lockFile.version >= 5 && lockFile.version <= 7 110 | then allNodes.${lockFile.root}.inputs 111 | else throw "lock file '${lockFilePath}' has unsupported version ${toString lockFile.version}"; 112 | 113 | in 114 | result 115 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "nixpkgs-fmt"; 3 | 4 | inputs.flake-utils.url = "github:numtide/flake-utils"; 5 | inputs.fenix.url = "github:nix-community/fenix"; 6 | inputs.fenix.inputs.nixpkgs.follows = "nixpkgs"; 7 | 8 | outputs = { self, nixpkgs, fenix, flake-utils }@inputs: 9 | flake-utils.lib.eachDefaultSystem (system: 10 | let pkgs = import ./. { inherit system inputs; }; in 11 | { 12 | defaultPackage = pkgs.nixpkgs-fmt; 13 | legacyPackages = pkgs; 14 | devShell = pkgs.devShell; 15 | } 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | corpus 4 | -------------------------------------------------------------------------------- /fuzz/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "ansi_term" 5 | version = "0.11.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 9 | ] 10 | 11 | [[package]] 12 | name = "arbitrary" 13 | version = "0.1.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | 16 | [[package]] 17 | name = "atty" 18 | version = "0.2.13" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | dependencies = [ 21 | "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", 22 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 23 | ] 24 | 25 | [[package]] 26 | name = "autocfg" 27 | version = "0.1.5" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | 30 | [[package]] 31 | name = "bitflags" 32 | version = "1.1.0" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | 35 | [[package]] 36 | name = "byteorder" 37 | version = "1.3.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | 40 | [[package]] 41 | name = "cbitset" 42 | version = "0.2.0" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | dependencies = [ 45 | "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 46 | ] 47 | 48 | [[package]] 49 | name = "cc" 50 | version = "1.0.38" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | 53 | [[package]] 54 | name = "clap" 55 | version = "2.33.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | dependencies = [ 58 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 59 | "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 60 | "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 61 | "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 62 | "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 63 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 64 | "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 65 | ] 66 | 67 | [[package]] 68 | name = "libc" 69 | version = "0.2.60" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | 72 | [[package]] 73 | name = "libfuzzer-sys" 74 | version = "0.1.0" 75 | source = "git+https://github.com/rust-fuzz/libfuzzer-sys.git#4a413199b5cb1bbed6a1d157b2342b925f8464ac" 76 | dependencies = [ 77 | "arbitrary 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 78 | "cc 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)", 79 | ] 80 | 81 | [[package]] 82 | name = "nixpkgs-fmt" 83 | version = "0.3.1" 84 | dependencies = [ 85 | "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", 86 | "rnix 0.6.0 (git+https://gitlab.com/jD91mZM2/rnix.git)", 87 | ] 88 | 89 | [[package]] 90 | name = "nixpkgs-fmt-fuzz" 91 | version = "0.0.1" 92 | dependencies = [ 93 | "libfuzzer-sys 0.1.0 (git+https://github.com/rust-fuzz/libfuzzer-sys.git)", 94 | "nixpkgs-fmt 0.3.1", 95 | ] 96 | 97 | [[package]] 98 | name = "num-traits" 99 | version = "0.2.8" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | dependencies = [ 102 | "autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 103 | ] 104 | 105 | [[package]] 106 | name = "rnix" 107 | version = "0.6.0" 108 | source = "git+https://gitlab.com/jD91mZM2/rnix.git#5ab05a891509f22414f499b34da38423c46e929b" 109 | dependencies = [ 110 | "cbitset 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 111 | "rowan 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 112 | ] 113 | 114 | [[package]] 115 | name = "rowan" 116 | version = "0.6.1" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | dependencies = [ 119 | "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 120 | "smol_str 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 121 | "text_unit 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 122 | ] 123 | 124 | [[package]] 125 | name = "rustc-hash" 126 | version = "1.0.1" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | dependencies = [ 129 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 130 | ] 131 | 132 | [[package]] 133 | name = "smol_str" 134 | version = "0.1.12" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | 137 | [[package]] 138 | name = "strsim" 139 | version = "0.8.0" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | 142 | [[package]] 143 | name = "text_unit" 144 | version = "0.1.9" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | 147 | [[package]] 148 | name = "textwrap" 149 | version = "0.11.0" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | dependencies = [ 152 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 153 | ] 154 | 155 | [[package]] 156 | name = "unicode-width" 157 | version = "0.1.5" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | 160 | [[package]] 161 | name = "vec_map" 162 | version = "0.8.1" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | 165 | [[package]] 166 | name = "winapi" 167 | version = "0.3.7" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | dependencies = [ 170 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 171 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 172 | ] 173 | 174 | [[package]] 175 | name = "winapi-i686-pc-windows-gnu" 176 | version = "0.4.0" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | 179 | [[package]] 180 | name = "winapi-x86_64-pc-windows-gnu" 181 | version = "0.4.0" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | 184 | [metadata] 185 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 186 | "checksum arbitrary 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c7d1523aa3a127adf8b27af2404c03c12825b4c4d0698f01648d63fa9df62ee" 187 | "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" 188 | "checksum autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "22130e92352b948e7e82a49cdb0aa94f2211761117f29e052dd397c1ac33542b" 189 | "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" 190 | "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" 191 | "checksum cbitset 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "29b6ad25ae296159fb0da12b970b2fe179b234584d7cd294c891e2bbb284466b" 192 | "checksum cc 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)" = "ce400c638d48ee0e9ab75aef7997609ec57367ccfe1463f21bf53c3eca67bf46" 193 | "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" 194 | "checksum libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "d44e80633f007889c7eff624b709ab43c92d708caad982295768a7b13ca3b5eb" 195 | "checksum libfuzzer-sys 0.1.0 (git+https://github.com/rust-fuzz/libfuzzer-sys.git)" = "" 196 | "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" 197 | "checksum rnix 0.6.0 (git+https://gitlab.com/jD91mZM2/rnix.git)" = "" 198 | "checksum rowan 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03e34c2e5f01d7fa4ab7e6a49da44f59fb38ffb61e6c9f714deb8e157274c2c7" 199 | "checksum rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7540fc8b0c49f096ee9c961cda096467dce8084bec6bdca2fc83895fd9b28cb8" 200 | "checksum smol_str 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "590700be3630457c56f8c73c0ea39881476ad7076cd84057d44f4f38f79914fb" 201 | "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 202 | "checksum text_unit 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e08bbcb7a3adbda0eb23431206b653bdad3d8dea311e72d36bf2215e27a42579" 203 | "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 204 | "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" 205 | "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 206 | "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" 207 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 208 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 209 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nixpkgs-fmt-fuzz" 3 | version = "0.0.1" 4 | authors = [] 5 | publish = false 6 | edition = "2018" 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies] 12 | nixpkgs-fmt = { path = "../" } 13 | libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer-sys.git" } 14 | 15 | # Prevent this from interfering with workspaces 16 | [workspace] 17 | members = ["."] 18 | 19 | [[bin]] 20 | name = "fmt" 21 | path = "fuzz_targets/fmt.rs" 22 | -------------------------------------------------------------------------------- /fuzz/artifacts/fmt/crash-0bdada2399fbe428ba729b8a87268e6670e5c07b: -------------------------------------------------------------------------------- 1 | }{{ 2 | {N -------------------------------------------------------------------------------- /fuzz/artifacts/fmt/crash-33dad1919de59d6e9ab676817db5db4642bcf212: -------------------------------------------------------------------------------- 1 | ]. 2 | -------------------------------------------------------------------------------- /fuzz/artifacts/fmt/crash-a37db01a12a3c74202ac13360069e732af701c7e: -------------------------------------------------------------------------------- 1 | noth b vo jpkgs*eveth b vo jver ndntxed ? imporon./nbxed ? imporon./n 2 | Ga 3 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/fmt.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | #[macro_use] 4 | extern crate libfuzzer_sys; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | if let Ok(text) = std::str::from_utf8(data) { 8 | nixpkgs_fmt::reformat_string(text); 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | reorder_modules = false 2 | use_small_heuristics = "Max" 3 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { system ? builtins.currentSystem }: 2 | (import ./. { inherit system; }).devShell 3 | -------------------------------------------------------------------------------- /src/engine.rs: -------------------------------------------------------------------------------- 1 | //! This module applies the rules from `super::dsl` to a `SyntaxNode`, to 2 | //! get a `FmtDiff`. 3 | mod fmt_model; 4 | mod indentation; 5 | mod spacing; 6 | mod fixes; 7 | 8 | use rnix::{SyntaxNode, TextRange}; 9 | use smol_str::SmolStr; 10 | 11 | use crate::{ 12 | dsl::{IndentDsl, RuleName, SpacingDsl}, 13 | engine::fmt_model::{BlockPosition, FmtModel, SpaceBlock, SpaceBlockOrToken}, 14 | pattern::PatternSet, 15 | tree_utils::walk_non_whitespace_non_interpol, 16 | AtomEdit, FmtDiff, 17 | }; 18 | 19 | pub(crate) enum ExtraInfo<'a> { 20 | // Explanation contains a vector of edits, each of which is optionally paired with the rule name 21 | // that caused the edit. 22 | Explanation(&'a mut Vec<(AtomEdit, Option)>), 23 | /// Edits contains spacing edits to be applied to the document and indent edits to be applied in 24 | /// a second, separate transaction. 25 | Edits { 26 | spacing_edits: &'a mut Vec, 27 | indent_edits: &'a mut Vec, 28 | }, 29 | None, 30 | } 31 | 32 | /// The main entry point for formatting 33 | pub(crate) fn reformat( 34 | spacing_dsl: &SpacingDsl, 35 | indent_dsl: &IndentDsl, 36 | node: &SyntaxNode, 37 | // Passing this enum is just a cute type-safe way for the caller to 38 | // select what extra info they need. 39 | mut extra_info: ExtraInfo, 40 | ) -> SyntaxNode { 41 | // First, adjust spacing rules between the nodes. 42 | // This can force some newlines. 43 | let mut model = FmtModel::new(node.clone()); 44 | 45 | // First, adjust spacing rules between the nodes. 46 | // This can force some newlines. 47 | let spacing_rule_set = PatternSet::new(spacing_dsl.rules.iter()); 48 | for element in walk_non_whitespace_non_interpol(node) { 49 | for rule in spacing_rule_set.matching(element.clone()) { 50 | rule.apply(&element, &mut model) 51 | } 52 | } 53 | 54 | let spacing_diff = model.into_diff(); 55 | if let ExtraInfo::Explanation(explanation) = &mut extra_info { 56 | if spacing_diff.has_changes() { 57 | explanation.extend(spacing_diff.edits.clone()) 58 | } 59 | } else if let ExtraInfo::Edits { spacing_edits, .. } = &mut extra_info { 60 | spacing_edits 61 | .extend(spacing_diff.edits.iter().map(|(ae, _)| ae.clone()).collect::>()); 62 | } 63 | let node = spacing_diff.to_node(); 64 | 65 | // Next, for each node which starts the newline, adjust the indent. 66 | let mut model = FmtModel::new(node.clone()); 67 | 68 | let anchor_set = PatternSet::new(indent_dsl.anchors.iter()); 69 | for element in walk_non_whitespace_non_interpol(&node) { 70 | let block = model.block_for(&element, BlockPosition::Before); 71 | if !block.has_newline() { 72 | // No need to indent an element if it doesn't start a line 73 | continue; 74 | } 75 | // In cases like 76 | // 77 | // ```nix 78 | // param: 79 | // body 80 | // ``` 81 | // 82 | // we only indent top-level node (lambda), and not it's first child (parameter) 83 | // TODO: Remove it when refactoring indentation engine. 84 | if element.parent().map(|it| it.text_range().start()) == Some(element.text_range().start()) 85 | { 86 | continue; 87 | } 88 | 89 | let mut matching = indent_dsl.rules.iter().filter(|it| it.matches(&element)); 90 | if let Some(rule) = matching.next() { 91 | rule.apply(&element, &mut model, &anchor_set); 92 | assert!(matching.next().is_none(), "more that one indent rule matched"); 93 | } else { 94 | indentation::default_indent(&element, &mut model, &anchor_set) 95 | } 96 | } 97 | 98 | // Finally, do custom touch-ups like re-indenting of string literals and 99 | // replacing URLs with string literals. 100 | for element in walk_non_whitespace_non_interpol(&node) { 101 | fixes::fix(element, &mut model, &anchor_set) 102 | } 103 | 104 | let indent_diff = model.into_diff(); 105 | if let ExtraInfo::Explanation(explanation) = extra_info { 106 | // We don't add indentation explanations if we had whitespace changes, 107 | // as that'll require fixing up the original ranges. This could be done, 108 | // but it's not clear if it is really necessary. 109 | if indent_diff.has_changes() && explanation.is_empty() { 110 | explanation.extend(indent_diff.edits.clone()) 111 | } 112 | } else if let ExtraInfo::Edits { indent_edits, .. } = extra_info { 113 | indent_edits.extend(indent_diff.edits.iter().map(|(ae, _)| ae.clone()).collect::>()); 114 | } 115 | indent_diff.to_node() 116 | } 117 | 118 | impl FmtDiff { 119 | fn replace(&mut self, range: TextRange, text: SmolStr, reason: Option) { 120 | self.edits.push((AtomEdit { delete: range, insert: text }, reason)) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/engine/fixes.rs: -------------------------------------------------------------------------------- 1 | use std::{cmp::min, convert::TryFrom}; 2 | 3 | use rnix::{ 4 | NodeOrToken, SyntaxElement, 5 | SyntaxKind::{NODE_STRING, TOKEN_COMMENT, TOKEN_STRING_CONTENT, TOKEN_WHITESPACE}, 6 | SyntaxNode, SyntaxToken, TextRange, TextSize, 7 | }; 8 | 9 | use super::indentation::single_line_comment_indent; 10 | use crate::{ 11 | engine::{ 12 | indentation::{indent_anchor, IndentLevel}, 13 | BlockPosition, FmtModel, 14 | }, 15 | pattern::{Pattern, PatternSet}, 16 | AtomEdit, 17 | }; 18 | 19 | pub(super) fn fix(element: SyntaxElement, model: &mut FmtModel, anchor_set: &PatternSet<&Pattern>) { 20 | match element { 21 | NodeOrToken::Node(node) => { 22 | if let NODE_STRING = node.kind() { 23 | fix_string_indentation(&node, model, anchor_set) 24 | } 25 | } 26 | NodeOrToken::Token(token) => { 27 | if let TOKEN_COMMENT = token.kind() { 28 | fix_comment_indentation(&token, model, anchor_set) 29 | } 30 | } 31 | } 32 | } 33 | 34 | fn fix_string_indentation( 35 | node: &SyntaxNode, 36 | model: &mut FmtModel, 37 | anchor_set: &PatternSet<&Pattern>, 38 | ) { 39 | let quote_indent = { 40 | let element: SyntaxElement = node.clone().into(); 41 | let block = model.block_for(&element, BlockPosition::Before); 42 | if block.text().contains('\n') { 43 | IndentLevel::from_whitespace_block(block.text()) 44 | } else { 45 | match indent_anchor(&element, model, anchor_set) { 46 | None => return, 47 | Some((_element, indent)) => indent, 48 | } 49 | } 50 | }; 51 | let content_indent = quote_indent.indent(); 52 | 53 | let indent_ranges: Vec = node_indent_ranges(node).collect(); 54 | 55 | let (first_indent, last_indent) = match (indent_ranges.first(), indent_ranges.last()) { 56 | (Some(first), Some(last)) => (first, last), 57 | _ => return, 58 | }; 59 | 60 | let first_line_is_blank = 61 | first_indent.start() == node.text_range().start() + TextSize::of("''\n"); 62 | 63 | let last_line_is_blank = last_indent.end() + TextSize::of("''") == node.text_range().end(); 64 | 65 | if !first_line_is_blank { 66 | return; 67 | } 68 | 69 | let content_ranges = 70 | if last_line_is_blank { &indent_ranges[..indent_ranges.len() - 1] } else { &indent_ranges }; 71 | 72 | let common_indent = match content_ranges.iter().map(|it| it.len()).min() { 73 | Some(it) => it, 74 | None => return, 75 | }; 76 | 77 | if content_indent != IndentLevel::from_len(common_indent) { 78 | for &range in content_ranges.iter() { 79 | let delete = TextRange::at(range.start(), min(common_indent, range.len())); 80 | model.raw_edit(AtomEdit { delete, insert: content_indent.into() }) 81 | } 82 | } 83 | 84 | if last_line_is_blank && last_indent.len() != quote_indent.len() { 85 | model.raw_edit(AtomEdit { delete: *last_indent, insert: quote_indent.into() }) 86 | } 87 | } 88 | 89 | /// If we indent multiline block comment, we should indent it's content as well. 90 | fn fix_comment_indentation( 91 | token: &SyntaxToken, 92 | model: &mut FmtModel, 93 | anchor_set: &PatternSet<&Pattern>, 94 | ) { 95 | let is_block_comment = token.text().starts_with("/*"); 96 | let normal_indent = match indent_anchor(&token.clone().into(), model, anchor_set) { 97 | None => return, 98 | Some((_element, indent)) => indent, 99 | }; 100 | let block = model.block_for(&token.clone().into(), BlockPosition::Before); 101 | if !is_block_comment { 102 | single_line_comment_indent(token, model, anchor_set); 103 | return; 104 | } 105 | 106 | let comment_indent = { 107 | if block.text().contains('\n') { 108 | IndentLevel::from_whitespace_block(block.text()) 109 | } else { 110 | normal_indent 111 | } 112 | }; 113 | 114 | let content_indent = comment_indent.indent(); 115 | let mut curr_offset = token.text_range().start(); 116 | let mut first = true; 117 | for line in token.text().lines() { 118 | let offset = curr_offset; 119 | curr_offset += TextSize::of(line) + TextSize::of('\n'); 120 | if first { 121 | first = false; 122 | continue; 123 | } 124 | let last_line_only_end_block = line.ends_with("*/") || line.trim_start() == "*/"; 125 | let start_with_asterisk = line.trim_start().starts_with("*"); 126 | let current_indent = IndentLevel::get_whitespace_block(line); 127 | if let Some(ws_end) = line.find(|it| it != ' ') { 128 | let delete = 129 | TextRange::at(offset, TextSize::try_from(ws_end).expect("woah big number")); 130 | if last_line_only_end_block || start_with_asterisk { 131 | model.raw_edit(AtomEdit { 132 | delete, 133 | insert: comment_indent.add_alignment(current_indent).into(), 134 | }) 135 | } else { 136 | model.raw_edit(AtomEdit { 137 | delete, 138 | insert: content_indent.adjust_alignment(current_indent).into(), 139 | }) 140 | } 141 | } 142 | } 143 | } 144 | 145 | /// For indented string like 146 | /// 147 | /// ```nix 148 | /// '' 149 | /// hello 150 | /// world 151 | /// '' 152 | /// ``` 153 | /// 154 | /// returns the ranges, corresponding to indentation. That is `" "` before 155 | /// hello, `" "` before world and `""` before the last `''`. 156 | fn node_indent_ranges(indented_string: &SyntaxNode) -> impl Iterator { 157 | indented_string 158 | .descendants_with_tokens() 159 | .filter_map(|it| it.into_token()) 160 | .filter(|it| it.kind() == TOKEN_STRING_CONTENT || it.kind() == TOKEN_WHITESPACE) 161 | .flat_map(|string_bit| { 162 | let start_offset = string_bit.text_range().start(); 163 | string_indent_ranges(string_bit.text()) 164 | .into_iter() 165 | .map(move |range| range + start_offset) 166 | }) 167 | } 168 | 169 | fn string_indent_ranges(mut s: &str) -> Vec { 170 | let mut offset = 0; 171 | std::iter::from_fn(move || loop { 172 | let indent_start = s.find('\n')? + 1; 173 | s = &s[indent_start..]; 174 | offset += indent_start; 175 | 176 | let indent_len = s.find(|c| c != ' ').unwrap_or_else(|| s.len()); 177 | s = &s[indent_len..]; 178 | offset += indent_len; 179 | if s.starts_with('\n') { 180 | continue; 181 | } 182 | 183 | return Some(TextRange::new( 184 | TextSize::try_from(offset - indent_len).expect("woah big numbers"), 185 | TextSize::try_from(offset).expect("woah big numbers"), 186 | )); 187 | }) 188 | .collect() 189 | } 190 | 191 | #[cfg(test)] 192 | mod tests { 193 | use super::*; 194 | 195 | #[test] 196 | fn test_node_indent_ranges() { 197 | let text = r#"{ 198 | python = 199 | '' 200 | for i in range(10): 201 | print(i) 202 | ''; 203 | }"#; 204 | 205 | let ast = rnix::parse(text); 206 | let node = crate::tree_utils::walk(&ast.node()) 207 | .filter_map(|it| it.into_node()) 208 | .find(|node| node.kind() == NODE_STRING) 209 | .unwrap(); 210 | let indent_ranges: Vec = node_indent_ranges(&node).collect(); 211 | assert_eq!( 212 | indent_ranges, 213 | vec![ 214 | TextRange::new(20.into(), 24.into()), 215 | TextRange::new(44.into(), 52.into()), 216 | TextRange::new(61.into(), 65.into()), 217 | ] 218 | ); 219 | 220 | let text = r#"{ 221 | python = 222 | ''python 223 | for i in range(${range}): 224 | print(i) 225 | ''; 226 | }"#; 227 | 228 | let ast = rnix::parse(text); 229 | let node = crate::tree_utils::walk(&ast.node()) 230 | .filter_map(|it| it.into_node()) 231 | .find(|node| node.kind() == NODE_STRING) 232 | .unwrap(); 233 | let indent_ranges: Vec = node_indent_ranges(&node).collect(); 234 | assert_eq!( 235 | indent_ranges, 236 | vec![ 237 | TextRange::new(26.into(), 30.into()), 238 | TextRange::new(56.into(), 64.into()), 239 | TextRange::new(73.into(), 77.into()), 240 | ] 241 | ); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/engine/fmt_model.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use rnix::{ 4 | NodeOrToken, SyntaxElement, 5 | SyntaxKind::{NODE_ROOT, TOKEN_COMMENT, TOKEN_WHITESPACE}, 6 | SyntaxNode, SyntaxToken, TextRange, TextSize, 7 | }; 8 | use smol_str::SmolStr; 9 | 10 | use crate::{dsl::RuleName, engine::FmtDiff, tree_utils::preceding_tokens, AtomEdit}; 11 | 12 | /// `FmtModel` is a data structure to which we apply formatting rules. 13 | /// 14 | /// It wraps a syntax trees and adds `SpaceBlock`s. `SpaceBlock` represents a 15 | /// run (potentially empty) of whitespace characters. We create whitespace 16 | /// blocks for existing whitespace tokens. However, if two non-whitespace tokens 17 | /// are joined together in the syntax tree, we still create an empty 18 | /// `SpaceBlock` to represent the space between them. That way, rules don't have 19 | /// to separately handle a case when whitespace node is completely missing from 20 | /// the original tree. 21 | /// 22 | /// The `FmtModel` is a mutable data structure, formatting rules work by 23 | /// changing the actual `SpacingBlock`s. For this reason, the order of 24 | /// application of the rules is significant. 25 | /// 26 | /// We maintain the invariant that no two `SpaceBlock`s are directly adjoint to 27 | /// each other. 28 | #[derive(Debug)] 29 | pub(super) struct FmtModel { 30 | original_node: SyntaxNode, 31 | /// We store `SpaceBlock`s in array. With this setup, we can refer to a 32 | /// specific block by index, dodging many lifetime issues. 33 | blocks: Vec, 34 | /// Maps offset to an index of the block, for which the offset is the start 35 | /// offset. 36 | by_start_offset: HashMap, 37 | /// Maps offset to an index of the block, for which the offset is the end 38 | /// offset. 39 | by_end_offset: HashMap, 40 | /// Arbitrary non-whitespace edits created by the last formatter phase. 41 | fixes: Vec, 42 | } 43 | 44 | #[derive(Debug)] 45 | pub(super) struct SpaceBlock { 46 | original: OriginalSpace, 47 | /// Block's textual content, which is seen and modified by formatting rules. 48 | change: Option, 49 | /// If this block requires a newline to preserve semantics. 50 | /// 51 | /// True for blocks after comments. The engine takes care to never remove 52 | /// newline, even if some interaction of rules asks us to do so. 53 | semantic_newline: bool, 54 | } 55 | 56 | #[derive(Debug)] 57 | struct SpaceChange { 58 | new_text: SmolStr, 59 | reason: Option, 60 | } 61 | 62 | #[derive(Debug, Clone, Copy)] 63 | pub(super) enum BlockPosition { 64 | Before, 65 | After, 66 | } 67 | 68 | /// Original whitespace token, if any, that backs a `SpaceBlock. 69 | #[derive(Debug)] 70 | pub(super) enum OriginalSpace { 71 | Some(SyntaxToken), 72 | None { offset: TextSize }, 73 | } 74 | 75 | impl OriginalSpace { 76 | fn text_range(&self) -> TextRange { 77 | match self { 78 | OriginalSpace::Some(token) => token.text_range(), 79 | OriginalSpace::None { offset } => TextRange::new(*offset, *offset), 80 | } 81 | } 82 | } 83 | 84 | impl SpaceBlock { 85 | fn new(original: OriginalSpace) -> SpaceBlock { 86 | let semantic_newline = match &original { 87 | OriginalSpace::Some(token) => { 88 | token.text().contains('\n') && is_line_comment(token.prev_sibling_or_token()) 89 | } 90 | OriginalSpace::None { .. } => false, 91 | }; 92 | SpaceBlock { original, change: None, semantic_newline } 93 | } 94 | pub(super) fn set_line_break_preserving_existing_newlines(&mut self, rule: Option) { 95 | if self.has_newline() { 96 | return; 97 | } 98 | self.set_text("\n", rule); 99 | } 100 | pub(super) fn set_text(&mut self, text: &str, rule: Option) { 101 | if self.semantic_newline && !text.contains('\n') { 102 | return; 103 | } 104 | self.change = match &self.original { 105 | OriginalSpace::Some(token) if token.text() == text => None, 106 | OriginalSpace::None { .. } if text.is_empty() => None, 107 | _ => Some(SpaceChange { new_text: text.into(), reason: rule }), 108 | } 109 | } 110 | pub(super) fn text(&self) -> &str { 111 | if let Some(change) = &self.change { 112 | return change.new_text.as_str(); 113 | } 114 | self.original_text() 115 | } 116 | pub(crate) fn original_text(&self) -> &str { 117 | match &self.original { 118 | OriginalSpace::Some(token) => token.text(), 119 | OriginalSpace::None { .. } => "", 120 | } 121 | } 122 | pub(super) fn has_newline(&self) -> bool { 123 | self.text().contains('\n') 124 | } 125 | } 126 | 127 | pub(super) enum SpaceBlockOrToken<'a> { 128 | SpaceBlock(&'a mut SpaceBlock), 129 | Token(SyntaxToken), 130 | } 131 | 132 | impl FmtModel { 133 | pub(super) fn new(original_node: SyntaxNode) -> FmtModel { 134 | FmtModel { 135 | original_node, 136 | blocks: vec![], 137 | by_start_offset: HashMap::default(), 138 | by_end_offset: HashMap::default(), 139 | fixes: vec![], 140 | } 141 | } 142 | 143 | pub(super) fn into_diff(self) -> FmtDiff { 144 | let mut diff = FmtDiff { original_node: self.original_node.to_owned(), edits: vec![] }; 145 | for block in self.blocks { 146 | if let Some(change) = block.change { 147 | diff.replace(block.original.text_range(), change.new_text, change.reason); 148 | } 149 | } 150 | diff.edits.extend(self.fixes.into_iter().map(|edit| (edit, None))); 151 | diff 152 | } 153 | 154 | /// This method gets a `SpaceBlock` before or after element. It's pretty 155 | /// complicated, because it needs to handle these different cases: 156 | /// * We could have already created the block. In this case, we should 157 | /// return the existing block instead of creating a new one. 158 | /// * There may, or may not be, backing original whitespace token for the 159 | /// block. 160 | /// * The necessary whitespace token is not necessary a sibling of 161 | /// `element`, it might be a sibling of `element`'s ancestor. 162 | /// * Finally, root node is special, as it doesn't have siblings and instead 163 | /// leading and trailing whitespace appear as children. 164 | pub(super) fn block_for( 165 | &mut self, 166 | element: &SyntaxElement, 167 | position: BlockPosition, 168 | ) -> &mut SpaceBlock { 169 | use BlockPosition::{After, Before}; 170 | 171 | assert!(element.kind() != TOKEN_WHITESPACE); 172 | 173 | // Special case, for root node, leading and trailing ws are children of 174 | // the root. For all other node types, they are siblings 175 | if element.kind() == NODE_ROOT { 176 | let root_node = element.as_node().unwrap(); 177 | let original_space = match position { 178 | BlockPosition::Before => root_node.first_child_or_token(), 179 | BlockPosition::After => root_node.last_child_or_token(), 180 | }; 181 | return match &original_space { 182 | Some(NodeOrToken::Token(token)) if token.kind() == TOKEN_WHITESPACE => { 183 | if let Some(&existing) = match position { 184 | Before => self.by_end_offset.get(&token.text_range().end()), 185 | After => self.by_start_offset.get(&token.text_range().start()), 186 | } { 187 | &mut self.blocks[existing] 188 | } else { 189 | self.push_block(SpaceBlock::new(OriginalSpace::Some(token.clone()))) 190 | } 191 | } 192 | _ => { 193 | let offset = match position { 194 | Before => root_node.text_range().start(), 195 | After => root_node.text_range().end(), 196 | }; 197 | 198 | if let Some(&existing) = match position { 199 | Before => self.by_end_offset.get(&offset), 200 | After => self.by_start_offset.get(&offset), 201 | } { 202 | &mut self.blocks[existing] 203 | } else { 204 | self.push_block(SpaceBlock::new(OriginalSpace::None { offset })) 205 | } 206 | } 207 | }; 208 | } 209 | 210 | let offset = match position { 211 | Before => element.text_range().start(), 212 | After => element.text_range().end(), 213 | }; 214 | 215 | if let Some(&existing) = match position { 216 | Before => self.by_end_offset.get(&offset), 217 | After => self.by_start_offset.get(&offset), 218 | } { 219 | return &mut self.blocks[existing]; 220 | } 221 | 222 | let original_token = match position { 223 | Before => element.prev_sibling_or_token(), 224 | After => element.next_sibling_or_token(), 225 | }; 226 | 227 | let original_space = match &original_token { 228 | Some(NodeOrToken::Token(token)) if token.kind() == TOKEN_WHITESPACE => { 229 | OriginalSpace::Some(token.clone()) 230 | } 231 | Some(_) => OriginalSpace::None { offset }, 232 | _ => match element.parent() { 233 | Option::Some(parent) => return self.block_for(&parent.into(), position), 234 | None => OriginalSpace::None { offset }, 235 | }, 236 | }; 237 | 238 | self.push_block(SpaceBlock::new(original_space)) 239 | } 240 | 241 | /// Traverses tokens and space blocks that precede the given `node`. 242 | /// 243 | /// This is implemented as internal iterator due to lifetime issues. 244 | pub(super) fn with_preceding_elements( 245 | &mut self, 246 | node: &SyntaxNode, 247 | f: &mut impl FnMut(SpaceBlockOrToken<'_>) -> bool, 248 | ) { 249 | let block = self.block_for(&node.clone().into(), BlockPosition::Before); 250 | if f(SpaceBlockOrToken::SpaceBlock(block)) { 251 | return; 252 | } 253 | 254 | for token in preceding_tokens(node).filter(|it| it.kind() != TOKEN_WHITESPACE) { 255 | if f(SpaceBlockOrToken::Token(token.clone())) { 256 | return; 257 | } 258 | 259 | let block = self.block_for(&token.clone().into(), BlockPosition::Before); 260 | if f(SpaceBlockOrToken::SpaceBlock(block)) { 261 | return; 262 | } 263 | } 264 | } 265 | 266 | pub(super) fn raw_edit(&mut self, edit: AtomEdit) { 267 | self.fixes.push(edit) 268 | } 269 | 270 | fn push_block(&mut self, block: SpaceBlock) -> &mut SpaceBlock { 271 | let idx = self.blocks.len(); 272 | let range = block.original.text_range(); 273 | 274 | let prev = self.by_start_offset.insert(range.start(), idx); 275 | assert!(prev.is_none()); 276 | let prev = self.by_end_offset.insert(range.end(), idx); 277 | assert!(prev.is_none()); 278 | 279 | self.blocks.push(block); 280 | self.blocks.last_mut().unwrap() 281 | } 282 | } 283 | 284 | fn is_line_comment(node: Option) -> bool { 285 | match node { 286 | Some(NodeOrToken::Token(token)) => { 287 | token.kind() == TOKEN_COMMENT && token.text().starts_with('#') 288 | } 289 | _ => false, 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/engine/indentation.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cmp::{Ord, Ordering, PartialOrd}, 3 | fmt, 4 | }; 5 | 6 | use rnix::{SyntaxElement, SyntaxKind::*, SyntaxNode, SyntaxToken, TextSize}; 7 | use smol_str::SmolStr; 8 | 9 | use crate::{ 10 | dsl::{IndentRule, Modality, RuleName}, 11 | engine::{BlockPosition, FmtModel, SpaceBlock, SpaceBlockOrToken}, 12 | pattern::{Pattern, PatternSet}, 13 | tree_utils::prev_non_whitespace_token_sibling, 14 | }; 15 | 16 | const INDENT_SIZE: u32 = 2; 17 | 18 | /// Indentation level (number of leading spaces). 19 | /// 20 | /// It consists of two bits: 21 | /// * `level`: the usual nesting level 22 | /// * `alignment`: additional space required to align the node. 23 | /// 24 | /// For example, in something like 25 | /// 26 | /// ```nix 27 | /// | foo.bar { 28 | /// | x = z; 29 | /// | } 30 | /// ``` 31 | /// 32 | /// `x = z` has alignment of one space, and level of one " ". 33 | #[derive(Default, Debug, Clone, Copy)] 34 | pub(super) struct IndentLevel { 35 | level: u32, 36 | alignment: u32, 37 | } 38 | 39 | impl std::ops::AddAssign for IndentLevel { 40 | fn add_assign(&mut self, rhs: IndentLevel) { 41 | self.level += rhs.level; 42 | self.alignment += rhs.alignment; 43 | } 44 | } 45 | 46 | impl PartialEq for IndentLevel { 47 | fn eq(&self, other: &IndentLevel) -> bool { 48 | self.len() == other.len() 49 | } 50 | } 51 | 52 | impl Eq for IndentLevel {} 53 | 54 | impl PartialOrd for IndentLevel { 55 | fn partial_cmp(&self, other: &IndentLevel) -> Option { 56 | Some(self.cmp(other)) 57 | } 58 | } 59 | 60 | impl Ord for IndentLevel { 61 | fn cmp(&self, other: &IndentLevel) -> Ordering { 62 | self.len().cmp(&other.len()) 63 | } 64 | } 65 | 66 | impl fmt::Display for IndentLevel { 67 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 68 | match self.as_short_str() { 69 | Some(s) => f.write_str(s), 70 | None => write!(f, "{:width$}", "", width = u32::from(self.len()) as usize), 71 | } 72 | } 73 | } 74 | 75 | impl From for SmolStr { 76 | fn from(indent: IndentLevel) -> SmolStr { 77 | match indent.as_short_str() { 78 | Some(s) => s.into(), 79 | None => indent.to_string().into(), 80 | } 81 | } 82 | } 83 | 84 | impl IndentLevel { 85 | /// Constructs `IndentLevel` from indent string (without \n) 86 | pub(super) fn from_str(s: &str) -> IndentLevel { 87 | let len = len_for_indent(s); 88 | IndentLevel { level: len / INDENT_SIZE, alignment: len % INDENT_SIZE } 89 | } 90 | 91 | /// adjust `IndentLevel` based on whitespace provided 92 | pub(super) fn adjust_alignment(self, new_indent: IndentLevel) -> IndentLevel { 93 | if new_indent.len() > TextSize::from(5) { 94 | return new_indent; 95 | } 96 | IndentLevel { level: self.level, alignment: new_indent.alignment } 97 | } 98 | 99 | /// adding alignment for multiline comment 100 | pub(super) fn add_alignment(self, new_indent: IndentLevel) -> IndentLevel { 101 | if self.level < new_indent.level { 102 | return new_indent; 103 | } 104 | IndentLevel { level: self.level, alignment: new_indent.alignment } 105 | } 106 | 107 | pub(super) fn get_whitespace_block(s: &str) -> IndentLevel { 108 | match s.find(|c: char| !c.is_whitespace()) { 109 | None => IndentLevel::default(), 110 | Some(idx) => IndentLevel::from_str(&s[..idx]), 111 | } 112 | } 113 | 114 | pub(super) fn from_whitespace_block(s: &str) -> IndentLevel { 115 | match s.rfind('\n') { 116 | None => IndentLevel::default(), 117 | Some(idx) => IndentLevel::from_str(&s[idx + 1..]), 118 | } 119 | } 120 | 121 | pub(super) fn from_len(len: TextSize) -> IndentLevel { 122 | let len: u32 = len.into(); 123 | IndentLevel { level: len / INDENT_SIZE, alignment: len % INDENT_SIZE } 124 | } 125 | 126 | pub(super) fn indent(self) -> IndentLevel { 127 | IndentLevel { level: self.level + 1, alignment: self.alignment } 128 | } 129 | 130 | pub(super) fn len(self) -> TextSize { 131 | (self.level * INDENT_SIZE + self.alignment).into() 132 | } 133 | 134 | fn as_short_str(self) -> Option<&'static str> { 135 | #[rustfmt::skip] 136 | const SPACES: &str = 137 | " "; 138 | let len = u32::from(self.len()) as usize; 139 | if len <= SPACES.len() { 140 | Some(&SPACES[..len]) 141 | } else { 142 | None 143 | } 144 | } 145 | } 146 | 147 | impl IndentRule { 148 | pub(super) fn matches(&self, element: &SyntaxElement) -> bool { 149 | let parent = match element.parent() { 150 | None => return false, 151 | Some(it) => it, 152 | }; 153 | if !self.parent.matches(&parent.into()) { 154 | return false; 155 | } 156 | if let Some(child) = &self.child { 157 | child.matches(element) == (self.child_modality == Modality::Positive) 158 | } else { 159 | true 160 | } 161 | } 162 | 163 | pub(super) fn apply( 164 | &self, 165 | element: &SyntaxElement, 166 | model: &mut FmtModel, 167 | anchor_set: &PatternSet<&Pattern>, 168 | ) { 169 | debug_assert!(self.matches(element)); 170 | let anchor_indent = match indent_anchor(element, model, anchor_set) { 171 | Some((anchor, indent)) => { 172 | if let Some(p) = &self.anchor_pattern { 173 | if !p.matches(&anchor.into()) { 174 | default_indent(element, model, anchor_set); 175 | return; 176 | } 177 | } 178 | indent 179 | } 180 | _ => IndentLevel::default(), 181 | }; 182 | let block = model.block_for(element, BlockPosition::Before); 183 | block.set_indent(anchor_indent.indent(), self.name); 184 | } 185 | } 186 | 187 | impl SpaceBlock { 188 | fn set_indent(&mut self, indent: IndentLevel, rule: RuleName) { 189 | let newlines: String = self.text().chars().filter(|&it| it == '\n').collect(); 190 | self.set_text(&format!("{}{}", newlines, indent), Some(rule)); 191 | } 192 | 193 | fn indent(&self) -> IndentLevel { 194 | IndentLevel::from_whitespace_block(self.text()) 195 | } 196 | } 197 | 198 | pub(super) fn default_indent( 199 | element: &SyntaxElement, 200 | model: &mut FmtModel, 201 | anchor_set: &PatternSet<&Pattern>, 202 | ) { 203 | let anchor_indent = match indent_anchor(element, model, anchor_set) { 204 | Some((_anchor, indent)) => indent, 205 | _ => IndentLevel::default(), 206 | }; 207 | let block = model.block_for(element, BlockPosition::Before); 208 | block.set_indent(anchor_indent, RuleName::new("Preserve indentation")); 209 | } 210 | 211 | pub(super) fn single_line_comment_indent( 212 | token: &SyntaxToken, 213 | model: &mut FmtModel, 214 | anchor_set: &PatternSet<&Pattern>, 215 | ) { 216 | let syntax_element = &token.clone().into(); 217 | let anchor_indent = match indent_anchor(&syntax_element, model, anchor_set) { 218 | Some((_anchor, indent)) => indent, 219 | _ => IndentLevel::default(), 220 | }; 221 | let block = model.block_for(&syntax_element, BlockPosition::Before); 222 | let prev_is_token_in = prev_non_whitespace_token_sibling(syntax_element) 223 | .map(|e| e.kind() == TOKEN_IN) 224 | .unwrap_or(false); 225 | if prev_is_token_in { 226 | block.set_indent(anchor_indent, RuleName::new("Comment Single Line Value")); 227 | return; 228 | } 229 | return; 230 | } 231 | /// Computes an anchoring element, together with its indent. 232 | /// 233 | /// By default, the anchor is an ancestor of `element` which itself is the first 234 | /// element on the line. 235 | /// 236 | /// Elements from `anchor_set` are considered anchors even if they don't begin 237 | /// the line. 238 | pub(super) fn indent_anchor( 239 | element: &SyntaxElement, 240 | model: &mut FmtModel, 241 | anchor_set: &PatternSet<&Pattern>, 242 | ) -> Option<(SyntaxNode, IndentLevel)> { 243 | let parent = element.parent()?; 244 | for node in parent.ancestors() { 245 | let block = model.block_for(&node.clone().into(), BlockPosition::Before); 246 | if block.has_newline() { 247 | return Some((node.clone(), block.indent())); 248 | } 249 | if anchor_set.matching(node.clone().into()).next().is_some() { 250 | let indent = model.indent_of(&node); 251 | return Some((node, indent)); 252 | } 253 | // For the root node, the block will typically be empty, but it still 254 | // should be considered an indent anchor. 255 | if node.kind() == NODE_ROOT { 256 | return Some((node, IndentLevel::default())); 257 | } 258 | } 259 | None 260 | } 261 | 262 | impl FmtModel { 263 | /// Calculates current indent level for node. 264 | fn indent_of(&mut self, node: &SyntaxNode) -> IndentLevel { 265 | // The impl is tricky: we need to account for whitespace in `model`, which 266 | // might be different from original whitespace in the syntax tree 267 | let mut indent = IndentLevel::default(); 268 | self.with_preceding_elements(node, &mut |element| match element { 269 | SpaceBlockOrToken::Token(it) => { 270 | let (len, has_newline) = len_of_last_line(it.text()); 271 | indent.alignment += len; 272 | has_newline 273 | } 274 | SpaceBlockOrToken::SpaceBlock(it) => { 275 | let (len, has_newline) = len_of_last_line(it.text()); 276 | if has_newline { 277 | indent += it.indent(); 278 | } else { 279 | indent.alignment += len; 280 | } 281 | has_newline 282 | } 283 | }); 284 | 285 | return indent; 286 | 287 | fn len_of_last_line(s: &str) -> (u32, bool) { 288 | if let Some(idx) = s.rfind('\n') { 289 | return (len_for_indent(&s[idx + 1..]), true); 290 | } 291 | (len_for_indent(s), false) 292 | } 293 | } 294 | } 295 | 296 | fn len_for_indent(s: &str) -> u32 { 297 | s.chars().count() as u32 298 | } 299 | -------------------------------------------------------------------------------- /src/engine/spacing.rs: -------------------------------------------------------------------------------- 1 | use rnix::SyntaxElement; 2 | 3 | use crate::{ 4 | dsl::{RuleName, SpaceLoc, SpaceValue, SpacingRule}, 5 | engine::{BlockPosition, FmtModel, SpaceBlock}, 6 | tree_utils::has_newline, 7 | }; 8 | 9 | impl SpacingRule { 10 | pub(super) fn apply(&self, element: &SyntaxElement, model: &mut FmtModel) { 11 | if !self.pattern.matches(element) { 12 | return; 13 | } 14 | if self.space.loc.is_before() { 15 | let block = model.block_for(element, BlockPosition::Before); 16 | ensure_space(element, block, self.space.value, self.name); 17 | } 18 | if self.space.loc.is_after() { 19 | let block = model.block_for(element, BlockPosition::After); 20 | ensure_space(element, block, self.space.value, self.name); 21 | } 22 | } 23 | } 24 | 25 | impl SpaceLoc { 26 | fn is_before(self) -> bool { 27 | match self { 28 | SpaceLoc::Before | SpaceLoc::Around => true, 29 | SpaceLoc::After => false, 30 | } 31 | } 32 | fn is_after(self) -> bool { 33 | match self { 34 | SpaceLoc::After | SpaceLoc::Around => true, 35 | SpaceLoc::Before => false, 36 | } 37 | } 38 | } 39 | 40 | fn ensure_space( 41 | element: &SyntaxElement, 42 | block: &mut SpaceBlock, 43 | value: SpaceValue, 44 | rule_name: Option, 45 | ) { 46 | match value { 47 | SpaceValue::Single => block.set_text(" ", rule_name), 48 | SpaceValue::SingleOptionalNewline => { 49 | if !block.has_newline() { 50 | block.set_text(" ", rule_name) 51 | } 52 | } 53 | SpaceValue::Newline => { 54 | // This will ignore if the block already has a newline 55 | if !block.has_newline() { 56 | block.set_text("\n", rule_name) 57 | } 58 | } 59 | SpaceValue::None => block.set_text("", rule_name), 60 | SpaceValue::NoneOptionalNewline => { 61 | if !block.has_newline() { 62 | block.set_text("", rule_name) 63 | } 64 | } 65 | SpaceValue::SingleOrNewline => { 66 | let parent_is_multiline = element.parent().map_or(false, |it| has_newline(&it)); 67 | if parent_is_multiline { 68 | block.set_line_break_preserving_existing_newlines(None) 69 | } else { 70 | block.set_text(" ", rule_name) 71 | } 72 | } 73 | SpaceValue::NoneOrNewline => { 74 | let parent_is_multiline = element.parent().map_or(false, |it| has_newline(&it)); 75 | if parent_is_multiline { 76 | block.set_line_break_preserving_existing_newlines(None) 77 | } else { 78 | block.set_text("", rule_name) 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod dsl; 3 | mod engine; 4 | mod rules; 5 | mod tree_utils; 6 | mod pattern; 7 | 8 | use std::{borrow::Cow, fmt, fmt::Formatter}; 9 | 10 | use engine::ExtraInfo; 11 | use rnix::{SyntaxNode, TextRange, TextSize}; 12 | use smol_str::SmolStr; 13 | 14 | use crate::dsl::RuleName; 15 | 16 | /// The result of formatting. 17 | /// 18 | /// From this Diff, you can get either the resulting `String`, or the 19 | /// reformatted syntax node. 20 | #[derive(Debug)] 21 | pub(crate) struct FmtDiff { 22 | original_node: SyntaxNode, 23 | edits: Vec<(AtomEdit, Option)>, 24 | } 25 | 26 | /// An edit where the `delete` range represents the range of the original text 27 | // that should be replaced with the `insert` string. 28 | #[derive(Debug, Clone, PartialEq, Eq)] 29 | pub struct AtomEdit { 30 | pub delete: TextRange, 31 | pub insert: SmolStr, 32 | } 33 | 34 | impl fmt::Display for FmtDiff { 35 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 36 | // TODO: don't copy strings all over the place 37 | let old_text = self.original_node.to_string(); 38 | 39 | let mut total_len = old_text.len(); 40 | let mut edits = self.text_diff(); 41 | edits.sort_by_key(|edit| edit.delete.start()); 42 | 43 | for atom in edits.iter() { 44 | total_len += atom.insert.len(); 45 | total_len -= u32::from(atom.delete.end() - atom.delete.start()) as usize; 46 | } 47 | 48 | let mut buf = String::with_capacity(total_len); 49 | let mut prev = 0; 50 | for atom in edits.iter() { 51 | let start = u32::from(atom.delete.start()) as usize; 52 | let end = u32::from(atom.delete.end()) as usize; 53 | if start > prev { 54 | buf.push_str(&old_text[prev..start]); 55 | } 56 | buf.push_str(&atom.insert); 57 | prev = end; 58 | } 59 | buf.push_str(&old_text[prev..]); 60 | assert_eq!(buf.len(), total_len); 61 | write!(f, "{}", buf) 62 | } 63 | } 64 | 65 | impl FmtDiff { 66 | /// Get the diff of deletes and inserts 67 | pub(crate) fn text_diff(&self) -> Vec { 68 | self.edits.iter().map(|(edit, _reason)| edit.clone()).collect() 69 | } 70 | 71 | /// Whether or not formatting did caused any changes 72 | pub(crate) fn has_changes(&self) -> bool { 73 | !self.edits.is_empty() 74 | } 75 | 76 | /// Apply the formatting suggestions and return the new node 77 | pub(crate) fn to_node(&self) -> SyntaxNode { 78 | if self.has_changes() { 79 | rnix::parse(&self.to_string()).node() 80 | } else { 81 | self.original_node.clone() 82 | } 83 | } 84 | } 85 | 86 | pub fn reformat_node(node: &SyntaxNode) -> SyntaxNode { 87 | let spacing = rules::spacing(); 88 | let indentation = rules::indentation(); 89 | engine::reformat(&spacing, &indentation, node, ExtraInfo::None) 90 | } 91 | 92 | pub fn reformat_string(text: &str) -> String { 93 | let (text, line_endings) = convert_to_unix_line_endings(text); 94 | 95 | let ast = rnix::parse(&*text); 96 | let root_node = ast.node(); 97 | let res = reformat_node(&root_node).to_string(); 98 | match line_endings { 99 | LineEndings::Unix => res, 100 | LineEndings::Dos => convert_to_dos_line_endings(res), 101 | } 102 | } 103 | 104 | /// Returns the edits that must be applied to `node` in order to reformat it. 105 | /// The first of the two results contains spacing edits, and the second 106 | /// contains indentation edits. Note that the ranges in both sets of edits 107 | /// refer to ranges in the document before edits have been applied (in other 108 | /// words, edits should not be applies sequentially; they do not represent 109 | /// intermediate state). Also note that the ranges in the indentation edits 110 | /// refer to positions in the document **after the spacing edits have been 111 | /// applied**. 112 | pub fn reformat_edits(node: &SyntaxNode) -> (Vec, Vec) { 113 | let spacing = rules::spacing(); 114 | let indentation = rules::indentation(); 115 | 116 | let (mut spacing_edits, mut indent_edits) = (Vec::new(), Vec::new()); 117 | engine::reformat( 118 | &spacing, 119 | &indentation, 120 | node, 121 | ExtraInfo::Edits { spacing_edits: &mut spacing_edits, indent_edits: &mut indent_edits }, 122 | ); 123 | spacing_edits.sort_by(|a, b| a.delete.start().cmp(&b.delete.start())); 124 | indent_edits.sort_by(|a, b| a.delete.start().cmp(&b.delete.start())); 125 | (spacing_edits, indent_edits) 126 | } 127 | 128 | pub fn explain(text: &str) -> String { 129 | let (text, _line_endings) = convert_to_unix_line_endings(text); 130 | let ast = rnix::parse(&*text); 131 | let spacing = rules::spacing(); 132 | let indentation = rules::indentation(); 133 | let mut explanation = Vec::new(); 134 | engine::reformat(&spacing, &indentation, &ast.node(), ExtraInfo::Explanation(&mut explanation)); 135 | 136 | let mut buf = String::new(); 137 | let mut line_start: TextSize = 0.into(); 138 | for line in text.to_string().lines() { 139 | let line_len = TextSize::of(line) + TextSize::of("\n"); 140 | let line_range = TextRange::at(line_start, line_len); 141 | 142 | buf.push_str(line); 143 | let mut first = true; 144 | for (edit, reason) in explanation.iter() { 145 | if line_range.contains(edit.delete.end()) { 146 | if first { 147 | first = false; 148 | buf.push_str(" # ") 149 | } else { 150 | buf.push_str(", ") 151 | } 152 | buf.push_str(&format!( 153 | "[{}; {}): ", 154 | usize::from(edit.delete.start()), 155 | usize::from(edit.delete.end()) 156 | )); 157 | match reason { 158 | Some(reason) => buf.push_str(&reason.to_string()), 159 | None => buf.push_str("unnamed rule"), 160 | } 161 | } 162 | } 163 | buf.push('\n'); 164 | 165 | line_start += line_len; 166 | } 167 | buf 168 | } 169 | 170 | enum LineEndings { 171 | Unix, 172 | Dos, 173 | } 174 | 175 | fn convert_to_unix_line_endings(text: &str) -> (Cow, LineEndings) { 176 | if !text.contains("\r\n") { 177 | return (Cow::Borrowed(text), LineEndings::Unix); 178 | } 179 | (Cow::Owned(text.replace("\r\n", "\n")), LineEndings::Dos) 180 | } 181 | 182 | fn convert_to_dos_line_endings(text: String) -> String { 183 | text.replace('\n', "\r\n") 184 | } 185 | 186 | #[cfg(test)] 187 | mod tests { 188 | use super::*; 189 | 190 | #[test] 191 | fn preserves_dos_line_endings() { 192 | assert_eq!(&reformat_string("{foo = 92;\n}"), "{\n foo = 92;\n}\n"); 193 | assert_eq!(&reformat_string("{foo = 92;\r\n}"), "{\r\n foo = 92;\r\n}\r\n") 194 | } 195 | 196 | #[test] 197 | fn converts_tabs_to_spaces() { 198 | assert_eq!(&reformat_string("{\n\tfoo = 92;\t}\n"), "{\n foo = 92;\n}\n"); 199 | } 200 | 201 | #[test] 202 | fn explain_smoke_test() { 203 | let input = "{\nfoo =1;\n}\n"; 204 | let explanation = explain(input); 205 | assert_eq!( 206 | explanation, 207 | "{ 208 | foo =1; # [7; 7): Space after = 209 | } 210 | " 211 | ) 212 | } 213 | 214 | #[test] 215 | fn edits() { 216 | let input = include_str!("../test_data/indent_tabs-2.bad.nix"); 217 | let expected = include_str!("../test_data/indent_tabs-2.good.nix"); 218 | let (spacing_edits, indent_edits) = reformat_edits(&rnix::parse(input).node()); 219 | dbg!(&spacing_edits, &indent_edits); 220 | 221 | let mut intermediate_len = input.len() as isize; 222 | for ae in &spacing_edits { 223 | intermediate_len += ae.insert.len() as isize; 224 | let del_len: u32 = ae.delete.len().into(); 225 | intermediate_len -= del_len as isize; 226 | } 227 | let mut intermediate = String::with_capacity(intermediate_len as usize); 228 | let mut prev = 0; 229 | for ae in spacing_edits { 230 | intermediate.push_str(&input[prev..ae.delete.start().into()]); 231 | intermediate.push_str(&ae.insert); 232 | prev = ae.delete.end().into(); 233 | } 234 | intermediate.push_str(&input[prev..]); 235 | 236 | let mut final_len = intermediate.len() as isize; 237 | for ae in &indent_edits { 238 | final_len += ae.insert.len() as isize; 239 | let del_len: u32 = ae.delete.len().into(); 240 | final_len -= del_len as isize; 241 | } 242 | let mut actual = String::with_capacity(final_len as usize); 243 | prev = 0; 244 | for ae in indent_edits { 245 | actual.push_str(&intermediate[prev..ae.delete.start().into()]); 246 | actual.push_str(&ae.insert); 247 | prev = ae.delete.end().into(); 248 | } 249 | actual.push_str(&intermediate[prev..]); 250 | 251 | assert_eq!(expected, &actual); 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::Write, 3 | fs, 4 | io::{self, stdin, Read}, 5 | path::{Path, PathBuf}, 6 | thread, 7 | }; 8 | 9 | use clap::{App, Arg}; 10 | use crossbeam_channel::{unbounded, Receiver, Sender}; 11 | use rnix::types::TypedNode; 12 | 13 | type Result = std::result::Result>; 14 | 15 | type FormatResult = (PathBuf, FormatStatus); 16 | enum FormatStatus { 17 | Change, 18 | NoChange, 19 | } 20 | 21 | fn main() { 22 | if let Err(err) = parse_args().and_then(try_main) { 23 | eprintln!("{}", err); 24 | std::process::exit(1); 25 | } 26 | } 27 | 28 | #[derive(Debug)] 29 | struct Args { 30 | src: Src, 31 | operation: Operation, 32 | } 33 | 34 | #[derive(Debug)] 35 | enum Operation { 36 | Fmt { write_changes: bool, fail_on_changes: bool }, 37 | Explain, 38 | Parse { output_format: OutputFormat }, 39 | } 40 | 41 | #[derive(Debug)] 42 | enum Src { 43 | Stdin, 44 | Paths(Vec), 45 | } 46 | 47 | #[derive(Debug)] 48 | enum OutputFormat { 49 | Rnix, // rnix library: ast.root().dump() 50 | Json, 51 | } 52 | 53 | fn parse_args() -> Result { 54 | let matches = App::new("nixpkgs-fmt") 55 | .version(clap::crate_version!()) 56 | .about("Format Nix code") 57 | .arg( 58 | Arg::with_name("srcs") 59 | .value_name("FILE") 60 | .multiple(true) 61 | .conflicts_with("explain") 62 | .help("File to reformat in place. If no file is passed, read from stdin."), 63 | ) 64 | .arg( 65 | Arg::with_name("parse") 66 | .long("parse") 67 | .conflicts_with("explain") 68 | .conflicts_with("check") 69 | .help("Show syntax tree instead of reformatting"), 70 | ) 71 | .arg( 72 | Arg::with_name("output-format") 73 | .long("output-format") 74 | .value_name("FORMAT") 75 | .takes_value(true) 76 | .possible_values(&["rnix", "json"]) 77 | .default_value("rnix") 78 | .help("Set output format of --parse"), 79 | ) 80 | .arg( 81 | Arg::with_name("explain") 82 | .long("explain") 83 | .conflicts_with("parse") 84 | .conflicts_with("check") 85 | .help("Show which rules are violated"), 86 | ) 87 | .arg( 88 | Arg::with_name("check") 89 | .long("check") 90 | .conflicts_with("parse") 91 | .conflicts_with("explain") 92 | .help("Only test if the formatter would produce differences"), 93 | ) 94 | .get_matches_safe()?; 95 | 96 | let src = match matches.values_of("srcs") { 97 | None => Src::Stdin, // default to reading from stdin 98 | Some(srcs) => Src::Paths(srcs.map(PathBuf::from).collect()), 99 | }; 100 | let operation = if matches.is_present("parse") { 101 | let output_format = match matches.value_of("output-format") { 102 | Some("json") => OutputFormat::Json, 103 | _ => OutputFormat::Rnix, 104 | }; 105 | Operation::Parse { output_format } 106 | } else if matches.is_present("explain") { 107 | Operation::Explain 108 | } else if matches.is_present("check") { 109 | Operation::Fmt { write_changes: false, fail_on_changes: true } 110 | } else { 111 | Operation::Fmt { write_changes: true, fail_on_changes: false } 112 | }; 113 | 114 | Ok(Args { operation, src }) 115 | } 116 | 117 | fn reset_sigpipe() -> io::Result<()> { 118 | use libc::{signal, SIGPIPE, SIG_DFL, SIG_ERR}; 119 | if unsafe { signal(SIGPIPE, SIG_DFL) } == SIG_ERR { 120 | return Err(io::Error::last_os_error()); 121 | } 122 | Ok(()) 123 | } 124 | 125 | fn try_main(args: Args) -> Result<()> { 126 | match args.operation { 127 | Operation::Fmt { write_changes, fail_on_changes } => match &args.src { 128 | Src::Stdin => { 129 | reset_sigpipe()?; 130 | let input = read_stdin_to_string()?; 131 | let output = nixpkgs_fmt::reformat_string(&input); 132 | let has_changes = input != output; 133 | if write_changes { 134 | print!("{}", output); 135 | } 136 | if fail_on_changes && has_changes { 137 | return Err("error: fail on changes".into()); 138 | } 139 | } 140 | Src::Paths(paths) => { 141 | let (sender, receiver): (Sender, Receiver) = 142 | unbounded(); 143 | 144 | // Reducer, collect all the paths and statuses that have been seen 145 | let reducer = thread::spawn(move || { 146 | let mut files_count = 0; 147 | let mut files_changed = 0; 148 | for (file_path, status) in receiver { 149 | files_count += 1; 150 | if let FormatStatus::Change = status { 151 | files_changed += 1; 152 | println!("{}", file_path.display()); 153 | } 154 | } 155 | (files_count, files_changed) 156 | }); 157 | 158 | // Start formatting 159 | for path in paths { 160 | if path.is_dir() { 161 | reformat_dir_in_place(path, write_changes, &sender)?; 162 | } else { 163 | let status = reformat_file(path, write_changes)?; 164 | // unwrap justification: the channel only fails if it's closed on either 165 | // end. The drop() happens below. 166 | sender.send((path.clone(), status)).unwrap() 167 | } 168 | } 169 | 170 | // Time to collect the results 171 | drop(sender); 172 | // unwrap justification: the reducer code has no exceptions 173 | let (files_count, files_changed) = reducer.join().unwrap(); 174 | 175 | let text = if write_changes { 176 | "have been reformatted" 177 | } else { 178 | "would have been reformatted" 179 | }; 180 | eprintln!("{} / {} {}", files_changed, files_count, text); 181 | if fail_on_changes && files_changed > 0 { 182 | return Err("error: fail on changes".into()); 183 | } 184 | } 185 | }, 186 | Operation::Parse { output_format } => { 187 | reset_sigpipe()?; 188 | let input = read_single_source(&args.src)?; 189 | let ast = rnix::parse(&input); 190 | let mut error_buf = String::new(); 191 | let res = match output_format { 192 | OutputFormat::Rnix => { 193 | let mut buf = String::new(); 194 | for error in ast.errors() { 195 | writeln!(error_buf, "error: {}", error).unwrap(); 196 | } 197 | writeln!(buf, "{}", ast.root().dump()).unwrap(); 198 | buf 199 | } 200 | OutputFormat::Json => serde_json::to_string_pretty(&ast.node())?, 201 | }; 202 | match (output_format, error_buf.is_empty()) { 203 | (OutputFormat::Rnix, false) => { 204 | return Err(error_buf.into()); 205 | } 206 | (_, _) => {} 207 | }; 208 | print!("{}", res) 209 | } 210 | Operation::Explain => { 211 | reset_sigpipe()?; 212 | let input = read_stdin_to_string()?; 213 | let output = nixpkgs_fmt::explain(&input); 214 | print!("{}", output); 215 | } 216 | }; 217 | 218 | Ok(()) 219 | } 220 | 221 | fn read_stdin_to_string() -> Result { 222 | let mut buf = String::new(); 223 | stdin().read_to_string(&mut buf)?; 224 | Ok(buf) 225 | } 226 | 227 | fn read_single_source(src: &Src) -> Result { 228 | let res = match src { 229 | Src::Stdin => read_stdin_to_string()?, 230 | Src::Paths(paths) => { 231 | if paths.len() != 1 { 232 | return Err("exactly one path required".into()); 233 | } 234 | fs::read_to_string(&paths[0])? 235 | } 236 | }; 237 | Ok(res) 238 | } 239 | 240 | fn reformat_dir_in_place( 241 | dir: &Path, 242 | write_changes: bool, 243 | sender: &Sender, 244 | ) -> Result<()> { 245 | let nix_file_types = { 246 | let mut builder = ignore::types::TypesBuilder::new(); 247 | builder.add_defaults(); 248 | // unwrap justification: this would be a bug in the code, logic error 249 | builder.add("nix", "*.nix").unwrap(); 250 | builder.select("nix"); 251 | // unwrap justification: this would be a bug in the code, logic error 252 | builder.build().unwrap() 253 | }; 254 | 255 | ignore::WalkBuilder::new(dir).types(nix_file_types).threads(8).build_parallel().run( 256 | move || { 257 | let s = sender.clone(); 258 | Box::new(move |entry| { 259 | match reformat_dir_entry(entry, write_changes, &s) { 260 | Err(err) => eprintln!("error: {}", err), 261 | Ok(()) => {} 262 | } 263 | ignore::WalkState::Continue 264 | }) 265 | }, 266 | ); 267 | Ok(()) 268 | } 269 | 270 | fn reformat_dir_entry( 271 | entry: std::result::Result, 272 | write_changes: bool, 273 | sender: &Sender, 274 | ) -> Result<()> { 275 | let path = entry?.into_path(); 276 | if !path.is_file() { 277 | return Ok(()); 278 | } 279 | let status = reformat_file(&path, write_changes)?; 280 | sender.send((path, status))?; 281 | Ok(()) 282 | } 283 | 284 | fn reformat_file(file: &Path, write_changes: bool) -> Result { 285 | let input = fs::read_to_string(file)?; 286 | let output = nixpkgs_fmt::reformat_string(&input); 287 | if input != output { 288 | if write_changes { 289 | fs::write(file, &output)?; 290 | } 291 | return Ok(FormatStatus::Change); 292 | } 293 | Ok(FormatStatus::NoChange) 294 | } 295 | -------------------------------------------------------------------------------- /src/pattern.rs: -------------------------------------------------------------------------------- 1 | //! This module defines `Pattern`: a predicate over syntax elements 2 | use std::{ 3 | collections::{HashMap, HashSet}, 4 | fmt, iter, ops, 5 | sync::Arc, 6 | }; 7 | 8 | use rnix::{SyntaxElement, SyntaxKind}; 9 | 10 | /// A convenience function to convert something a pattern for use with `&` and 11 | /// `|` operators 12 | pub(crate) fn p(p: impl Into) -> Pattern { 13 | p.into() 14 | } 15 | 16 | /// Pattern is boolean function on `SyntaxElement`. 17 | /// 18 | /// It is like `Box -> bool`, but with additional 19 | /// convenience methods (for example, you can `&` two patterns). `Pattern` also 20 | /// knows the set of node types which *could* match, which allows to implement 21 | /// matching over a set of patterns efficiently. 22 | /// 23 | /// Currently, we liberally box predicates inside of `Pattern`s, as there's only 24 | /// a constant amount of patterns. 25 | #[derive(Clone)] 26 | pub(crate) struct Pattern { 27 | kinds: Option>, 28 | pred: Arc bool)>, 29 | } 30 | 31 | impl AsRef for Pattern { 32 | fn as_ref(&self) -> &Pattern { 33 | self 34 | } 35 | } 36 | 37 | impl fmt::Debug for Pattern { 38 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 39 | f.write_str("Pattern { ... }") 40 | } 41 | } 42 | 43 | impl Pattern { 44 | fn new( 45 | kinds: Option>, 46 | pred: impl Fn(&SyntaxElement) -> bool + 'static, 47 | ) -> Pattern { 48 | Pattern { kinds, pred: Arc::new(pred) } 49 | } 50 | 51 | fn filter_by_kind(kinds: impl Iterator) -> Pattern { 52 | Pattern::new(Some(kinds.collect()), |_| true) 53 | } 54 | 55 | /// Creates a pattern which matches the same elements as `self` with the 56 | /// additional constraint that their parent matches `parent`. 57 | pub(crate) fn with_parent(self, parent: Pattern) -> Pattern { 58 | let Pattern { kinds, pred } = self; 59 | Pattern::new(kinds, move |element| { 60 | (pred)(element) && element.parent().map(|it| parent.matches(&it.into())) == Some(true) 61 | }) 62 | } 63 | 64 | /// Checks if this pattern matches an element 65 | pub(crate) fn matches(&self, element: &SyntaxElement) -> bool { 66 | if let Some(kinds) = self.kinds.as_ref() { 67 | if !kinds.contains(&element.kind()) { 68 | return false; 69 | } 70 | } 71 | (self.pred)(element) 72 | } 73 | } 74 | 75 | /// `pat1 & pat2` operator 76 | impl ops::BitAnd for Pattern { 77 | type Output = Pattern; 78 | fn bitand(self, other: Pattern) -> Pattern { 79 | let kinds = match (self.kinds, other.kinds) { 80 | (Some(lhs), Some(rhs)) => Some(lhs.intersection(&rhs).cloned().collect::>()), 81 | (Some(it), None) | (None, Some(it)) => Some(it), 82 | (None, None) => None, 83 | }; 84 | let (p1, p2) = (self.pred, other.pred); 85 | Pattern::new(kinds, move |element| p1(element) && p2(element)) 86 | } 87 | } 88 | 89 | /// `pat1 | pat2` operator 90 | impl ops::BitOr for Pattern { 91 | type Output = Pattern; 92 | fn bitor(self, other: Pattern) -> Pattern { 93 | let kinds = match (self.kinds, other.kinds) { 94 | (Some(lhs), Some(rhs)) => Some(lhs.union(&rhs).cloned().collect::>()), 95 | (Some(it), None) | (None, Some(it)) => Some(it), 96 | (None, None) => None, 97 | }; 98 | let (p1, p2) = (self.pred, other.pred); 99 | Pattern::new(kinds, move |element| p1(element) || p2(element)) 100 | } 101 | } 102 | 103 | /// Construct pattern from closure. 104 | impl From for Pattern 105 | where 106 | F: for<'a> Fn(&SyntaxElement) -> bool + 'static, 107 | { 108 | fn from(f: F) -> Pattern { 109 | Pattern::new(None, f) 110 | } 111 | } 112 | 113 | /// Construct pattern from a single `SyntaxKind`. 114 | impl From for Pattern { 115 | fn from(kind: SyntaxKind) -> Pattern { 116 | Pattern::filter_by_kind(iter::once(kind)) 117 | } 118 | } 119 | 120 | /// Construct pattern from several `SyntaxKind`s (using slices). 121 | impl From<&'_ [SyntaxKind]> for Pattern { 122 | fn from(kinds: &[SyntaxKind]) -> Pattern { 123 | Pattern::filter_by_kind(kinds.iter().cloned()) 124 | } 125 | } 126 | 127 | /// Construct pattern from several `SyntaxKind`s (using arrays). 128 | macro_rules! from_array { 129 | ($($arity:literal),*) => {$( 130 | impl From<[SyntaxKind; $arity]> for Pattern { 131 | fn from(kinds: [SyntaxKind; $arity]) -> Pattern { 132 | Pattern::from(&kinds[..]) 133 | } 134 | } 135 | )*} 136 | } 137 | from_array!(0, 1, 2, 3, 4, 5, 6, 7, 8); 138 | 139 | /// `PatternSet` allows to match many patterns at the same time efficiently. 140 | /// 141 | /// This is generic over `P: AsRef`, so it works with any type which 142 | /// contains a pattern. 143 | pub(crate) struct PatternSet

{ 144 | by_kind: HashMap>, 145 | unconstrained: Vec

, 146 | } 147 | 148 | impl<'a, P: AsRef> PatternSet<&'a P> { 149 | pub(crate) fn new(patterns: impl Iterator) -> PatternSet<&'a P> { 150 | let mut by_kind: HashMap> = HashMap::new(); 151 | let mut unconstrained = vec![]; 152 | patterns.for_each(|item| { 153 | let pat: &Pattern = item.as_ref(); 154 | if let Some(kinds) = &pat.kinds { 155 | for &kind in kinds { 156 | by_kind.entry(kind).or_default().push(item) 157 | } 158 | } else { 159 | unconstrained.push(item) 160 | } 161 | }); 162 | PatternSet { by_kind, unconstrained } 163 | } 164 | 165 | /// Returns an iterator of patterns that match 166 | pub(crate) fn matching<'b>( 167 | &'b self, 168 | element: SyntaxElement, 169 | ) -> impl Iterator + 'b { 170 | self.by_kind 171 | .get(&element.kind()) 172 | .into_iter() 173 | .flat_map(|vec| vec.iter()) 174 | .chain(self.unconstrained.iter()) 175 | .copied() 176 | .filter(move |p| p.as_ref().matches(&element)) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/tree_utils.rs: -------------------------------------------------------------------------------- 1 | use std::iter::successors; 2 | 3 | use rnix::{ 4 | NodeOrToken, SyntaxElement, 5 | SyntaxKind::{ 6 | NODE_APPLY, NODE_ASSERT, NODE_IF_ELSE, NODE_LAMBDA, NODE_LET_IN, NODE_PAREN, NODE_ROOT, 7 | NODE_STRING_INTERPOL, NODE_WITH, TOKEN_WHITESPACE, 8 | }, 9 | SyntaxNode, SyntaxToken, WalkEvent, 10 | }; 11 | 12 | pub(crate) fn walk(node: &SyntaxNode) -> impl Iterator { 13 | node.preorder_with_tokens().filter_map(|event| match event { 14 | WalkEvent::Enter(element) => Some(element), 15 | WalkEvent::Leave(_) => None, 16 | }) 17 | } 18 | 19 | pub(crate) fn walk_non_whitespace_non_interpol( 20 | node: &SyntaxNode, 21 | ) -> impl Iterator { 22 | let mut interpool_level = 0; 23 | node.preorder_with_tokens().filter_map(move |event| { 24 | match &event { 25 | WalkEvent::Enter(element) if element.kind() == NODE_STRING_INTERPOL => { 26 | interpool_level += 1 27 | } 28 | WalkEvent::Leave(element) if element.kind() == NODE_STRING_INTERPOL => { 29 | interpool_level -= 1 30 | } 31 | _ => (), 32 | } 33 | match event { 34 | WalkEvent::Enter(element) => { 35 | Some(element).filter(|it| interpool_level == 0 && it.kind() != TOKEN_WHITESPACE) 36 | } 37 | WalkEvent::Leave(_) => None, 38 | } 39 | }) 40 | } 41 | 42 | pub(crate) fn walk_tokens(node: &SyntaxNode) -> impl Iterator { 43 | walk(node).filter_map(|element| match element { 44 | NodeOrToken::Token(token) => Some(token), 45 | _ => None, 46 | }) 47 | } 48 | 49 | pub(crate) fn has_newline(node: &SyntaxNode) -> bool { 50 | walk_tokens(node).any(|it| it.text().contains('\n')) 51 | } 52 | 53 | pub(crate) fn prev_sibling(element: &SyntaxElement) -> Option { 54 | successors(element.prev_sibling_or_token(), |it| it.prev_sibling_or_token()).find_map( 55 | |element| match element { 56 | NodeOrToken::Node(it) => Some(it), 57 | NodeOrToken::Token(_) => None, 58 | }, 59 | ) 60 | } 61 | 62 | pub(crate) fn next_sibling(element: &SyntaxElement) -> Option { 63 | successors(element.next_sibling_or_token(), |it| it.next_sibling_or_token()).find_map( 64 | |element| match element { 65 | NodeOrToken::Node(it) => Some(it), 66 | NodeOrToken::Token(_) => None, 67 | }, 68 | ) 69 | } 70 | 71 | pub(crate) fn not_on_top_level(element: &SyntaxElement) -> bool { 72 | !on_top_level(element) 73 | } 74 | 75 | pub(crate) fn on_top_level(element: &SyntaxElement) -> bool { 76 | let parent = match element.parent() { 77 | None => return true, 78 | Some(it) => it, 79 | }; 80 | match parent.kind() { 81 | NODE_ROOT => true, 82 | NODE_LAMBDA | NODE_APPLY | NODE_WITH | NODE_ASSERT | NODE_LET_IN | NODE_IF_ELSE 83 | | NODE_PAREN => on_top_level(&parent.into()), 84 | _ => false, 85 | } 86 | } 87 | 88 | pub(crate) fn prev_token_sibling(element: &SyntaxElement) -> Option { 89 | successors(element.prev_sibling_or_token(), |it| it.prev_sibling_or_token()).find_map( 90 | |element| match element { 91 | NodeOrToken::Node(_) => None, 92 | NodeOrToken::Token(it) => Some(it), 93 | }, 94 | ) 95 | } 96 | 97 | pub(crate) fn prev_non_whitespace_token_sibling(element: &SyntaxElement) -> Option { 98 | successors(element.prev_sibling_or_token(), |it| it.prev_sibling_or_token()).find_map( 99 | |element| match element { 100 | NodeOrToken::Node(_) => None, 101 | NodeOrToken::Token(it) => { 102 | if it.kind() == TOKEN_WHITESPACE { 103 | None 104 | } else { 105 | Some(it) 106 | } 107 | } 108 | }, 109 | ) 110 | } 111 | 112 | pub(crate) fn prev_non_whitespace_sibling(element: &SyntaxElement) -> Option { 113 | successors(element.prev_sibling_or_token(), |it| it.prev_sibling_or_token()) 114 | .find(|it| it.kind() != TOKEN_WHITESPACE) 115 | } 116 | 117 | pub(crate) fn next_non_whitespace_sibling(element: &SyntaxElement) -> Option { 118 | successors(element.next_sibling_or_token(), |it| it.next_sibling_or_token()) 119 | .find(|it| it.kind() != TOKEN_WHITESPACE) 120 | } 121 | 122 | pub(crate) fn preceding_tokens(node: &SyntaxNode) -> impl Iterator { 123 | successors(node.first_token().and_then(|it| it.prev_token()), |it| it.prev_token()) 124 | } 125 | -------------------------------------------------------------------------------- /test_data/attr_fn.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | f = { x 3 | , y 4 | }: body; 5 | 6 | testAllTrue = expr: {inherit expr;expected=map (x: true) expr; }; 7 | } 8 | -------------------------------------------------------------------------------- /test_data/attr_fn.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | f = 3 | { x 4 | , y 5 | }: body; 6 | 7 | testAllTrue = expr: { inherit expr; expected = map (x: true) expr; }; 8 | } 9 | -------------------------------------------------------------------------------- /test_data/binop_wrap_before.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | a = "foo" ++ 3 | "bar" ++ 4 | "lol" 5 | ++ 6 | "bla" 7 | ; 8 | 9 | b = "foo" 10 | ++ "bar" 11 | ++ "lol" 12 | ++ 13 | "bla" 14 | ; 15 | 16 | c = "foo" 17 | ++ "bar" 18 | ++ 19 | "lol" ++ 20 | "bla" 21 | ; 22 | } 23 | -------------------------------------------------------------------------------- /test_data/binop_wrap_before.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | a = "foo" ++ 3 | "bar" ++ 4 | "lol" 5 | ++ 6 | "bla" 7 | ; 8 | 9 | b = "foo" 10 | ++ "bar" 11 | ++ "lol" 12 | ++ 13 | "bla" 14 | ; 15 | 16 | c = "foo" 17 | ++ "bar" 18 | ++ 19 | "lol" ++ 20 | "bla" 21 | ; 22 | } 23 | -------------------------------------------------------------------------------- /test_data/comment_in_inherit.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | inherit (builtins) 3 | # comment 4 | toString 5 | ; 6 | } 7 | -------------------------------------------------------------------------------- /test_data/comment_in_inherit.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | inherit (builtins) 3 | # comment 4 | toString 5 | ; 6 | } 7 | -------------------------------------------------------------------------------- /test_data/comment_indent.bad.nix: -------------------------------------------------------------------------------- 1 | [ 2 | # over indented 3 | 1 4 | # under indented 5 | 2 6 | # FIXME: this whole-line comment should not be moved 7 | ] 8 | -------------------------------------------------------------------------------- /test_data/comment_indent.good.nix: -------------------------------------------------------------------------------- 1 | [ 2 | # over indented 3 | 1 4 | # under indented 5 | 2 6 | # FIXME: this whole-line comment should not be moved 7 | ] 8 | -------------------------------------------------------------------------------- /test_data/concat_in_attr.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | a = [ 0 ] 3 | ++ [ 0 ] 4 | ++ [ 0 ] 5 | ; 6 | 7 | b = 8 | [ 0 ] 9 | ++ [ 0 ] 10 | ++ [ 0 ] 11 | ; 12 | 13 | c = [ 14 | 0 15 | ] 16 | ++ [ 0 ] 17 | ++ [ 0 ] 18 | ; 19 | 20 | d = [ 21 | 0 22 | ] 23 | ++ [ 0 ] 24 | ++ [ 25 | 0 26 | ] 27 | ++ [ 0 ] 28 | ; 29 | 30 | e = 31 | [ 0 ] 32 | ++ [ 33 | 0 34 | ] 35 | ++ [ 0 ] 36 | ; 37 | } 38 | -------------------------------------------------------------------------------- /test_data/concat_in_attr.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | a = [ 0 ] 3 | ++ [ 0 ] 4 | ++ [ 0 ] 5 | ; 6 | 7 | b = 8 | [ 0 ] 9 | ++ [ 0 ] 10 | ++ [ 0 ] 11 | ; 12 | 13 | c = [ 14 | 0 15 | ] 16 | ++ [ 0 ] 17 | ++ [ 0 ] 18 | ; 19 | 20 | d = [ 21 | 0 22 | ] 23 | ++ [ 0 ] 24 | ++ [ 25 | 0 26 | ] 27 | ++ [ 0 ] 28 | ; 29 | 30 | e = 31 | [ 0 ] 32 | ++ [ 33 | 0 34 | ] 35 | ++ [ 0 ] 36 | ; 37 | } 38 | -------------------------------------------------------------------------------- /test_data/curried_fn.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | mkValueStringDefault = {}: v: with builtins; 3 | let err = t: v: abort 4 | "oups"; 5 | in 92; 6 | } 7 | -------------------------------------------------------------------------------- /test_data/curried_fn.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | mkValueStringDefault = {}: v: with builtins; 3 | let 4 | err = t: v: abort 5 | "oups"; 6 | in 7 | 92; 8 | } 9 | -------------------------------------------------------------------------------- /test_data/curried_fn_no_indent.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | foo = 3 | bar: 4 | baz: 5 | fnbody; 6 | } 7 | -------------------------------------------------------------------------------- /test_data/curried_fn_no_indent.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | foo = 3 | bar: 4 | baz: 5 | fnbody; 6 | } 7 | -------------------------------------------------------------------------------- /test_data/existing_blank_lines.bad.nix: -------------------------------------------------------------------------------- 1 | [1 2 | 3 | # first block 4 | 2 5 | 3 6 | 7 | # second block 8 | 4 9 | 5] 10 | -------------------------------------------------------------------------------- /test_data/existing_blank_lines.good.nix: -------------------------------------------------------------------------------- 1 | [ 2 | 1 3 | 4 | # first block 5 | 2 6 | 3 7 | 8 | # second block 9 | 4 10 | 5 11 | ] 12 | -------------------------------------------------------------------------------- /test_data/fn_args_multiline.bad.nix: -------------------------------------------------------------------------------- 1 | { stdenv, lib, 2 | curl 3 | }: 4 | null 5 | -------------------------------------------------------------------------------- /test_data/fn_args_multiline.good.nix: -------------------------------------------------------------------------------- 1 | { stdenv 2 | , lib 3 | , curl 4 | }: 5 | null 6 | -------------------------------------------------------------------------------- /test_data/fn_args_multiple.bad.nix: -------------------------------------------------------------------------------- 1 | a: 2 | b: 3 | c: 4 | a+b+c 5 | -------------------------------------------------------------------------------- /test_data/fn_args_multiple.good.nix: -------------------------------------------------------------------------------- 1 | a: 2 | b: 3 | c: 4 | a + b + c 5 | -------------------------------------------------------------------------------- /test_data/fn_args_singleline.bad.nix: -------------------------------------------------------------------------------- 1 | {stdenv,lib,curl} : 2 | null 3 | -------------------------------------------------------------------------------- /test_data/fn_args_singleline.good.nix: -------------------------------------------------------------------------------- 1 | { stdenv, lib, curl }: 2 | null 3 | -------------------------------------------------------------------------------- /test_data/function_call.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | fooA = fun.f a b (with c; d); 3 | fooB = fun (with a; b) c d; 4 | fooC = fun a (with b; c) d; 5 | fooD = fun a b (with c; { 6 | inherit d; 7 | }); 8 | fooF = fun (with a; b) 9 | c d; 10 | fooG = fun (with a; b) c 11 | d; 12 | fooH = fun a (with b; c) 13 | d; 14 | fooI = fun a 15 | (with b; c) d; 16 | fooJ = fun a 17 | (with b; c) d.a; 18 | fooK = fun a; 19 | fooL = fun 20 | a; 21 | 22 | 23 | barA = fun a b {inherit d;}; 24 | barB = fun {inherit b;} c d; 25 | barC = fun a {inherit c;} d; 26 | barD = fun a b { 27 | inherit d; 28 | }; 29 | barF = fun {inherit b;} c 30 | d; 31 | barG = fun {inherit b;} c 32 | d; 33 | barH = fun a {inherit c;} 34 | d; 35 | barI = fun a 36 | {inherit c;} d; 37 | } 38 | -------------------------------------------------------------------------------- /test_data/function_call.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | fooA = fun.f a b (with c; d); 3 | fooB = fun (with a; b) c d; 4 | fooC = fun a (with b; c) d; 5 | fooD = fun a b (with c; { 6 | inherit d; 7 | }); 8 | fooF = fun (with a; b) 9 | c 10 | d; 11 | fooG = fun (with a; b) c 12 | d; 13 | fooH = fun a (with b; c) 14 | d; 15 | fooI = fun a 16 | (with b; c) 17 | d; 18 | fooJ = fun a 19 | (with b; c) 20 | d.a; 21 | fooK = fun a; 22 | fooL = fun 23 | a; 24 | 25 | 26 | barA = fun a b { inherit d; }; 27 | barB = fun { inherit b; } c d; 28 | barC = fun a { inherit c; } d; 29 | barD = fun a b { 30 | inherit d; 31 | }; 32 | barF = fun { inherit b; } c 33 | d; 34 | barG = fun { inherit b; } c 35 | d; 36 | barH = fun a { inherit c; } 37 | d; 38 | barI = fun a 39 | { inherit c; } 40 | d; 41 | } 42 | -------------------------------------------------------------------------------- /test_data/if_then_else_indent.bad.nix: -------------------------------------------------------------------------------- 1 | if 2 | true 3 | then 4 | 1 5 | else if false 6 | then 7 | 2 8 | else 9 | if true 10 | then 11 | 3 12 | else 13 | 4 14 | -------------------------------------------------------------------------------- /test_data/if_then_else_indent.good.nix: -------------------------------------------------------------------------------- 1 | if 2 | true 3 | then 4 | 1 5 | else if false 6 | then 7 | 2 8 | else 9 | if true 10 | then 11 | 3 12 | else 13 | 4 14 | -------------------------------------------------------------------------------- /test_data/indent_assert_body.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | foo = assert true; 3 | false; 4 | } 5 | -------------------------------------------------------------------------------- /test_data/indent_assert_body.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | foo = assert true; 3 | false; 4 | } 5 | -------------------------------------------------------------------------------- /test_data/indent_fn_body.bad.nix: -------------------------------------------------------------------------------- 1 | {}: 2 | { 3 | foo = x: 4 | 92; 5 | } 6 | -------------------------------------------------------------------------------- /test_data/indent_fn_body.good.nix: -------------------------------------------------------------------------------- 1 | {}: 2 | { 3 | foo = x: 4 | 92; 5 | } 6 | -------------------------------------------------------------------------------- /test_data/indent_lambda_top_level.bad.nix: -------------------------------------------------------------------------------- 1 | import ./make-test-python.nix ({pkgs, lib, ...}: 2 | 3 | let 4 | bar = 57; 5 | in { 6 | baz = qux; 7 | }) -------------------------------------------------------------------------------- /test_data/indent_lambda_top_level.good.nix: -------------------------------------------------------------------------------- 1 | import ./make-test-python.nix ({ pkgs, lib, ... }: 2 | 3 | let 4 | bar = 57; 5 | in 6 | { 7 | baz = qux; 8 | }) 9 | -------------------------------------------------------------------------------- /test_data/indent_let.bad.nix: -------------------------------------------------------------------------------- 1 | let 2 | x = 1; 3 | y = 2; 4 | inherit z; 5 | in x + y + z 6 | -------------------------------------------------------------------------------- /test_data/indent_let.good.nix: -------------------------------------------------------------------------------- 1 | let 2 | x = 1; 3 | y = 2; 4 | inherit z; 5 | in 6 | x + y + z 7 | -------------------------------------------------------------------------------- /test_data/indent_or_default.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | catAttrs = builtins.catAttrs or 3 | (attr: l: concatLists (map (s: if s ? ${attr} then [ s.${attr} ] else []) l)); 4 | } 5 | -------------------------------------------------------------------------------- /test_data/indent_or_default.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | catAttrs = builtins.catAttrs or 3 | (attr: l: concatLists (map (s: if s ? ${attr} then [ s.${attr} ] else [ ]) l)); 4 | } 5 | -------------------------------------------------------------------------------- /test_data/indent_paren.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | foo = ( 3 | ( 4 | ( 5 | 92 6 | ) 7 | ) 8 | ); 9 | xxx = listToAttrs (concatMap (name: 10 | let a = 4; in 5 11 | )); 12 | foldAttrs = op: nul: list_of_attrs: 13 | fold (n: a: 14 | fold (name: o: 15 | o // { ${name} = op n.${name} (a.${name} or nul); } 16 | ) a (attrNames n) 17 | ) {} list_of_attrs; 18 | bar = fun "arg" (callPackage ./. { 19 | inherit foo; 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /test_data/indent_paren.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | foo = ( 3 | ( 4 | ( 5 | 92 6 | ) 7 | ) 8 | ); 9 | xxx = listToAttrs (concatMap (name: 10 | let a = 4; in 5 11 | )); 12 | foldAttrs = op: nul: list_of_attrs: 13 | fold 14 | (n: a: 15 | fold 16 | (name: o: 17 | o // { ${name} = op n.${name} (a.${name} or nul); } 18 | ) 19 | a 20 | (attrNames n) 21 | ) 22 | { } 23 | list_of_attrs; 24 | bar = fun "arg" (callPackage ./. { 25 | inherit foo; 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /test_data/indent_string_literal.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | python = 3 | '' 4 | for i in range(10): 5 | print(i) 6 | ''; 7 | 8 | python_indented = '' 9 | for i in range(10): 10 | print(i) 11 | ''; 12 | 13 | python_last_line1 = 14 | '' 15 | for i in range(10): 16 | print(i)''; 17 | 18 | python_last_line2 = 19 | '' 20 | for i in range(10): 21 | print(i) 22 | ''; 23 | 24 | unindetable = 25 | ''python 26 | for i in range(10): 27 | print(i) 28 | ''; 29 | 30 | nix.extraOptions = '' 31 | builders-use-substitutes = true 32 | ''; 33 | 34 | empty = ''''; 35 | blank = '' ''; 36 | } 37 | -------------------------------------------------------------------------------- /test_data/indent_string_literal.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | python = 3 | '' 4 | for i in range(10): 5 | print(i) 6 | ''; 7 | 8 | python_indented = '' 9 | for i in range(10): 10 | print(i) 11 | ''; 12 | 13 | python_last_line1 = 14 | '' 15 | for i in range(10): 16 | print(i)''; 17 | 18 | python_last_line2 = 19 | '' 20 | for i in range(10): 21 | print(i) 22 | ''; 23 | 24 | unindetable = 25 | ''python 26 | for i in range(10): 27 | print(i) 28 | ''; 29 | 30 | nix.extraOptions = '' 31 | builders-use-substitutes = true 32 | ''; 33 | 34 | empty = ''''; 35 | blank = '' ''; 36 | } 37 | -------------------------------------------------------------------------------- /test_data/indent_string_literal_interpolation.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | postFixup = '' 3 | cd $out/clion-${version} 4 | rm -rf bin/cmake/linux 5 | ln -s ${cmake} bin/cmake/linux 6 | 7 | lldbLibPath=$out/clion-${version}/bin/lldb/linux/lib 8 | interp="$(cat $NIX_CC/nix-support/dynamic-linker)" 9 | ''; 10 | 11 | foo = '' 12 | bar = ${builtins.concatStringsSep " " [ 13 | 1 14 | 2 15 | 3 16 | ]} 17 | bla = hoi 18 | ''; 19 | 20 | bar = '' 21 | foo 22 | ${ 23 | foo 24 | } 25 | foo 26 | ''; 27 | 28 | baz = 29 | '' 30 | foo 31 | ${ 32 | foo 33 | } 34 | foo 35 | ''; 36 | 37 | qux = 38 | '' 39 | bar = ${builtins.concatStringsSep " " [ 40 | 1 41 | 2 42 | 3 43 | ]} 44 | bla = hoi 45 | ''; 46 | 47 | singleAsciiDoc = value: '' 48 | ${ 49 | if lib.hasAttr "example" value then '' 50 | Example:: 51 | ${ 52 | builtins.toJSON value.example 53 | } 54 | '' 55 | else "No Example:: {blank}" 56 | } 57 | ''; 58 | 59 | nested_antiquotation = mkBefore 60 | '' 61 | ${optionalString cfg.earlySetup '' 62 | ${optionalString cfg.earlySetup '' 63 | setfont -C /dev/console $extraUtils/share/consolefonts/font.psf 64 | ''} 65 | setfont -C /dev/console $extraUtils/share/consolefonts/font.psf 66 | ''} 67 | ''; 68 | 69 | singleAsciiDoc = value: '' 70 | Example:: 71 | ${ 72 | builtins.toJSON value.example 73 | } 74 | ''; 75 | 76 | script = writeText "build-maven-repository.sh" '' 77 | ${lib.concatStrings (map (dep: let 78 | fetch = if (url != "") then ((if authenticated then requireFile else fetchurl) { 79 | inherit url sha1; 80 | }) else ""; 81 | in '' 82 | '') info.dependencies)} 83 | ''; 84 | } 85 | -------------------------------------------------------------------------------- /test_data/indent_string_literal_interpolation.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | postFixup = '' 3 | cd $out/clion-${version} 4 | rm -rf bin/cmake/linux 5 | ln -s ${cmake} bin/cmake/linux 6 | 7 | lldbLibPath=$out/clion-${version}/bin/lldb/linux/lib 8 | interp="$(cat $NIX_CC/nix-support/dynamic-linker)" 9 | ''; 10 | 11 | foo = '' 12 | bar = ${builtins.concatStringsSep " " [ 13 | 1 14 | 2 15 | 3 16 | ]} 17 | bla = hoi 18 | ''; 19 | 20 | bar = '' 21 | foo 22 | ${ 23 | foo 24 | } 25 | foo 26 | ''; 27 | 28 | baz = 29 | '' 30 | foo 31 | ${ 32 | foo 33 | } 34 | foo 35 | ''; 36 | 37 | qux = 38 | '' 39 | bar = ${builtins.concatStringsSep " " [ 40 | 1 41 | 2 42 | 3 43 | ]} 44 | bla = hoi 45 | ''; 46 | 47 | singleAsciiDoc = value: '' 48 | ${ 49 | if lib.hasAttr "example" value then '' 50 | Example:: 51 | ${ 52 | builtins.toJSON value.example 53 | } 54 | '' 55 | else "No Example:: {blank}" 56 | } 57 | ''; 58 | 59 | nested_antiquotation = mkBefore 60 | '' 61 | ${optionalString cfg.earlySetup '' 62 | ${optionalString cfg.earlySetup '' 63 | setfont -C /dev/console $extraUtils/share/consolefonts/font.psf 64 | ''} 65 | setfont -C /dev/console $extraUtils/share/consolefonts/font.psf 66 | ''} 67 | ''; 68 | 69 | singleAsciiDoc = value: '' 70 | Example:: 71 | ${ 72 | builtins.toJSON value.example 73 | } 74 | ''; 75 | 76 | script = writeText "build-maven-repository.sh" '' 77 | ${lib.concatStrings (map (dep: let 78 | fetch = if (url != "") then ((if authenticated then requireFile else fetchurl) { 79 | inherit url sha1; 80 | }) else ""; 81 | in '' 82 | '') info.dependencies)} 83 | ''; 84 | } 85 | -------------------------------------------------------------------------------- /test_data/indent_tabs-1.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | test = "1"; 3 | } 4 | -------------------------------------------------------------------------------- /test_data/indent_tabs-1.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | test = "1"; 3 | } 4 | -------------------------------------------------------------------------------- /test_data/indent_tabs-2.bad.nix: -------------------------------------------------------------------------------- 1 | args@{ z , } : 2 | 3 | { 4 | a = "1" ; 5 | b = { } // { } ; 6 | c = z . y ; 7 | d = 1.2 ; 8 | e = 5 ; 9 | f = ./test ; 10 | g = true ; 11 | h = null ; 12 | i = [ 1 ./example.bin { hello = "world" ; } ] ; 13 | j = 5 * 5; 14 | k = z ? fs ; 15 | l = z ++ z; 16 | m = - 324 ; 17 | n = z z ; 18 | o = import ; 19 | p = with z; "fdsfs"; 20 | q = '' 21 | ${5+5} 22 | ''; 23 | r = " f${z}ds "; 24 | } -------------------------------------------------------------------------------- /test_data/indent_tabs-2.good.nix: -------------------------------------------------------------------------------- 1 | args@{ z, }: 2 | 3 | { 4 | a = "1"; 5 | b = { } // { }; 6 | c = z.y; 7 | d = 1.2; 8 | e = 5; 9 | f = ./test; 10 | g = true; 11 | h = null; 12 | i = [ 1 ./example.bin { hello = "world"; } ]; 13 | j = 5 * 5; 14 | k = z ? fs; 15 | l = z ++ z; 16 | m = - 324; 17 | n = z z; 18 | o = import ; 19 | p = with z; "fdsfs"; 20 | q = '' 21 | ${5+5} 22 | ''; 23 | r = " f${z}ds "; 24 | } 25 | -------------------------------------------------------------------------------- /test_data/indent_tabs-3.bad.nix: -------------------------------------------------------------------------------- 1 | # this is cool 2 | { 3 | # nix doesn't see tabs as indentation so don't replace them 4 | test = '' 5 | test 6 | test 7 | ''; 8 | test2 = "fdf dsfs jdfs fsdf df"; 9 | } 10 | -------------------------------------------------------------------------------- /test_data/indent_tabs-3.good.nix: -------------------------------------------------------------------------------- 1 | # this is cool 2 | { 3 | # nix doesn't see tabs as indentation so don't replace them 4 | test = '' 5 | test 6 | test 7 | ''; 8 | test2 = "fdf dsfs jdfs fsdf df"; 9 | } 10 | -------------------------------------------------------------------------------- /test_data/indented_lambda.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | # how to string-format the option name; 3 | # by default one character is a short option (`-`), 4 | # more than one characters a long option (`--`). 5 | mkOptionName ? 6 | k: if builtins.stringLength k == 1 7 | then "-${k}" 8 | else "--${k}" 9 | , mkOption ? k: v: 10 | if v == null 11 | then [ ] 12 | else [ (mkOptionName k) (lib.generators.mkValueStringDefault { } v) ] 13 | }: 14 | { toINI = { 15 | # parameter comment 16 | mkSectionName ? (name: libStr.escape [ "[" "]" ] name) 17 | , mkKeyValue ? mkKeyValueDefault {} "=" 18 | }: attrsOfAttrs: 19 | mapAttrsToStringsSep "\n" mkSection attrsOfAttrs; 20 | } 21 | -------------------------------------------------------------------------------- /test_data/indented_lambda.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | # how to string-format the option name; 3 | # by default one character is a short option (`-`), 4 | # more than one characters a long option (`--`). 5 | mkOptionName ? k: 6 | if builtins.stringLength k == 1 7 | then "-${k}" 8 | else "--${k}" 9 | , mkOption ? k: v: 10 | if v == null 11 | then [ ] 12 | else [ (mkOptionName k) (lib.generators.mkValueStringDefault { } v) ] 13 | }: 14 | { 15 | toINI = 16 | { 17 | # parameter comment 18 | mkSectionName ? (name: libStr.escape [ "[" "]" ] name) 19 | , mkKeyValue ? mkKeyValueDefault { } "=" 20 | }: attrsOfAttrs: 21 | mapAttrsToStringsSep "\n" mkSection attrsOfAttrs; 22 | } 23 | -------------------------------------------------------------------------------- /test_data/issue-125.bad.nix: -------------------------------------------------------------------------------- 1 | {foo,bar}: 2 | let 3 | # comment 4 | foo = let x = y; in 5 | z; 6 | foo2 = 7 | let x = y; in 8 | z; 9 | # comment 10 | bar = let x = 3; in x; 11 | faz = let x = 3; 12 | y = 4; in x; 13 | baz = let x = 3; 14 | in x; 15 | qaz = let x = 3; in 16 | x; 17 | paz = v: let x = 3; in 18 | x; 19 | qux = v: let y = 3; in y; 20 | nux = v: let x = 3; 21 | y = 3; in y; 22 | 23 | in 24 | # comment 25 | body 26 | # who put comment in this line? 27 | -------------------------------------------------------------------------------- /test_data/issue-125.good.nix: -------------------------------------------------------------------------------- 1 | { foo, bar }: 2 | let 3 | # comment 4 | foo = let x = y; in 5 | z; 6 | foo2 = 7 | let x = y; in 8 | z; 9 | # comment 10 | bar = let x = 3; in x; 11 | faz = 12 | let 13 | x = 3; 14 | y = 4; 15 | in 16 | x; 17 | baz = 18 | let x = 3; 19 | in x; 20 | qaz = let x = 3; in 21 | x; 22 | paz = v: 23 | let x = 3; in 24 | x; 25 | qux = v: let y = 3; in y; 26 | nux = v: 27 | let 28 | x = 3; 29 | y = 3; 30 | in 31 | y; 32 | 33 | in 34 | # comment 35 | body 36 | # who put comment in this line? 37 | -------------------------------------------------------------------------------- /test_data/issue-126.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | testA= [(fetchurl { 3 | url = "bla"; 4 | })]; 5 | 6 | testB = [( fetchurl{url="bla";} )]; 7 | 8 | testC = { 9 | testA1= [(fetchurl { 10 | url = "bla"; 11 | })]; 12 | 13 | testB1 = [( fetchurl{url="bla";} )]; 14 | }; 15 | 16 | testD= [(fetchurl { 17 | url = "bla"; 18 | })(fetchurl { 19 | url = "foo"; 20 | })]; 21 | 22 | testE = [( fetchurl{url="bla";} )( fetchurl{url="foo";} )]; 23 | testF = [( fetchurl{ 24 | url="bla";} )( fetchurl{url="foo";} )]; 25 | testG = [( fetchurl{url="bla";} ) bar ]; 26 | testH = [( fetchurl{url="bla";} ) 27 | bar ]; 28 | 29 | testI = { stdenv, ... } @ args: 30 | let 31 | foo = "0.95"; 32 | in 33 | buildLinux ({ 34 | version = "${foo}-mptcp_v1.0.0"; 35 | inherit bar; 36 | } // args); 37 | 38 | testJ = { stdenv, ... } @ args: 39 | let 40 | foo = "0.95"; 41 | in 42 | buildLinux ({ 43 | version = "${foo}-mptcp_v1.0.0"; 44 | inherit bar; 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /test_data/issue-126.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | testA = [ 3 | (fetchurl { 4 | url = "bla"; 5 | }) 6 | ]; 7 | 8 | testB = [ (fetchurl { url = "bla"; }) ]; 9 | 10 | testC = { 11 | testA1 = [ 12 | (fetchurl { 13 | url = "bla"; 14 | }) 15 | ]; 16 | 17 | testB1 = [ (fetchurl { url = "bla"; }) ]; 18 | }; 19 | 20 | testD = [ 21 | (fetchurl { 22 | url = "bla"; 23 | }) 24 | (fetchurl { 25 | url = "foo"; 26 | }) 27 | ]; 28 | 29 | testE = [ (fetchurl { url = "bla"; }) (fetchurl { url = "foo"; }) ]; 30 | testF = [ 31 | (fetchurl { 32 | url = "bla"; 33 | }) 34 | (fetchurl { url = "foo"; }) 35 | ]; 36 | testG = [ (fetchurl { url = "bla"; }) bar ]; 37 | testH = [ 38 | (fetchurl { url = "bla"; }) 39 | bar 40 | ]; 41 | 42 | testI = { stdenv, ... } @ args: 43 | let 44 | foo = "0.95"; 45 | in 46 | buildLinux ({ 47 | version = "${foo}-mptcp_v1.0.0"; 48 | inherit bar; 49 | } // args); 50 | 51 | testJ = { stdenv, ... } @ args: 52 | let 53 | foo = "0.95"; 54 | in 55 | buildLinux ({ 56 | version = "${foo}-mptcp_v1.0.0"; 57 | inherit bar; 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /test_data/issue-132.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | buildInputs = [ 3 | ] ++ stdenv.lib.optionals enableGui (with qt5; [ qtbase qtwebkit ]) 4 | ++ stdenv.lib.optionals enableJupyter [ boost jsoncpp openssl zmqpp ] 5 | ; 6 | } 7 | -------------------------------------------------------------------------------- /test_data/issue-132.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | buildInputs = [ 3 | ] ++ stdenv.lib.optionals enableGui (with qt5; [ qtbase qtwebkit ]) 4 | ++ stdenv.lib.optionals enableJupyter [ boost jsoncpp openssl zmqpp ] 5 | ; 6 | } 7 | -------------------------------------------------------------------------------- /test_data/issue-151.bad.nix: -------------------------------------------------------------------------------- 1 | let 2 | aaa = 3 | {foo,bar}: 4 | foo+bar 5 | ; 6 | bbb = {foo,bar}: 7 | foo+bar 8 | ; 9 | ccc = 10 | {foo,bar}: 11 | let x = foo + bar; in x; 12 | ddd = 13 | {foo,bar}: 14 | { 15 | inherit foo bar; 16 | }; 17 | in 18 | null 19 | -------------------------------------------------------------------------------- /test_data/issue-151.good.nix: -------------------------------------------------------------------------------- 1 | let 2 | aaa = 3 | { foo, bar }: 4 | foo + bar 5 | ; 6 | bbb = { foo, bar }: 7 | foo + bar 8 | ; 9 | ccc = 10 | { foo, bar }: 11 | let x = foo + bar; in x; 12 | ddd = 13 | { foo, bar }: 14 | { 15 | inherit foo bar; 16 | }; 17 | in 18 | null 19 | -------------------------------------------------------------------------------- /test_data/issue-152.bad.nix: -------------------------------------------------------------------------------- 1 | let 2 | id = 3 | x: 4 | # comment 5 | x; 6 | foo = x: 7 | #comment 8 | x+x; 9 | bar = x: let y = x; 10 | in 11 | #comment 12 | x; 13 | baz = x: 14 | #comment 15 | y: 16 | #comment 17 | foo+bar; 18 | in 19 | #comment 20 | id 1 21 | -------------------------------------------------------------------------------- /test_data/issue-152.good.nix: -------------------------------------------------------------------------------- 1 | let 2 | id = 3 | x: 4 | # comment 5 | x; 6 | foo = x: 7 | #comment 8 | x + x; 9 | bar = x: 10 | let y = x; 11 | in 12 | #comment 13 | x; 14 | baz = x: 15 | #comment 16 | y: 17 | #comment 18 | foo + bar; 19 | in 20 | #comment 21 | id 1 22 | -------------------------------------------------------------------------------- /test_data/issue-158.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | foo = if bar != null 3 | then bar 4 | else baz; 5 | bar = 6 | if bar == null 7 | then foo 8 | else baz; 9 | qux = if bux == null then nux else baz; 10 | nux = if foo == baz then bar 11 | else bar; 12 | } 13 | -------------------------------------------------------------------------------- /test_data/issue-158.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | foo = 3 | if bar != null 4 | then bar 5 | else baz; 6 | bar = 7 | if bar == null 8 | then foo 9 | else baz; 10 | qux = if bux == null then nux else baz; 11 | nux = 12 | if foo == baz then bar 13 | else bar; 14 | } 15 | -------------------------------------------------------------------------------- /test_data/issue-161.bad.nix: -------------------------------------------------------------------------------- 1 | ( 2 | builtins.foldl' ( 3 | acc: v: let 4 | isOperator = builtins.typeOf v == "list"; 5 | operator = if isOperator then (builtins.elemAt v 0) else acc.operator; 6 | in 7 | if isOperator then (acc // { inherit operator; }) else { 8 | inherit operator; 9 | state = operators."${operator}" acc.state (satisfiesSemver pythonVersion v); 10 | } 11 | ) 12 | { 13 | operator = ","; 14 | state = true; 15 | } 16 | tokens 17 | ).state 18 | -------------------------------------------------------------------------------- /test_data/issue-161.good.nix: -------------------------------------------------------------------------------- 1 | ( 2 | builtins.foldl' 3 | ( 4 | acc: v: 5 | let 6 | isOperator = builtins.typeOf v == "list"; 7 | operator = if isOperator then (builtins.elemAt v 0) else acc.operator; 8 | in 9 | if isOperator then (acc // { inherit operator; }) else { 10 | inherit operator; 11 | state = operators."${operator}" acc.state (satisfiesSemver pythonVersion v); 12 | } 13 | ) 14 | { 15 | operator = ","; 16 | state = true; 17 | } 18 | tokens 19 | ).state 20 | -------------------------------------------------------------------------------- /test_data/issue-162.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | inherit (import ./pep425.nix { 3 | inherit lib python; 4 | inherit (pkgs) stdenv; 5 | }) selectWheel; 6 | 7 | foo = 3; 8 | 9 | inherit bar; 10 | } 11 | -------------------------------------------------------------------------------- /test_data/issue-162.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | inherit (import ./pep425.nix { 3 | inherit lib python; 4 | inherit (pkgs) stdenv; 5 | }) selectWheel; 6 | 7 | foo = 3; 8 | 9 | inherit bar; 10 | } 11 | -------------------------------------------------------------------------------- /test_data/issue-178.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | testA =(buildCargoCrate { 3 | name = "nixpkgs-fmt"; 4 | # TODO: probably want to filter .gitignore or something 5 | src = sources.nixpkgs-fmt; 6 | }).nixpkgs-fmt.build; 7 | 8 | testB=buildLinux (map ({ name }: 9 | {inherit name; }) cfg.feeds); 10 | } 11 | -------------------------------------------------------------------------------- /test_data/issue-178.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | testA = (buildCargoCrate { 3 | name = "nixpkgs-fmt"; 4 | # TODO: probably want to filter .gitignore or something 5 | src = sources.nixpkgs-fmt; 6 | }).nixpkgs-fmt.build; 7 | 8 | testB = buildLinux (map 9 | ({ name }: 10 | { inherit name; }) 11 | cfg.feeds); 12 | } 13 | -------------------------------------------------------------------------------- /test_data/issue-181.bad.nix: -------------------------------------------------------------------------------- 1 | { python3 }: 2 | python3.pkgs.buildPythonApplication { 3 | propagatedBuildInputs = (with python3.pkgs; [ 4 | pkg1 5 | ]); 6 | testB = forAllSystems ( 7 | system: 8 | self.apps.${system}.nixpkgs-fmt); 9 | } 10 | -------------------------------------------------------------------------------- /test_data/issue-181.good.nix: -------------------------------------------------------------------------------- 1 | { python3 }: 2 | python3.pkgs.buildPythonApplication { 3 | propagatedBuildInputs = (with python3.pkgs; [ 4 | pkg1 5 | ]); 6 | testB = forAllSystems ( 7 | system: 8 | self.apps.${system}.nixpkgs-fmt 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /test_data/issue-185.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | attrs = {}; 3 | lists = [ ]; 4 | } 5 | -------------------------------------------------------------------------------- /test_data/issue-185.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | attrs = { }; 3 | lists = [ ]; 4 | } 5 | -------------------------------------------------------------------------------- /test_data/issue-196.bad.nix: -------------------------------------------------------------------------------- 1 | let 2 | x = 3 | let 4 | y= 4; 5 | in 6 | assert true; 7 | y; 8 | in 9 | x 10 | -------------------------------------------------------------------------------- /test_data/issue-196.good.nix: -------------------------------------------------------------------------------- 1 | let 2 | x = 3 | let 4 | y = 4; 5 | in 6 | assert true; 7 | y; 8 | in 9 | x 10 | -------------------------------------------------------------------------------- /test_data/issue-199.bad.nix: -------------------------------------------------------------------------------- 1 | let 2 | # comment 3 | 4 | 5 | a = (if true then 1 else 2); 6 | b = "${if true then "1" else "2"}"; 7 | 8 | 9 | # comment 10 | 11 | 12 | c = '' 13 | ${foo (if true then '' 14 | bla 15 | '' else '' 16 | blub 17 | '')} 18 | ''; 19 | 20 | d = '' 21 | ${if true then '' 22 | bla 23 | '' else '' 24 | blub 25 | ''} 26 | ''; 27 | 28 | 29 | 30 | 31 | in 32 | {} 33 | -------------------------------------------------------------------------------- /test_data/issue-199.good.nix: -------------------------------------------------------------------------------- 1 | let 2 | # comment 3 | 4 | 5 | a = (if true then 1 else 2); 6 | b = "${if true then "1" else "2"}"; 7 | 8 | 9 | # comment 10 | 11 | 12 | c = '' 13 | ${foo (if true then '' 14 | bla 15 | '' else '' 16 | blub 17 | '')} 18 | ''; 19 | 20 | d = '' 21 | ${if true then '' 22 | bla 23 | '' else '' 24 | blub 25 | ''} 26 | ''; 27 | 28 | 29 | 30 | 31 | in 32 | { } 33 | -------------------------------------------------------------------------------- /test_data/issue-205.bad.nix: -------------------------------------------------------------------------------- 1 | let 2 | a = { 3 | inherit foo;inherit bar; 4 | }; 5 | b = { 6 | inherit foo;# Comment 7 | }; 8 | c = { 9 | inherit foo;bar = baz; 10 | }; 11 | in 12 | { } 13 | -------------------------------------------------------------------------------- /test_data/issue-205.good.nix: -------------------------------------------------------------------------------- 1 | let 2 | a = { 3 | inherit foo; inherit bar; 4 | }; 5 | b = { 6 | inherit foo; # Comment 7 | }; 8 | c = { 9 | inherit foo; bar = baz; 10 | }; 11 | in 12 | { } 13 | -------------------------------------------------------------------------------- /test_data/issue-83-1.bad.nix: -------------------------------------------------------------------------------- 1 | let 2 | a = '' 3 | foo 4 | ''; 5 | in a 6 | -------------------------------------------------------------------------------- /test_data/issue-83-1.good.nix: -------------------------------------------------------------------------------- 1 | let 2 | a = '' 3 | foo 4 | ''; 5 | in 6 | a 7 | -------------------------------------------------------------------------------- /test_data/issue-83-2.bad.nix: -------------------------------------------------------------------------------- 1 | (let 2 | a = '' 3 | foo 4 | ''; 5 | in a) 6 | -------------------------------------------------------------------------------- /test_data/issue-83-2.good.nix: -------------------------------------------------------------------------------- 1 | ( 2 | let 3 | a = '' 4 | foo 5 | ''; 6 | in 7 | a 8 | ) 9 | -------------------------------------------------------------------------------- /test_data/issue-83-3.bad.nix: -------------------------------------------------------------------------------- 1 | (foo { 2 | inherit bar; 3 | }) 4 | -------------------------------------------------------------------------------- /test_data/issue-83-3.good.nix: -------------------------------------------------------------------------------- 1 | (foo { 2 | inherit bar; 3 | }) 4 | -------------------------------------------------------------------------------- /test_data/leading_whitespace.bad.nix: -------------------------------------------------------------------------------- 1 | {foo,bar}: 2 | foo+bar 3 | -------------------------------------------------------------------------------- /test_data/leading_whitespace.good.nix: -------------------------------------------------------------------------------- 1 | { foo, bar }: 2 | foo + bar 3 | -------------------------------------------------------------------------------- /test_data/list_elements.bad.nix: -------------------------------------------------------------------------------- 1 | [ 2 | 1 3 | [] 4 | { a = 92; } 5 | "hello" 6 | foo.bar 7 | foo 8 | (92) 9 | ] 10 | -------------------------------------------------------------------------------- /test_data/list_elements.good.nix: -------------------------------------------------------------------------------- 1 | [ 2 | 1 3 | [ ] 4 | { a = 92; } 5 | "hello" 6 | foo.bar 7 | foo 8 | (92) 9 | ] 10 | -------------------------------------------------------------------------------- /test_data/list_multiline.bad.nix: -------------------------------------------------------------------------------- 1 | [ 1 2 2 | 3 ] 3 | -------------------------------------------------------------------------------- /test_data/list_multiline.good.nix: -------------------------------------------------------------------------------- 1 | [ 2 | 1 3 | 2 4 | 3 5 | ] 6 | -------------------------------------------------------------------------------- /test_data/list_with_commented_out_item.bad.nix: -------------------------------------------------------------------------------- 1 | [ one /*two*/ three four 2 | ] 3 | -------------------------------------------------------------------------------- /test_data/list_with_commented_out_item.good.nix: -------------------------------------------------------------------------------- 1 | [ 2 | one /*two*/ 3 | three 4 | four 5 | ] 6 | -------------------------------------------------------------------------------- /test_data/nested_if_else.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | modifyA = n: fn: v: if (n == 0) then fn v 3 | else if isList v then map (modify (n - 1) fn) v 4 | else if isAttrs v then mapAttrs 5 | (const (modify (n - 1) fn)) v 6 | else v; 7 | modifyB = n: fn: v: 8 | if (n == 0) then fn v 9 | else if isList v then map (modify (n - 1) fn) v 10 | else if isAttrs v then mapAttrs 11 | (const (modify (n - 1) fn)) v 12 | else v; 13 | } 14 | -------------------------------------------------------------------------------- /test_data/nested_if_else.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | modifyA = n: fn: v: 3 | if (n == 0) then fn v 4 | else if isList v then map (modify (n - 1) fn) v 5 | else if isAttrs v then 6 | mapAttrs 7 | (const (modify (n - 1) fn)) 8 | v 9 | else v; 10 | modifyB = n: fn: v: 11 | if (n == 0) then fn v 12 | else if isList v then map (modify (n - 1) fn) v 13 | else if isAttrs v then 14 | mapAttrs 15 | (const (modify (n - 1) fn)) 16 | v 17 | else v; 18 | } 19 | -------------------------------------------------------------------------------- /test_data/nested_indent.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? import ./nix/nixpkgs.nix { }, 3 | src ? builtins.fetchGit { 4 | url = ./.; 5 | ref = "HEAD"; 6 | } 7 | }: 8 | pkgs.example rec { 9 | } 10 | -------------------------------------------------------------------------------- /test_data/nested_indent.good.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import ./nix/nixpkgs.nix { } 2 | , src ? builtins.fetchGit { 3 | url = ./.; 4 | ref = "HEAD"; 5 | } 6 | }: 7 | pkgs.example rec { } 8 | -------------------------------------------------------------------------------- /test_data/nested_indent_after_binop.bad.nix: -------------------------------------------------------------------------------- 1 | pkgs.example rec { 2 | buildInputs = [ pkgs.foo ] ++ 3 | pkgs.stdenv.lib.optionals pkgs.stdenv.isDarwin [ 4 | pkgs.bar 5 | ]; 6 | } 7 | -------------------------------------------------------------------------------- /test_data/nested_indent_after_binop.good.nix: -------------------------------------------------------------------------------- 1 | pkgs.example rec { 2 | buildInputs = [ pkgs.foo ] ++ 3 | pkgs.stdenv.lib.optionals pkgs.stdenv.isDarwin [ 4 | pkgs.bar 5 | ]; 6 | } 7 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/doc_shellnix.bad.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import ../. {} }: 2 | (import ./default.nix {}).overrideAttrs (x: { 3 | buildInputs = x.buildInputs ++ [ pkgs.xmloscopy pkgs.ruby ]; 4 | 5 | }) 6 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/doc_shellnix.good.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import ../. { } }: 2 | (import ./default.nix { }).overrideAttrs (x: { 3 | buildInputs = x.buildInputs ++ [ pkgs.xmloscopy pkgs.ruby ]; 4 | 5 | }) 6 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/flakenix.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib = lib // { 3 | nixosSystem = { modules, ... } @ args: 4 | import ./nixos/lib/eval-config.nix (args // { 5 | modules = modules ++ 6 | [ 7 | { 8 | system.nixos.versionSuffix = 9 | ".${lib.substring 0 8 self.lastModified}.${self.shortRev or "dirty"}"; 10 | system.nixos.revision = lib.mkIf (self ? rev) self.rev; 11 | } 12 | ]; 13 | }); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/flakenix.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib = lib // { 3 | nixosSystem = { modules, ... } @ args: 4 | import ./nixos/lib/eval-config.nix (args // { 5 | modules = modules ++ 6 | [ 7 | { 8 | system.nixos.versionSuffix = 9 | ".${lib.substring 0 8 self.lastModified}.${self.shortRev or "dirty"}"; 10 | system.nixos.revision = lib.mkIf (self ? rev) self.rev; 11 | } 12 | ]; 13 | }); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/lib_attrset.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | matchAttrs = pattern: attrs: assert isAttrs pattern; 3 | fold and true (attrValues (zipAttrsWithNames (attrNames pattern) (n: values: 4 | let pat = head values; val = head (tail values); in 5 | if length values == 1 then false 6 | else if isAttrs pat then isAttrs val && matchAttrs pat val 7 | else pat == val 8 | ) [ pattern attrs ])); 9 | } 10 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/lib_attrset.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | matchAttrs = pattern: attrs: assert isAttrs pattern; 3 | fold and true (attrValues (zipAttrsWithNames (attrNames pattern) 4 | (n: values: 5 | let pat = head values; val = head (tail values); in 6 | if length values == 1 then false 7 | else if isAttrs pat then isAttrs val && matchAttrs pat val 8 | else pat == val 9 | ) [ pattern attrs ])); 10 | } 11 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/lib_cli.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | foo = { 3 | baz 4 | }: 5 | options: 6 | let 7 | x = baz; 8 | y = 1; 9 | in 10 | x - y; 11 | } 12 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/lib_cli.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | foo = 3 | { baz 4 | }: 5 | options: 6 | let 7 | x = baz; 8 | y = 1; 9 | in 10 | x - y; 11 | } 12 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/lib_customisation.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | overrideDerivation = drv: f: 3 | let 4 | newDrv = derivation (drv.drvAttrs // (f drv)); 5 | in lib.flip (extendDerivation true) newDrv ( 6 | { 7 | meta = drv.meta or { }; 8 | passthru = if drv ? passthru then drv.passthru else { }; 9 | } 10 | // 11 | (drv.passthru or { }) 12 | // 13 | (if (drv ? crossDrv && drv ? nativeDrv) 14 | then { 15 | crossDrv = overrideDerivation drv.crossDrv f; 16 | nativeDrv = overrideDerivation drv.nativeDrv f; 17 | } 18 | else { })); 19 | } 20 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/lib_customisation.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | overrideDerivation = drv: f: 3 | let 4 | newDrv = derivation (drv.drvAttrs // (f drv)); 5 | in 6 | lib.flip (extendDerivation true) newDrv ( 7 | { 8 | meta = drv.meta or { }; 9 | passthru = if drv ? passthru then drv.passthru else { }; 10 | } 11 | // 12 | (drv.passthru or { }) 13 | // 14 | (if (drv ? crossDrv && drv ? nativeDrv) 15 | then { 16 | crossDrv = overrideDerivation drv.crossDrv f; 17 | nativeDrv = overrideDerivation drv.nativeDrv f; 18 | } 19 | else { }) 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/lib_deprecated.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | innerClosePropagation = acc: xs: 3 | if xs == [] 4 | then acc 5 | else let y = head xs; 6 | ys = tail xs; 7 | in if ! isAttrs y 8 | then innerClosePropagation acc ys 9 | else let acc' = [y] ++ acc; 10 | in innerClosePropagation 11 | acc' 12 | (uniqList { inputList = (maybeAttrNullable "propagatedBuildInputs" [] y) 13 | ++ (maybeAttrNullable "propagatedNativeBuildInputs" [] y) 14 | ++ ys; 15 | acc = acc'; 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/lib_deprecated.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | innerClosePropagation = acc: xs: 3 | if xs == [ ] 4 | then acc 5 | else 6 | let 7 | y = head xs; 8 | ys = tail xs; 9 | in 10 | if ! isAttrs y 11 | then innerClosePropagation acc ys 12 | else 13 | let acc' = [ y ] ++ acc; 14 | in innerClosePropagation 15 | acc' 16 | (uniqList { 17 | inputList = (maybeAttrNullable "propagatedBuildInputs" [ ] y) 18 | ++ (maybeAttrNullable "propagatedNativeBuildInputs" [ ] y) 19 | ++ ys; 20 | acc = acc'; 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/lib_generators.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | toINI = 3 | { 4 | # apply transformations (e.g. escapes) to section names 5 | mkSectionName ? (name: libStr.escape [ "[" "]" ] name) 6 | }: attrsOfAttrs: 7 | let 8 | # map function to string for each key val 9 | mapAttrsToStringsSep = sep: mapFn: attrs: 10 | libStr.concatStringsSep sep 11 | (libAttr.mapAttrsToList mapFn attrs); 12 | mkSection = sectName: sectValues: '' 13 | [${mkSectionName sectName}] 14 | '' + toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } sectValues; 15 | in 16 | # map input to ini sections 17 | mapAttrsToStringsSep "\n" mkSection attrsOfAttrs; 18 | 19 | expr = ind: x: with builtins; 20 | if x == null then "" else 21 | if isBool x then bool ind x else 22 | if isInt x then int ind x else 23 | if isString x then str ind x else 24 | if isList x then list ind x else 25 | if isAttrs x then attrs ind x else 26 | if isFloat x then float ind x else 27 | abort "generators.toPlist: should never happen (v = ${v})"; 28 | } 29 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/lib_generators.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | toINI = 3 | { 4 | # apply transformations (e.g. escapes) to section names 5 | mkSectionName ? (name: libStr.escape [ "[" "]" ] name) 6 | }: attrsOfAttrs: 7 | let 8 | # map function to string for each key val 9 | mapAttrsToStringsSep = sep: mapFn: attrs: 10 | libStr.concatStringsSep sep 11 | (libAttr.mapAttrsToList mapFn attrs); 12 | mkSection = sectName: sectValues: '' 13 | [${mkSectionName sectName}] 14 | '' + toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } sectValues; 15 | in 16 | # map input to ini sections 17 | mapAttrsToStringsSep "\n" mkSection attrsOfAttrs; 18 | 19 | expr = ind: x: with builtins; 20 | if x == null then "" else 21 | if isBool x then bool ind x else 22 | if isInt x then int ind x else 23 | if isString x then str ind x else 24 | if isList x then list ind x else 25 | if isAttrs x then attrs ind x else 26 | if isFloat x then float ind x else 27 | abort "generators.toPlist: should never happen (v = ${v})"; 28 | } 29 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/lib_modules.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | foo = { 3 | z 4 | , y ? 0 5 | }: 6 | let x = 1; 7 | y = 2; 8 | in x + y + z; 9 | 10 | bar = 11 | { 12 | a 13 | , b ? 0 14 | }: 15 | let c = 1; 16 | b = 2; 17 | in a + b + c; 18 | 19 | baz = 20 | { 21 | pkg 22 | , num ? 0 23 | }: 24 | let val = 1; num = 2; in 25 | pkg + num + val; 26 | } 27 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/lib_modules.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | foo = 3 | { z 4 | , y ? 0 5 | }: 6 | let 7 | x = 1; 8 | y = 2; 9 | in 10 | x + y + z; 11 | 12 | bar = 13 | { a 14 | , b ? 0 15 | }: 16 | let 17 | c = 1; 18 | b = 2; 19 | in 20 | a + b + c; 21 | 22 | baz = 23 | { pkg 24 | , num ? 0 25 | }: 26 | let val = 1; num = 2; in 27 | pkg + num + val; 28 | } 29 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/lib_strings.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | fixedWidthString = width: filler: str: 3 | let 4 | strw = lib.stringLength str; 5 | reqWidth = width - (lib.stringLength filler); 6 | in 7 | assert lib.assertMsg 8 | (strw <= width) 9 | "fixedWidthString: requested string length (${ 10 | toString width}) must not be shorter than actual length (${ 11 | toString strw})"; 12 | if strw == width then str else filler + fixedWidthString reqWidth filler str; 13 | } 14 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/lib_strings.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | fixedWidthString = width: filler: str: 3 | let 4 | strw = lib.stringLength str; 5 | reqWidth = width - (lib.stringLength filler); 6 | in 7 | assert lib.assertMsg 8 | (strw <= width) 9 | "fixedWidthString: requested string length (${ 10 | toString width}) must not be shorter than actual length (${ 11 | toString strw})"; 12 | if strw == width then str else filler + fixedWidthString reqWidth filler str; 13 | } 14 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/lib_tests_modules_alias_priority_override.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | imports = [ 3 | # Create an alias for the "enable" option. 4 | (mkAliasOptionModule [ "enableAlias" ] [ "enable" ]) 5 | 6 | # Disable the aliased option with a high priority so it 7 | # should override the next import. 8 | ({ config, lib, ... }: 9 | { 10 | enableAlias = lib.mkForce false; 11 | }) 12 | 13 | # Enable the normal (non-aliased) option. 14 | ({ config, lib, ... }: 15 | { 16 | enable = true; 17 | } ) ]; 18 | } 19 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/lib_tests_modules_alias_priority_override.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | imports = [ 3 | # Create an alias for the "enable" option. 4 | (mkAliasOptionModule [ "enableAlias" ] [ "enable" ]) 5 | 6 | # Disable the aliased option with a high priority so it 7 | # should override the next import. 8 | ({ config, lib, ... }: 9 | { 10 | enableAlias = lib.mkForce false; 11 | }) 12 | 13 | # Enable the normal (non-aliased) option. 14 | ({ config, lib, ... }: 15 | { 16 | enable = true; 17 | }) 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/maintainers_maintainer_list.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | keys = [{ 3 | longkeyid = "rsa8192/0x87027528B006D66D"; 4 | fingerprint = "F466 A548 AD3F C1F1 8C88 4576 8702 7528 B006 D66D"; 5 | }]; 6 | 7 | keys = [ 8 | { 9 | longkeyid = "rsa8192/0x87027528B006D66D"; 10 | fingerprint = "F466 A548 AD3F C1F1 8C88 4576 8702 7528 B006 D66D"; 11 | }]; 12 | } 13 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/maintainers_maintainer_list.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | keys = [{ 3 | longkeyid = "rsa8192/0x87027528B006D66D"; 4 | fingerprint = "F466 A548 AD3F C1F1 8C88 4576 8702 7528 B006 D66D"; 5 | }]; 6 | 7 | keys = [ 8 | { 9 | longkeyid = "rsa8192/0x87027528B006D66D"; 10 | fingerprint = "F466 A548 AD3F C1F1 8C88 4576 8702 7528 B006 D66D"; 11 | } 12 | ]; 13 | } 14 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/maintainers_scripts_update.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | packagesWithUpdateScriptAndMaintainer = maintainer': 3 | let 4 | maintainer = 5 | if ! builtins.hasAttr maintainer' pkgs.lib.maintainers then 6 | builtins.throw "Maintainer with name `${maintainer'} does not exist in `maintainers/maintainer-list.nix`." 7 | else 8 | builtins.getAttr maintainer' pkgs.lib.maintainers; 9 | in 10 | packagesWith (name: pkg: builtins.hasAttr "updateScript" pkg && 11 | (if builtins.hasAttr "maintainers" pkg.meta 12 | then (if builtins.isList pkg.meta.maintainers 13 | then builtins.elem maintainer pkg.meta.maintainers 14 | else maintainer == pkg.meta.maintainers) 15 | else false) 16 | ) 17 | (name: pkg: pkg) 18 | pkgs; 19 | 20 | packagesWithUpdateScript = path: 21 | let 22 | attrSet = pkgs.lib.attrByPath (pkgs.lib.splitString "." path) null pkgs; 23 | in 24 | if attrSet == null then 25 | builtins.throw "Attribute path `${path}` does not exists." 26 | else 27 | packagesWith (name: pkg: builtins.hasAttr "updateScript" pkg) 28 | (name: pkg: pkg) 29 | attrSet; 30 | } 31 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/maintainers_scripts_update.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | packagesWithUpdateScriptAndMaintainer = maintainer': 3 | let 4 | maintainer = 5 | if ! builtins.hasAttr maintainer' pkgs.lib.maintainers then 6 | builtins.throw "Maintainer with name `${maintainer'} does not exist in `maintainers/maintainer-list.nix`." 7 | else 8 | builtins.getAttr maintainer' pkgs.lib.maintainers; 9 | in 10 | packagesWith 11 | (name: pkg: builtins.hasAttr "updateScript" pkg && 12 | (if builtins.hasAttr "maintainers" pkg.meta 13 | then 14 | (if builtins.isList pkg.meta.maintainers 15 | then builtins.elem maintainer pkg.meta.maintainers 16 | else maintainer == pkg.meta.maintainers) 17 | else false) 18 | ) 19 | (name: pkg: pkg) 20 | pkgs; 21 | 22 | packagesWithUpdateScript = path: 23 | let 24 | attrSet = pkgs.lib.attrByPath (pkgs.lib.splitString "." path) null pkgs; 25 | in 26 | if attrSet == null then 27 | builtins.throw "Attribute path `${path}` does not exists." 28 | else 29 | packagesWith (name: pkg: builtins.hasAttr "updateScript" pkg) 30 | (name: pkg: pkg) 31 | attrSet; 32 | } 33 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/nixos_lib_build_vms.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | buildVM = 3 | nodes: configurations: 4 | 5 | import ./eval-config.nix { 6 | inherit system; 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/nixos_lib_build_vms.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | buildVM = 3 | nodes: configurations: 4 | 5 | import ./eval-config.nix { 6 | inherit system; 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/nixos_lib_testing_python.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | driver = let warn = if skipLint then lib.warn "Linting is disabled!" else lib.id; in warn (runCommand testDriverName 3 | { 4 | buildInputs = [ makeWrapper ]; 5 | testScript = testScript'; 6 | preferLocalBuild = true; 7 | testName = name; 8 | } 9 | '' 10 | mkdir -p $out/bin 11 | echo -n "$testScript" > $out/test-script 12 | ${lib.optionalString (!skipLint) '' 13 | ${python3Packages.black}/bin/black --check --diff $out/test-script 14 | ''} 15 | ln -s ${testDriver}/bin/nixos-test-driver $out/bin/ 16 | vms=($(for i in ${toString vms}; do echo $i/bin/run-*-vm; done)) 17 | wrapProgram $out/bin/nixos-test-driver \ 18 | --add-flags "''${vms[*]}" \ 19 | ${lib.optionalString enableOCR 20 | "--prefix PATH : '${ocrProg}/bin:${imagemagick_tiff}/bin'"} \ 21 | --run "export testScript=\"\$(${coreutils}/bin/cat $out/test-script)\"" \ 22 | --set VLANS '${toString vlans}' 23 | ln -s ${testDriver}/bin/nixos-test-driver $out/bin/nixos-run-vms 24 | wrapProgram $out/bin/nixos-run-vms \ 25 | --add-flags "''${vms[*]}" \ 26 | ${lib.optionalString enableOCR "--prefix PATH : '${ocrProg}/bin'"} \ 27 | --set tests 'start_all(); join_all();' \ 28 | --set VLANS '${toString vlans}' \ 29 | ${lib.optionalString (builtins.length vms == 1) "--set USE_SERIAL 1"} 30 | '' 31 | ); # " 32 | } 33 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/nixos_lib_testing_python.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | driver = let warn = if skipLint then lib.warn "Linting is disabled!" else lib.id; in warn (runCommand testDriverName 3 | { 4 | buildInputs = [ makeWrapper ]; 5 | testScript = testScript'; 6 | preferLocalBuild = true; 7 | testName = name; 8 | } 9 | '' 10 | mkdir -p $out/bin 11 | echo -n "$testScript" > $out/test-script 12 | ${lib.optionalString (!skipLint) '' 13 | ${python3Packages.black}/bin/black --check --diff $out/test-script 14 | ''} 15 | ln -s ${testDriver}/bin/nixos-test-driver $out/bin/ 16 | vms=($(for i in ${toString vms}; do echo $i/bin/run-*-vm; done)) 17 | wrapProgram $out/bin/nixos-test-driver \ 18 | --add-flags "''${vms[*]}" \ 19 | ${lib.optionalString enableOCR 20 | "--prefix PATH : '${ocrProg}/bin:${imagemagick_tiff}/bin'"} \ 21 | --run "export testScript=\"\$(${coreutils}/bin/cat $out/test-script)\"" \ 22 | --set VLANS '${toString vlans}' 23 | ln -s ${testDriver}/bin/nixos-test-driver $out/bin/nixos-run-vms 24 | wrapProgram $out/bin/nixos-run-vms \ 25 | --add-flags "''${vms[*]}" \ 26 | ${lib.optionalString enableOCR "--prefix PATH : '${ocrProg}/bin'"} \ 27 | --set tests 'start_all(); join_all();' \ 28 | --set VLANS '${toString vlans}' \ 29 | ${lib.optionalString (builtins.length vms == 1) "--set USE_SERIAL 1"} 30 | '' 31 | ); # " 32 | } 33 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/nixos_modules_config_console.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | boot.initrd.preLVMCommands = mkBefore '' 3 | kbd_mode ${ if isUnicode then "-u" else "-a"} -C /dev/console 4 | printf "\033%%${ if isUnicode then "G" else "@"}" >> /dev/console 5 | loadkmap < ${optimizedKeymap} 6 | ${optionalString cfg.earlySetup '' 7 | setfont -C /dev/console $extraUtils/share/consolefonts/font.psf 8 | ''} 9 | ''; 10 | } 11 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/nixos_modules_config_console.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | boot.initrd.preLVMCommands = mkBefore '' 3 | kbd_mode ${ if isUnicode then "-u" else "-a"} -C /dev/console 4 | printf "\033%%${ if isUnicode then "G" else "@"}" >> /dev/console 5 | loadkmap < ${optimizedKeymap} 6 | ${optionalString cfg.earlySetup '' 7 | setfont -C /dev/console $extraUtils/share/consolefonts/font.psf 8 | ''} 9 | ''; 10 | } 11 | -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/nixpkgs_idempotent.bad.nix: -------------------------------------------------------------------------------- 1 | { lib }: 2 | # Operations on attribute sets. 3 | rec { 4 | filterAttrs = pred: set: 5 | listToAttrs (concatMap (name: let v = set.${name}; in if pred name v then [(nameValuePair name v)] else []) (attrNames set)); 6 | 7 | inherit (self.trivial) id const pipe concat or and bitAnd bitOr bitXor; 8 | } -------------------------------------------------------------------------------- /test_data/nixpkgs_repository/nixpkgs_idempotent.good.nix: -------------------------------------------------------------------------------- 1 | { lib }: 2 | # Operations on attribute sets. 3 | rec { 4 | filterAttrs = pred: set: 5 | listToAttrs (concatMap (name: let v = set.${name}; in if pred name v then [ (nameValuePair name v) ] else [ ]) (attrNames set)); 6 | 7 | inherit (self.trivial) id const pipe concat or and bitAnd bitOr bitXor; 8 | } 9 | -------------------------------------------------------------------------------- /test_data/operators_whitespace.bad.nix: -------------------------------------------------------------------------------- 1 | 4+5 - 6/ (4* 3) 2 | -------------------------------------------------------------------------------- /test_data/operators_whitespace.good.nix: -------------------------------------------------------------------------------- 1 | 4 + 5 - 6 / (4 * 3) 2 | -------------------------------------------------------------------------------- /test_data/path-interpolation.bad.nix: -------------------------------------------------------------------------------- 1 | ./a/b/${a}-b.nix 2 | -------------------------------------------------------------------------------- /test_data/path-interpolation.good.nix: -------------------------------------------------------------------------------- 1 | ./a/b/${a}-b.nix 2 | -------------------------------------------------------------------------------- /test_data/reindents_block_comments.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | /* 3 | decorated 4 | block 5 | comment 6 | */ 7 | foo = 92; 8 | /* the closing block comment 9 | should not in the newline */ 10 | toPretty = { 11 | /* the closing block comment 12 | * should be aligned with the opening block comment 13 | */ 14 | allowPrettyValues ? false 15 | }@args: 16 | let a = 0; in a; 17 | /* Construct a binary search path (such as $PATH) containing the 18 | binaries for a set of packages. 19 | Example: 20 | makeBinPath ["/root" "/usr" "/usr/local"] 21 | => "/root/bin:/usr/bin:/usr/local/bin" 22 | */ 23 | makeBinPath = makeSearchPathOutput "bin" "bin"; 24 | /* concatMapAttrsToList :: (string -> any -> [any]) -> set -> [any] 25 | * 26 | * Like mapAttrsToList, but allows multiple values to be returned 27 | * from the mapping function as a list. 28 | */ 29 | concatMapAttrsToList = mapper: attrs: concatLists (mapAttrsToList mapper attrs); 30 | } -------------------------------------------------------------------------------- /test_data/reindents_block_comments.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | /* 3 | decorated 4 | block 5 | comment 6 | */ 7 | foo = 92; 8 | /* the closing block comment 9 | should not in the newline */ 10 | toPretty = 11 | { 12 | /* the closing block comment 13 | * should be aligned with the opening block comment 14 | */ 15 | allowPrettyValues ? false 16 | }@args: 17 | let a = 0; in a; 18 | /* Construct a binary search path (such as $PATH) containing the 19 | binaries for a set of packages. 20 | Example: 21 | makeBinPath ["/root" "/usr" "/usr/local"] 22 | => "/root/bin:/usr/bin:/usr/local/bin" 23 | */ 24 | makeBinPath = makeSearchPathOutput "bin" "bin"; 25 | /* concatMapAttrsToList :: (string -> any -> [any]) -> set -> [any] 26 | * 27 | * Like mapAttrsToList, but allows multiple values to be returned 28 | * from the mapping function as a list. 29 | */ 30 | concatMapAttrsToList = mapper: attrs: concatLists (mapAttrsToList mapper attrs); 31 | } 32 | -------------------------------------------------------------------------------- /test_data/semicolon_in_set.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | a = [ 3 | elem1 4 | elem2 5 | elem3 6 | ] 7 | ; 8 | 9 | b = { 10 | foo = bar; 11 | } 12 | ; 13 | 14 | c = [ x ] 15 | ++ [ y ] 16 | ++ [ z ]; 17 | 18 | d = with foo; [ 19 | bar 20 | ] 21 | ; 22 | 23 | e = a: b: 24 | a+b 25 | ; 26 | } 27 | -------------------------------------------------------------------------------- /test_data/semicolon_in_set.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | a = [ 3 | elem1 4 | elem2 5 | elem3 6 | ]; 7 | 8 | b = { 9 | foo = bar; 10 | }; 11 | 12 | c = [ x ] 13 | ++ [ y ] 14 | ++ [ z ]; 15 | 16 | d = with foo; [ 17 | bar 18 | ]; 19 | 20 | e = a: b: 21 | a + b 22 | ; 23 | } 24 | -------------------------------------------------------------------------------- /test_data/set_multi_elem_value.bad.nix: -------------------------------------------------------------------------------- 1 | { stdenv, lib }: 2 | { 3 | list = [ 4 | elem1 5 | elem2 6 | elem3 7 | ] ++ lib.optionals stdenv.isDarwin [ elem4 elem5 ] ++ lib.optionals stdenv.isLinux [ elem6 ]; 8 | } 9 | -------------------------------------------------------------------------------- /test_data/set_multi_elem_value.good.nix: -------------------------------------------------------------------------------- 1 | { stdenv, lib }: 2 | { 3 | list = [ 4 | elem1 5 | elem2 6 | elem3 7 | ] ++ lib.optionals stdenv.isDarwin [ elem4 elem5 ] ++ lib.optionals stdenv.isLinux [ elem6 ]; 8 | } 9 | -------------------------------------------------------------------------------- /test_data/set_multiline.bad.nix: -------------------------------------------------------------------------------- 1 | { xxx = 5; 2 | hello = 4; world = 6; 3 | } 4 | -------------------------------------------------------------------------------- /test_data/set_multiline.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | xxx = 5; 3 | hello = 4; 4 | world = 6; 5 | } 6 | -------------------------------------------------------------------------------- /test_data/set_nested_indent.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | foo = { 3 | bar = 1; 4 | }; 5 | } 6 | -------------------------------------------------------------------------------- /test_data/set_nested_indent.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | foo = { 3 | bar = 1; 4 | }; 5 | } 6 | -------------------------------------------------------------------------------- /test_data/set_singleline.bad.nix: -------------------------------------------------------------------------------- 1 | {foo=4;bar=5;} 2 | -------------------------------------------------------------------------------- /test_data/set_singleline.good.nix: -------------------------------------------------------------------------------- 1 | { foo = 4; bar = 5; } 2 | -------------------------------------------------------------------------------- /test_data/syntax_errors/incomplete_set.bad.nix: -------------------------------------------------------------------------------- 1 | { 2 | traceIf = 3 | pred: 4 | -------------------------------------------------------------------------------- /test_data/syntax_errors/incomplete_set.good.nix: -------------------------------------------------------------------------------- 1 | { 2 | traceIf = 3 | pred: 4 | -------------------------------------------------------------------------------- /test_data/syntax_errors/issue-93.bad.nix: -------------------------------------------------------------------------------- 1 | {} = 2 | -------------------------------------------------------------------------------- /test_data/syntax_errors/issue-93.good.nix: -------------------------------------------------------------------------------- 1 | { } = 2 | -------------------------------------------------------------------------------- /test_data/top_level_assert.bad.nix: -------------------------------------------------------------------------------- 1 | { lib }: 2 | assert true; 3 | let 4 | foo = 92; 5 | in 6 | foo 7 | -------------------------------------------------------------------------------- /test_data/top_level_assert.good.nix: -------------------------------------------------------------------------------- 1 | { lib }: 2 | assert true; 3 | let 4 | foo = 92; 5 | in 6 | foo 7 | -------------------------------------------------------------------------------- /test_data/top_level_let.bad.nix: -------------------------------------------------------------------------------- 1 | { stdenv, fetchFromGitHub }: 2 | let 3 | pname = "hello"; 4 | version = "1.2.3"; 5 | in #comment 6 | with pname; 7 | stdenv.mkDerivation { 8 | inherit pname version; 9 | src = fetchFromGitHub { 10 | owner = "xxx"; 11 | repo = pname; 12 | rev = version; 13 | sha256 = "..."; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /test_data/top_level_let.good.nix: -------------------------------------------------------------------------------- 1 | { stdenv, fetchFromGitHub }: 2 | let 3 | pname = "hello"; 4 | version = "1.2.3"; 5 | in 6 | #comment 7 | with pname; 8 | stdenv.mkDerivation { 9 | inherit pname version; 10 | src = fetchFromGitHub { 11 | owner = "xxx"; 12 | repo = pname; 13 | rev = version; 14 | sha256 = "..."; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /test_data/top_level_with.bad.nix: -------------------------------------------------------------------------------- 1 | { lib }: 2 | with stdenv.lib; 3 | let 4 | foo = 92; 5 | in 6 | foo 7 | -------------------------------------------------------------------------------- /test_data/top_level_with.good.nix: -------------------------------------------------------------------------------- 1 | { lib }: 2 | with stdenv.lib; 3 | let 4 | foo = 92; 5 | in 6 | foo 7 | -------------------------------------------------------------------------------- /test_data/top_level_with2.bad.nix: -------------------------------------------------------------------------------- 1 | with import {}; 2 | stdenv.mkDerivation { 3 | name = "rnix"; 4 | buildInputs = [ cargo ]; 5 | } 6 | -------------------------------------------------------------------------------- /test_data/top_level_with2.good.nix: -------------------------------------------------------------------------------- 1 | with import { }; 2 | stdenv.mkDerivation { 3 | name = "rnix"; 4 | buildInputs = [ cargo ]; 5 | } 6 | -------------------------------------------------------------------------------- /test_data/trailing_comment.bad.nix: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | rider = buildRider rec { 4 | name = "rider-${version}"; 5 | version = "2018.3.4"; /* updated by script */ 6 | }; 7 | } 8 | [ 9 | 1 10 | 2 # <- this 11 | ] 12 | ] 13 | -------------------------------------------------------------------------------- /test_data/trailing_comment.good.nix: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | rider = buildRider rec { 4 | name = "rider-${version}"; 5 | version = "2018.3.4"; /* updated by script */ 6 | }; 7 | } 8 | [ 9 | 1 10 | 2 # <- this 11 | ] 12 | ] 13 | -------------------------------------------------------------------------------- /tests/integration_tests.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::path::Path; 3 | use std::process::{Command, Stdio}; 4 | 5 | // It's an important Unix convention that text processing tools such as one might 6 | // use in a pipeline terminate cleanly if stdout is closed prematurely. If grep 7 | // didn't do this, there'd be an error message every time somebody piped it into 8 | // head. Unix processes even do this by default -- when a process writes to a 9 | // closed pipe, it's sent a SIGPIPE signal, which by default terminates the 10 | // process. But the Rust runtime ignores SIGPIPE, so Rust programs have to go out 11 | // of their way to restore the default SIGPIPE behaviour (or emulate it by checking 12 | // for EPIPE every time they write) to be good Unix citizens. 13 | fn test_stdout_closed(args: &[&str]) { 14 | use libc::{waitpid, SIGPIPE, WIFSIGNALED, WTERMSIG}; 15 | use std::io::copy; 16 | use std::thread::spawn; 17 | 18 | // Drop the child's stdout to close it. 19 | let (pid, mut stdin) = { 20 | let child = Command::new(env!("CARGO_BIN_EXE_nixpkgs-fmt")) 21 | .args(args) 22 | .stdin(Stdio::piped()) 23 | .stdout(Stdio::piped()) 24 | .spawn() 25 | .unwrap(); 26 | 27 | (child.id(), child.stdin.unwrap()) 28 | }; 29 | 30 | spawn(move || { 31 | let input_path = 32 | Path::new(env!("CARGO_MANIFEST_DIR")).join("test_data/binop_wrap_before.bad.nix"); 33 | let mut input = File::open(input_path).unwrap(); 34 | copy(&mut input, &mut stdin).unwrap(); 35 | drop(stdin) 36 | }); 37 | 38 | // We have to use libc because we don't have a Child struct any more. 39 | let mut wstatus = 0; 40 | assert_eq!(unsafe { waitpid(pid as i32, &mut wstatus, 0) }, pid as i32); 41 | 42 | assert!(WIFSIGNALED(wstatus)); 43 | assert_eq!(WTERMSIG(wstatus), SIGPIPE); 44 | } 45 | 46 | #[test] 47 | fn stdout_closed() { 48 | test_stdout_closed(&[]); 49 | } 50 | 51 | #[test] 52 | fn stdout_closed_parse() { 53 | test_stdout_closed(&["--parse"]); 54 | } 55 | 56 | #[test] 57 | fn stdout_closed_explain() { 58 | test_stdout_closed(&["--explain"]); 59 | } 60 | -------------------------------------------------------------------------------- /update-cargo-nix.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Run this script to prepare the release 4 | # 5 | set -euo pipefail 6 | 7 | echo Build cargo 8 | 9 | cargo build 10 | 11 | echo Update the README 12 | 13 | mdsh 14 | 15 | echo Testing that everything works 16 | 17 | nix-build 18 | 19 | echo SUCCESS 20 | -------------------------------------------------------------------------------- /wasm/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nix-community/nixpkgs-fmt/bdb15b4c7e0cb49ae091dd43113d0a938afae02c/wasm/.nojekyll -------------------------------------------------------------------------------- /wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nixpkgs-fmt-wasm" 3 | version = "0.1.0" 4 | authors = [ 5 | "Aleksey Kladov ", 6 | "zimbatm " 7 | ] 8 | edition = "2018" 9 | license = "Apache-2.0" 10 | description = "WASM bindings to the nix code formatter for nixpkgs" 11 | repository = "https://github.com/nix-community/nixpkgs-fmt" 12 | 13 | [lib] 14 | crate-type = ["cdylib"] 15 | 16 | [dependencies] 17 | console_error_panic_hook = "0.1.6" 18 | difflib = "0.4.0" 19 | nixpkgs-fmt = { "path" = "../." } 20 | wasm-bindgen = "0.2" 21 | wee_alloc = "0.4.4" 22 | -------------------------------------------------------------------------------- /wasm/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /wasm/README.md: -------------------------------------------------------------------------------- 1 | # nixpkgs-fmt-wasm 2 | 3 | This package is re-exporting the nixpkgs-fmt library to WASM. The goal is to 4 | provide a demo page for users to test and report issues. 5 | 6 | ## Dependencies 7 | 8 | Run `nix-shell` in the parent folder if you are fortunate enough to have Nix 9 | installed. Otherwise go to https://rustwasm.github.io/wasm-pack/installer/ and 10 | follow the installation instructions there. 11 | 12 | ## Building 13 | 14 | Run `./build.sh` to build the WASM target. It's going to take a while. 15 | 16 | Once the project has finished to build, all the outputs will be under `./pkg`. 17 | 18 | ## Running 19 | 20 | Use a static file server like [caddy](https://caddyserver.com/) to serve the 21 | demo. Start `caddy` and then go to http://localhost:2015/ . Enjoy! 22 | 23 | ## TODO 24 | 25 | * Automatically publish master to github pages 26 | * Submit formatting as GitHub issues 27 | -------------------------------------------------------------------------------- /wasm/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd "$(dirname "$0")" 4 | 5 | wasm-pack build --target web 6 | -------------------------------------------------------------------------------- /wasm/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | nixpkgs-fmt-wasm 6 | 8 | 9 | 10 |

11 |

nixpkgs-fmt

12 | 13 | 17 | 18 |

This project's goal is to provide a nix code formatter that would be 19 | applied on nixpkgs. Ideally automatically with a tool like ofborg.

20 | 21 |

WASM Demo

22 | 23 |

This project is written in Rust and made available to the browser by 24 | compiling it to WASM. This will require a modern web browser (Firefox, 25 | Safari or Chrome).

26 | 27 |

Try the formatter out by pasting your Nix code in the left pane or 28 | using the file selector.

29 | 30 |
31 | nixpkgs-fmt is still under heavy development. Formatting issues should 32 | happen quite often at this point. 33 |
34 | 35 |
36 |
37 |
38 |
39 |
40 | 43 | 44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |

 52 |             
 56 |           
57 |
58 |
59 |
60 | 61 |
62 | 63 | 64 | 65 | 66 |

Not liking the result?

67 | 68 |
69 |
70 | 71 | 72 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /wasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::panic; 2 | use wasm_bindgen::prelude::*; 3 | 4 | // Use the smaller `wee_alloc` as the global allocator. 5 | #[global_allocator] 6 | static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; 7 | 8 | // Called when the wasm module is instantiated 9 | #[wasm_bindgen(start)] 10 | pub fn main() -> Result<(), JsValue> { 11 | panic::set_hook(Box::new(console_error_panic_hook::hook)); 12 | Ok(()) 13 | } 14 | 15 | #[wasm_bindgen] 16 | pub fn reformat_string(text: &str, format: &str) -> String { 17 | let out = nixpkgs_fmt::reformat_string(text); 18 | 19 | if format == "diff" { 20 | let first_text = text.lines().collect::>(); 21 | let second_text = out.lines().collect::>(); 22 | let diff = difflib::unified_diff(&first_text, &second_text, "Input", "Output", "", "", 3); 23 | if diff.len() == 0 { 24 | return String::from("No changes found"); 25 | } else { 26 | return [diff[0..3].join(""), diff[3..].join("\n")].join(""); 27 | } 28 | } else { 29 | return out; 30 | } 31 | } 32 | --------------------------------------------------------------------------------