Compare commits

...

56 commits

Author SHA1 Message Date
Roberto Aloi
7a4eccf14d Add support for linter-specific excluded_apps
Some checks failed
Deploy Website to GitHub Pages / Deploy Website to GitHub Pages (push) Has been cancelled
ELP CI / edb (push) Has been cancelled
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, true, linux-x64) (push) Has been cancelled
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, true, linux-arm64) (push) Has been cancelled
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-15-intel, macos-15-x64, x86_64-apple-darwin, true, darwin-x64) (push) Has been cancelled
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-latest, macos-latest-arm, aarch64-apple-darwin, true, darwin-arm64) (push) Has been cancelled
ELP CI / ci (26, 26.2.5.13, windows, 26.2, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, true, win32-x64) (push) Has been cancelled
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Has been cancelled
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Has been cancelled
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Has been cancelled
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Has been cancelled
ELP CI / ci (27, 27.3.4, windows, 27.3, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Has been cancelled
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Has been cancelled
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Has been cancelled
ELP CI / ci (28, 28.0.1, macos, 28, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Has been cancelled
ELP CI / ci (28, 28.0.1, macos, 28, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Has been cancelled
ELP CI / ci (28, 28.0.1, windows, 28, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Has been cancelled
Summary:
We may want to disable a linter for a certain set of applications. This diff introduces such a mechanism:

```
[linters.my_linter]
exclude_apps = ["app_a", "app_2"]
```

Reviewed By: alanz

Differential Revision: D89658976

fbshipit-source-id: 78e1cf13ec22f1bf7ed1b6df6892cdad5ca2dfe5
2025-12-23 03:53:02 -08:00
Roberto Aloi
4bba415bc1 Fix diagnostic name
Some checks are pending
ELP CI / edb (push) Waiting to run
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, true, linux-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, true, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-15-intel, macos-15-x64, x86_64-apple-darwin, true, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-latest, macos-latest-arm, aarch64-apple-darwin, true, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, windows, 26.2, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, true, win32-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, windows, 27.3, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, windows, 28, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
Deploy Website to GitHub Pages / Deploy Website to GitHub Pages (push) Waiting to run
Summary: Missed during the original rename.

Reviewed By: alanz

Differential Revision: D89460380

fbshipit-source-id: 60446aa3f505d635ad5ddc842b2d420c24f0a9d2
2025-12-22 09:17:24 -08:00
Roberto Aloi
6456f325c3 Ignore buck-out in test_projects
Some checks failed
ELP CI / edb (push) Has been cancelled
Deploy Website to GitHub Pages / Deploy Website to GitHub Pages (push) Has been cancelled
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, true, linux-x64) (push) Has been cancelled
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, true, linux-arm64) (push) Has been cancelled
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-15-intel, macos-15-x64, x86_64-apple-darwin, true, darwin-x64) (push) Has been cancelled
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-latest, macos-latest-arm, aarch64-apple-darwin, true, darwin-arm64) (push) Has been cancelled
ELP CI / ci (26, 26.2.5.13, windows, 26.2, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, true, win32-x64) (push) Has been cancelled
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Has been cancelled
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Has been cancelled
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Has been cancelled
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Has been cancelled
ELP CI / ci (27, 27.3.4, windows, 27.3, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Has been cancelled
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Has been cancelled
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Has been cancelled
ELP CI / ci (28, 28.0.1, macos, 28, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Has been cancelled
ELP CI / ci (28, 28.0.1, macos, 28, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Has been cancelled
ELP CI / ci (28, 28.0.1, windows, 28, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Has been cancelled
Summary: So that version control systems do not try to publish the directory.

Reviewed By: alanz

Differential Revision: D89550389

fbshipit-source-id: d60646ddd4e8f0ae443673dac3c49efbac60dba7
2025-12-19 08:58:10 -08:00
Roberto Aloi
5c7cfae09d Only enable Buck2 project model tests if feature is enabled
Summary:
We already have a feature to understand if Buck2 is enabled or not. Let's use that one instead of a constant.

This change has no effect anyway since the only two use cases are currently marked as "ignored".

Also remove the extra check inside the helper (we already do it in both tests).

Reviewed By: alanz

Differential Revision: D89549782

fbshipit-source-id: 82e5bbf00c463dca3f4937d2d2a12d16aebc3c19
2025-12-19 08:58:10 -08:00
Alan Zimmerman
220acfd4f7 Back out "Allow project discovery for OTP"
Summary: It breaks navigation to stdlib

Reviewed By: TD5

Differential Revision: D89552844

fbshipit-source-id: 676af5f520d9189f98828b4364b0b1665531b245
2025-12-19 08:49:54 -08:00
Roberto Aloi
fc09ff94fd Enable buck2 tests on GitHub
Summary:
The test projects are now standalone, so the Buck2 tests can be enabled in CI.

Two major fixes to the tests:

* Use the project directory to calculate the project root (there's no `.buckconfig` for the ELP crate as a whole in OSS)
* Fix expectations for the Buck2 errors (no URL in OSS)

Reviewed By: alanz

Differential Revision: D89547047

fbshipit-source-id: 76fa8bcc1fadabbeb9ea59245d36d2aacd98a023
2025-12-19 07:43:01 -08:00
Alan Zimmerman
8e82f1cee4 Fix codegen_test test project for new buck root
Summary: As title

Reviewed By: TheGeorge

Differential Revision: D89543670

fbshipit-source-id: 8d4a417a58950ad9f9d8877839561f407d0e9cc0
2025-12-19 04:09:00 -08:00
Facebook Community Bot
8cb6ac7620
Re-sync with internal repository (#144)
The internal and external repositories are out of sync. This Pull Request attempts to brings them back in sync by patching the GitHub repository. Please carefully review this patch. You must disable ShipIt for your project in order to merge this pull request. DO NOT IMPORT this pull request. Instead, merge it directly on GitHub using the MERGE BUTTON. Re-enable ShipIt after merging.

fbshipit-source-id: 39bc64c1a96888b1034f7807ced9b7b2365df04e
2025-12-19 10:51:11 +00:00
Roberto Aloi
8b4c533527 Convert EDoc syntax to plain comments in codegen_test_SUITE
Some checks are pending
ELP CI / edb (push) Waiting to run
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, true, linux-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, true, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-15-intel, macos-15-x64, x86_64-apple-darwin, true, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-latest, macos-latest-arm, aarch64-apple-darwin, true, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, windows, 26.2, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, true, win32-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, windows, 27.3, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, windows, 28, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
Deploy Website to GitHub Pages / Deploy Website to GitHub Pages (push) Waiting to run
Summary:
Convert old EDoc style syntax (%%% doc, end) in test suite to plain
comments. EDoc documentation is unnecessary in test suites.

Differential Revision: D89526339

fbshipit-source-id: 0111f2ded79a7e802d29b95d6b79889e0a55af32
2025-12-18 23:41:12 -08:00
Roberto Aloi
b12cf72c7e Standalone Buck2 setup for test_projects
Some checks are pending
ELP CI / edb (push) Waiting to run
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, true, linux-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, true, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-15-intel, macos-15-x64, x86_64-apple-darwin, true, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-latest, macos-latest-arm, aarch64-apple-darwin, true, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, windows, 26.2, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, true, win32-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, windows, 27.3, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, windows, 28, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
Deploy Website to GitHub Pages / Deploy Website to GitHub Pages (push) Waiting to run
Summary: We want to enable Buck2 tests on GitHub. This requires a bit of groundwork.

Reviewed By: michalmuskala

Differential Revision: D88959148

fbshipit-source-id: e5f88f561e061635aae05c4e3bc22382771a8908
2025-12-18 14:47:07 -08:00
Alan Zimmerman
ce0125dec9 1/n: bound var in match: detect problem
Some checks are pending
ELP CI / edb (push) Waiting to run
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, true, linux-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, true, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-15-intel, macos-15-x64, x86_64-apple-darwin, true, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-latest, macos-latest-arm, aarch64-apple-darwin, true, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, windows, 26.2, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, true, win32-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, windows, 27.3, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, windows, 28, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
Deploy Website to GitHub Pages / Deploy Website to GitHub Pages (push) Waiting to run
Summary:
In non-test Erlang code, the following is almost always intended as assignment of a value to the LHS variable

```
Var = expr()
```

It is actually a match, and may fail if `Var` is already bound, and the new value differs from the old.

This diff introduces a diagnostic to warn of this case.

Reviewed By: robertoaloi

Differential Revision: D89374198

fbshipit-source-id: 19dcee50ad6e71d0a606a0eb6cf548b7194d61e6
2025-12-18 07:05:33 -08:00
Alan Zimmerman
d7b1c561ad Fix GH CI for OTP 26
Summary: As title.

Reviewed By: robertoaloi

Differential Revision: D89461312

fbshipit-source-id: 3c0c75da6ccd00735d3f444b123b0bab222d0980
2025-12-18 05:36:31 -08:00
Roberto Aloi
3804bd1706 Enable undefined_function linter in CI (prod only)
Summary:
The linter is enabled by default in ELP, and warnings appear in the IDE.
But the same linter is not explicitly added to the CI configuration, so warnings are not reported on diffs or in Contlint.

This diff enables the linter, aligning the IDE and the CI experience. It also makes it possible to track violations via ContLint.

We exclude known false positives via config.

Ideally, we would like to report undefined functions in all files, but there are too many false positives in test files to do so.
This is often due to mocked modules and test suite cleverness. We can revisit this decision in the future. See T249044930 for details.

We disable the linter for tests both in the default value (for the future) and the config (so we don't need to wait for a ELP release to land this change).

Reviewed By: alanz

Differential Revision: D89393641

fbshipit-source-id: bfb9ac760ba79566301087033af95bb805fdeaa0
2025-12-18 03:37:42 -08:00
Roberto Aloi
439ebade1b Rename edoc linter into old_edoc_syntax
Summary: The old name was too generic, and could be mis-interepreted as an EDoc linter (a linter that verifies EDoc syntax). By aligning the name of the linter with the actual diagnostic code, we make the intention more clear. No functional change.

Reviewed By: alanz

Differential Revision: D89374941

fbshipit-source-id: bf8518ed9602bdceca2fcbf3ddb00990f56af529
2025-12-18 03:37:42 -08:00
Roberto Aloi
54dd1089ed Do not run the old_edoc_syntax linter against test files by default
Summary:
Documentation is generally not produced for test files, so do not run the `old_edoc_syntax` linter for tests. If not, edoc references in tests should be converted to plain comments, not to `-moduledoc` or `-doc` attributes.

This behaviour can always be overwritten by setting the `severity` or the `include_tests` properties of the linter via config.

Reviewed By: alanz

Differential Revision: D89374552

fbshipit-source-id: 8a4299fcbd16a82f62efbc14f47fef46cd08fbc0
2025-12-18 03:37:42 -08:00
Alan Zimmerman
fa05043043 Allow project discovery for OTP (#141)
Some checks are pending
ELP CI / edb (push) Waiting to run
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, true, linux-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, true, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-15-intel, macos-15-x64, x86_64-apple-darwin, true, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-latest, macos-latest-arm, aarch64-apple-darwin, true, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, windows, 26.2, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, true, win32-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, windows, 27.3, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, windows, 28, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
Deploy Website to GitHub Pages / Deploy Website to GitHub Pages (push) Waiting to run
Summary:
Some clients, such as emacs eglot, do their own project discovery when a new file is opened, even from the target of a go to definition.

They then launch a fresh ELP server if they determine a different project root.

In the ELP server setup we preconfigure the OTP project root as known, but with a project manifest of None. This means go to def into an OTP file ends up with an uninitialized project in the new server.

This diff makes it so that if the project root is known, but has no project manifest, we do discovery on that root.

Pull Request resolved: https://github.com/whatsapp/erlang-language-platform/pull/141

Reviewed By: robertoaloi

Differential Revision: D89169465

Pulled By: alanz

fbshipit-source-id: db180d67df98fd1f1fd61a53a93f9adfa9e93ebd
2025-12-17 09:53:03 -08:00
Victor Lanvin
79c33480c5 Support for OTP version specific tests
Some checks are pending
ELP CI / edb (push) Waiting to run
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, true, linux-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, true, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-15-intel, macos-15-x64, x86_64-apple-darwin, true, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-latest, macos-latest-arm, aarch64-apple-darwin, true, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, windows, 26.2, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, true, win32-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, windows, 27.3, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, windows, 28, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
Deploy Website to GitHub Pages / Deploy Website to GitHub Pages (push) Waiting to run
Summary:
Remove the tag `OTPVersionDependent` which created one expected result per OTP version per snapshot.

Instead, use `OTPXXOnly` (where `XX` is a version number), that skips checks for OTP versions that are not `XX`.

In practice, it's used as `OTP27Only` for now.

Reviewed By: michalmuskala, TD5

Differential Revision: D89378687

fbshipit-source-id: 0421622d825ac4e8f393d32529c01eb17577b5e5
2025-12-17 08:54:32 -08:00
Alan Zimmerman
7057624c73 elp lint: print diagnostics in the case we are not streaming and not applying a fix
Summary:
Fix two bugs for `--no-stream` for `elp lint`

1. args.skip_stream_print() now skips if either the flag is set, or `--apply-fixes` is set
2. When we are not streaming, and also not applyiing fixes, print the diagnostics

Reviewed By: robertoaloi

Differential Revision: D89369072

fbshipit-source-id: 2279aca861fb09c5b33adb0000e3a58821026064
2025-12-17 06:36:56 -08:00
Alan Zimmerman
4a051d1af8 10/n: rename module: in defines RHS
Summary: Add a test demonstrating that the module rename happens in `.hrl` files too

Reviewed By: TheGeorge

Differential Revision: D89053579

fbshipit-source-id: f999484c08a5c33c76986156c10a2f6a7d34fc52
2025-12-17 06:00:04 -08:00
Alan Zimmerman
91a0d968db 9/n: rename module: remote calls in original module
Summary:
Simplify the rename module logic to use the standard approach, find usages, rename each of them.

And in the process ensure that remote calls in the module being renamed are also renamed.

Reviewed By: robertoaloi

Differential Revision: D88947976

fbshipit-source-id: c703be8392b7ac634ced2c45d8553d33882d460b
2025-12-17 06:00:04 -08:00
Alan Zimmerman
cc0a9c26d5 8/n: rename module: well known function arguments
Summary:
Certain functions are known to take a module name as an argument.  We already have a list of those that correspond to referencing a function as an MFA, in `to_def.rs`.  We extend this with some other examples, and check for renaming them too as part of the module rename.

The list is currently incommplete, and can be filled out based on usage. A future diff may look at ways of generating it from `-spec` information.

Reviewed By: robertoaloi

Differential Revision: D88943301

fbshipit-source-id: 05c67a108b1dc5b64f4353872427361fd039171f
2025-12-17 06:00:04 -08:00
Alan Zimmerman
83d5b4dfd0 7/n: rename module: check module name in a call
Summary:
When identifying a call from a reference, we start from the ast token for the module name, and ascend the ast until we find a call.

But up to now we have not checked that the module we started from is the module part of the call MFA.

This diff adds that check.

Reviewed By: robertoaloi

Differential Revision: D88934411

fbshipit-source-id: 81a57520ec834210fcea8fd6920653cc45f9890c
2025-12-17 06:00:04 -08:00
Alan Zimmerman
9015d80659 6/n: rename module: external fun reference
Summary: A module name can occur in an external fun. Rename it too.

Reviewed By: robertoaloi

Differential Revision: D88849514

fbshipit-source-id: e715628a6fbfe419cd56761fe62b43d3b68ecde4
2025-12-17 06:00:04 -08:00
Alan Zimmerman
8997372e1a 5/n: rename module: move original file
Summary:
The final step in renaming a module.  Move the old file, and apply the needed edits to it.

This diff also simplifies the mechanics of the rename, to be more general and in line with other renames, by iterating over usages of the module, rather than ad-hoc checks for things that may be needed.

It is still ad-hoc, in the sense that it only looks for call-like things at the moment (fully qualified calls and types). Other usages will come in later diffs

Reviewed By: robertoaloi

Differential Revision: D87861050

fbshipit-source-id: d393638b310a257f51151fc3caf1b1445c856700
2025-12-17 06:00:04 -08:00
Alan Zimmerman
bef3dd15f2 4/n: rename module: exported type references
Summary: When renaming a module, rename all references to types within it

Reviewed By: robertoaloi

Differential Revision: D87860496

fbshipit-source-id: 2ac6bc52cecaef6802fcf9f181b638f81c556d67
2025-12-17 06:00:04 -08:00
Alan Zimmerman
56e66e2cd5 3/n: rename module: renaming function references
Summary:
Rename references to functions exported from a module when the module is renamed.

This requires fleshing out the infrastructure to deal with deleting files too.

Reviewed By: robertoaloi

Differential Revision: D87860436

fbshipit-source-id: b4d04eb0cfc6448a407fa8c3202ef3e4e088b7e8
2025-12-17 06:00:04 -08:00
Alan Zimmerman
a0e076cb17 2/n: rename module: simple rename works
Summary: First case, just rename the module

Reviewed By: robertoaloi

Differential Revision: D87860412

fbshipit-source-id: 0427f09bd2176778e0280b0f45f9331fa279f778
2025-12-17 06:00:04 -08:00
Alan Zimmerman
8ff602f1c3 1/n rename module, basics
Summary:
This diff stack introduces rename for a module. As this is a complex operation, it will be done in steps.

The first one is to put the basics in place
- check the new name is valid
- Improve rename test fixture to cope with added and renamed files

Reviewed By: robertoaloi

Differential Revision: D87860388

fbshipit-source-id: 1a240a5e0e5e61d5c128bca0423744f001b7b68d
2025-12-17 06:00:04 -08:00
Alan Zimmerman
4a83d0edde BE: extract bound_vars_by_function as a method for sema
Some checks are pending
ELP CI / edb (push) Waiting to run
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, true, linux-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, true, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-15-intel, macos-15-x64, x86_64-apple-darwin, true, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-latest, macos-latest-arm, aarch64-apple-darwin, true, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, windows, 26.2, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, true, win32-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
Deploy Website to GitHub Pages / Deploy Website to GitHub Pages (push) Waiting to run
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, windows, 27.3, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, windows, 28, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
Summary:
The `mutable_variable` diagnostic processed bound variables organised by function.

We intend doing the same for more bound variable diagnostics, so as a precursor extract this a method we can call on `Semantic`

Reviewed By: TD5

Differential Revision: D89270590

fbshipit-source-id: 95a520d45e900c0a5c97fd8163cbaedbc15ca840
2025-12-17 01:32:35 -08:00
Alan Zimmerman
271065da03 BE: for a fold, track the macro definition for ctx.in_macro
Summary:
When folding with visible macros, we keep track of a macro stack, so we can decide how to process diagnostics within the body of a macro expansion.

To date we have simply stored the `HirIdx` of the macro call, so it can be retrieved and examined by anything that needs it.

But the thing we need when processing a macro is in fact what definition was expanded.  This diff adds it to the information stored for easy access.

It will be used in a subsequent diff.

Reviewed By: TD5

Differential Revision: D89269840

fbshipit-source-id: 7ed6212b658de91cdd3e20701edb51161966e8fa
2025-12-17 01:32:35 -08:00
Roberto Aloi
37b7c5e28e Introduce top-level test directory
Reviewed By: michalmuskala

Differential Revision: D89272460

fbshipit-source-id: 91eb93d7df55a1ca64fe2b05e21097daca3b6d33
2025-12-17 01:18:32 -08:00
Roberto Aloi
d4e14636b4 Do not run selected linters on tests by default
Some checks are pending
ELP CI / edb (push) Waiting to run
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, true, linux-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, true, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-15-intel, macos-15-x64, x86_64-apple-darwin, true, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-latest, macos-latest-arm, aarch64-apple-darwin, true, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, windows, 26.2, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, true, win32-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, windows, 27.3, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
Deploy Website to GitHub Pages / Deploy Website to GitHub Pages (push) Waiting to run
Differential Revision: D89302965

fbshipit-source-id: 9b5c1d3c6402a6fea9f3fd5a6d93c2e49acba51e
2025-12-16 15:30:53 -08:00
Open Source Bot
b2ea905e7f Updating hashes
Summary:
GitHub commits:

020284a6e8
261ff05851
acdd3baf0a
b435fe91fa
a75b46bbbc
427330df23
fd2a5c46a7
e95090adb4
c28a092249
fab1e58dfd
848ddcedb2
270d39b2c3
65b11fc090
0f514eb389

Reviewed By: pranavcivi

fbshipit-source-id: 0c3453ad24a874db08fbc66cc4945f70c64d25d0
2025-12-16 11:27:09 -08:00
Facebook Community Bot
858b8e64ab
Re-sync with internal repository (#143)
The internal and external repositories are out of sync. This Pull Request attempts to brings them back in sync by patching the GitHub repository. Please carefully review this patch. You must disable ShipIt for your project in order to merge this pull request. DO NOT IMPORT this pull request. Instead, merge it directly on GitHub using the MERGE BUTTON. Re-enable ShipIt after merging.

fbshipit-source-id: a4e36831ac3939cb9a4881ca077b5f8c0d421104
2025-12-16 10:54:56 -08:00
Alan Zimmerman
271a52c37c honour --include-generated for elp ssr
Some checks are pending
ELP CI / edb (push) Waiting to run
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, true, linux-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, true, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-15-intel, macos-15-x64, x86_64-apple-darwin, true, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-latest, macos-latest-arm, aarch64-apple-darwin, true, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, windows, 26.2, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, true, win32-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, windows, 27.3, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, windows, 28, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
Deploy Website to GitHub Pages / Deploy Website to GitHub Pages (push) Waiting to run
Summary: As title

Reviewed By: michalmuskala, TD5

Differential Revision: D89292307

fbshipit-source-id: cbde669d57276b2aaa2c7365906e3ed86f15aec5
2025-12-16 09:08:25 -08:00
Roberto Aloi
ba4505e981 Do not run application_env linter on test suites
Reviewed By: alanz, TheGeorge, michalmuskala

Differential Revision: D89287769

fbshipit-source-id: 29618250ee6d75ede850603cb60c5e4d77e3ff06
2025-12-16 08:56:01 -08:00
Alan Zimmerman
d2da476882 Add test project for code gen
Summary:
ELP using buck is able to trigger rules that generate code, and load this generated code as part of the project.

This diff brings in a test project with an example of this process, for ensuring our support is sold.

Reviewed By: robertoaloi

Differential Revision: D88953198

fbshipit-source-id: b92cf86b556f5465b2a528895b4e56852630f82e
2025-12-16 02:03:43 -08:00
Victor Lanvin
166770cc92 Remove logic for --include-tests, opt-in tests
Some checks are pending
ELP CI / edb (push) Waiting to run
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, true, linux-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, true, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-15-intel, macos-15-x64, x86_64-apple-darwin, true, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-latest, macos-latest-arm, aarch64-apple-darwin, true, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, windows, 26.2, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, true, win32-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, windows, 27.3, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, windows, 28, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
Deploy Website to GitHub Pages / Deploy Website to GitHub Pages (push) Waiting to run
Summary:
Remove all logic related to `--include-tests` in ELP for eqWAlizer.

Enable eqWAlizer on tests by default.

Reviewed By: michalmuskala

Differential Revision: D89049936

fbshipit-source-id: b3702ddcc8cdb9a3ced65ec6855918ea37b2a072
2025-12-15 03:16:34 -08:00
Roberto Aloi
b525eac0ea Pass range to fixes callback
Some checks failed
ELP CI / edb (push) Has been cancelled
Deploy Website to GitHub Pages / Deploy Website to GitHub Pages (push) Has been cancelled
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, true, linux-x64) (push) Has been cancelled
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, true, linux-arm64) (push) Has been cancelled
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-15-intel, macos-15-x64, x86_64-apple-darwin, true, darwin-x64) (push) Has been cancelled
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-latest, macos-latest-arm, aarch64-apple-darwin, true, darwin-arm64) (push) Has been cancelled
ELP CI / ci (26, 26.2.5.13, windows, 26.2, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, true, win32-x64) (push) Has been cancelled
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Has been cancelled
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Has been cancelled
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Has been cancelled
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Has been cancelled
ELP CI / ci (27, 27.3.4, windows, 27.3, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Has been cancelled
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Has been cancelled
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Has been cancelled
ELP CI / ci (28, 28.0.1, macos, 28, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Has been cancelled
ELP CI / ci (28, 28.0.1, macos, 28, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Has been cancelled
ELP CI / ci (28, 28.0.1, windows, 28, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Has been cancelled
Summary: It is common for the `fixes` callback to act on the original match range. By making the matched range available to the callback, we avoid manually including the range in the linter-specific context.

Reviewed By: alanz

Differential Revision: D89048282

fbshipit-source-id: 01faa9842f04c34efb14a7d803cf5317794ea321
2025-12-12 06:17:31 -08:00
Victor Lanvin
2bd3d57d02 Clean-up old error messages
Summary:
Use new error message generation for callback type errors, add corresponding test

Cleanup old error message generation

Reviewed By: TD5

Differential Revision: D88941623

fbshipit-source-id: 262c2031c7d19ad2528ef63a5d73d146fd84d408
2025-12-12 06:08:41 -08:00
Roberto Aloi
eb5e0abbf8 Install Buck2 in OSS CI workflow
Summary: In preparation for enabling Buck2 tests on GitHub, we install the Buck2 binary.

Reviewed By: michalmuskala

Differential Revision: D89038926

fbshipit-source-id: 7e37c7e9ec143391e10eba8597df2b2aa9d55d0f
2025-12-12 04:22:36 -08:00
Alan Zimmerman
887d287c94 tweak elp parse-elp --severity output
Summary:
Apply the severity filter to the results before printing any information about them.

This prevents printing a module name that has no errors in it, and gives a correct count in the module diagnostic summary

Reviewed By: TD5

Differential Revision: D89043311

fbshipit-source-id: aa0c813314f32ca9c23d753c598974a1eb605390
2025-12-12 03:56:28 -08:00
Roberto Aloi
553c90d631 Bump OSS VS Code extension to 0.47.0
Summary:
Due to some [most likely transient infra issues](2013794095), the latest release was only partially published.

Bump the extension to trigger a new release.

Reviewed By: alanz

Differential Revision: D89024759

fbshipit-source-id: fce412fee451eb72246c121706108af3ac34d414
2025-12-12 03:02:33 -08:00
Alan Zimmerman
a16af7e9e2 Update emacs eglot instructions
Summary: Add a stanza to set the config so we get semantic highlighting of `dynamic()`, and types on hover.

Reviewed By: michalmuskala

Differential Revision: D88957646

fbshipit-source-id: 5be4d69fdcf02999b840081baae076b85f9a9260
2025-12-12 02:19:15 -08:00
Alan Zimmerman
0085fba772 Add --severity argument to elp parse-elp to limit output
Summary: As title.

Reviewed By: TD5

Differential Revision: D88963207

fbshipit-source-id: a8cc5ddfedcfa8063537e586ac086c8a0caac465
2025-12-12 01:38:41 -08:00
Victor Lanvin
babf61214c Bad occurrence typing with maps:foreach -- repro
Some checks failed
ELP CI / edb (push) Has been cancelled
Deploy Website to GitHub Pages / Deploy Website to GitHub Pages (push) Has been cancelled
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, true, linux-x64) (push) Has been cancelled
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, true, linux-arm64) (push) Has been cancelled
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-15-intel, macos-15-x64, x86_64-apple-darwin, true, darwin-x64) (push) Has been cancelled
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-latest, macos-latest-arm, aarch64-apple-darwin, true, darwin-arm64) (push) Has been cancelled
ELP CI / ci (26, 26.2.5.13, windows, 26.2, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, true, win32-x64) (push) Has been cancelled
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Has been cancelled
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Has been cancelled
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Has been cancelled
ELP CI / ci (27, 27.3.4, windows, 27.3, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Has been cancelled
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Has been cancelled
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Has been cancelled
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Has been cancelled
ELP CI / ci (28, 28.0.1, macos, 28, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Has been cancelled
ELP CI / ci (28, 28.0.1, macos, 28, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Has been cancelled
ELP CI / ci (28, 28.0.1, windows, 28, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Has been cancelled
Summary: Occurrence typing doesn't take place in `maps:foreach` (and several other custom functions). Repro.

Reviewed By: michalmuskala

Differential Revision: D88949717

fbshipit-source-id: d6225a708f0c632d914c4ca38b1d789a3b077311
2025-12-11 07:25:42 -08:00
Roberto Aloi
7b0b29ae21 Temporarily ignore hierarchical_config tests
Summary:
The test is supposed to panic (and does so) when run with both Buck2 and rebar3.
Unfortunately, we are currently disabling the Buck2 tests on GitHub, which has the unfortunate consequence of not letting the test fail as expected, effectively breaking CI.

Temporarily ignore the test.

Reviewed By: alanz

Differential Revision: D88950046

fbshipit-source-id: edabc83ce2b533fa95075078feac624343c198ad
2025-12-11 06:36:45 -08:00
Alan Zimmerman
4bfedaee9a BE: validate all test fixtures
Summary:
The previous diff added the ability to validate a test fixture for parse errors, with an override mechanism if they are expected.

This diff enables the validation for all fixture parsing, and fixes all the resulting errors.

Reviewed By: TD5

Differential Revision: D88948727

fbshipit-source-id: c7e584f3e63c5ad095b9602eed56b469a73392d2
2025-12-11 06:21:28 -08:00
Alan Zimmerman
290b931170 BE: validate test fixture
Summary:
A common mistake when writing a declarative test fixture is to have a syntax error in it.

This diff adds `ChangeFixture::validate` to check for these, and panic if found, e.g.

```
thread 'tests::validate_fixture_with_parse_error' panicked at crates/ide_db/src/lib.rs:462:17:
Fixture validation failed: syntax errors found in test fixture

File: /src/test.erl
Errors: [Missing(")", 19..19)]
Content:
-module(test).
foo( -> ok.

Parse Tree:
SourceFile {
    syntax: SOURCE_FILE@0..27
      MODULE_ATTRIBUTE@0..14
        ANON_DASH@0..1 "-"
        ANON_MODULE@1..7 "module"
        ANON_LPAREN@7..8 "("
        ATOM@8..12
          ATOM@8..12 "test"
        ANON_RPAREN@12..13 ")"
        ANON_DOT@13..14 "."
      WHITESPACE@14..15 "\n"
      FUN_DECL@15..26
        FUNCTION_CLAUSE@15..25
          ATOM@15..18
            ATOM@15..18 "foo"
          EXPR_ARGS@18..19
            ANON_LPAREN@18..19 "("
          WHITESPACE@19..20 " "
          CLAUSE_BODY@20..25
            ANON_DASH_GT@20..22 "->"
            WHITESPACE@22..23 " "
            ATOM@23..25
              ATOM@23..25 "ok"
        ANON_DOT@25..26 "."
      WHITESPACE@26..27 "\n"
    ,
}
 ---
If this is expected, add `//- expect_parse_errors` to the start of the fixture
```

A subsequent diff will apply it to the code base

Reviewed By: TD5

Differential Revision: D88948352

fbshipit-source-id: 532275075bb338191594966bb3292986442cead6
2025-12-11 06:21:28 -08:00
Alan Zimmerman
0930edd693 BE: report malformed test fixture
Summary:
A declarative test fixture can have multiple files in it, in which case each has a metadata line giving the file name. But we have not been checking if it is omitted from the first file, leading to confusing behaviour when writing tests.

This diff updates test fixture parsing to panic in that case, with an appropriate message.

Reviewed By: TD5

Differential Revision: D88947477

fbshipit-source-id: 7ef7151fafb44d087b1da25c55454f20899ca67f
2025-12-11 05:22:57 -08:00
Alan Zimmerman
2d693f3578 BE: restore expected test result to generate a panic
Some checks are pending
ELP CI / edb (push) Waiting to run
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, true, linux-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, true, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-15-intel, macos-15-x64, x86_64-apple-darwin, true, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-latest, macos-latest-arm, aarch64-apple-darwin, true, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, windows, 26.2, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, true, win32-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, windows, 27.3, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, windows, 28, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
Deploy Website to GitHub Pages / Deploy Website to GitHub Pages (push) Waiting to run
Summary:
As title.

Note that this is still failing on GH.

Reviewed By: robertoaloi

Differential Revision: D88751685

fbshipit-source-id: 4050a9f2094ac7c46395f0f05a7f77dfad9a583b
2025-12-10 08:59:12 -08:00
Victor Lanvin
0aef1f4005 Update fixtures for OTP 26/28
Summary: As title.

Reviewed By: robertoaloi, TD5

Differential Revision: D88853775

fbshipit-source-id: 8954794c428f6f1139ebe4f393ed80d1836a2784
2025-12-10 08:08:55 -08:00
Roberto Aloi
29c017c565 Polish extension README, improve package.json for discoverability
Some checks are pending
ELP CI / edb (push) Waiting to run
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, true, linux-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, linux, 26.2, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, true, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-15-intel, macos-15-x64, x86_64-apple-darwin, true, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, macos, 26.2, macos-latest, macos-latest-arm, aarch64-apple-darwin, true, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (26, 26.2.5.13, windows, 26.2, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, true, win32-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, linux, 27.3, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, macos, 27.3, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (27, 27.3.4, windows, 27.3, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04, ubuntu-22.04-x64, x86_64-unknown-linux-gnu, false, linux-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, linux, 28, ubuntu-22.04-arm, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, false, linux-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-15-intel, macos-15-x64, x86_64-apple-darwin, false, darwin-x64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, macos, 28, macos-latest, macos-latest-arm, aarch64-apple-darwin, false, darwin-arm64) (push) Blocked by required conditions
ELP CI / ci (28, 28.0.1, windows, 28, windows-2022, windows-2022-x64, x86_64-pc-windows-msvc, false, win32-x64) (push) Blocked by required conditions
Deploy Website to GitHub Pages / Deploy Website to GitHub Pages (push) Waiting to run
Summary:
* Polish extension README (which is rendered [here](https://marketplace.visualstudio.com/items?itemName=erlang-language-platform.erlang-language-platform)).
* Set `displayName` (see [reference](https://code.visualstudio.com/api/references/extension-manifest)) to improve discoverability and hopefully ranking
* Update description
* Add `erlang` keyword to improve discoverability/ranking

Reviewed By: alanz

Differential Revision: D88847040

fbshipit-source-id: e2185e790f0711dd383d6cc51ad56716eeebb61e
2025-12-10 07:06:30 -08:00
Roberto Aloi
ed8820b3b2 Update Intel runners to mac-15-interl
Summary:
The Mac OS 13 Ventura runner images are [deprecated](https://github.com/actions/runner-images/issues/13046).
This results in a [CI error](2009932825).

This applies the recommended remediation.

Reviewed By: alanz

Differential Revision: D88848551

fbshipit-source-id: 201fa45e4834b082e84a150343042ae5a6104189
2025-12-10 07:06:17 -08:00
Roberto Aloi
a239a322cf Manually bump eqwalizer to the latest available version (#139)
Summary: Pull Request resolved: https://github.com/whatsapp/erlang-language-platform/pull/139

Reviewed By: alanz

Differential Revision: D88849209

Pulled By: robertoaloi

fbshipit-source-id: f3433841b248804dde41fca866680f8c6eaef338
2025-12-10 07:03:12 -08:00
Roberto Aloi
a4ded31602 Bump OSS VS Code extension to 0.46.0
Summary:
Several issues were affecting GitHub CI, preventing the extension to be correctly published to the marketplace.
A new release will therefore be necessary, hence the new bump.

Reviewed By: alanz

Differential Revision: D88848737

fbshipit-source-id: f4c31f1bbc5bb3aeec7359c8c8a5d1faeb0c2f9e
2025-12-10 07:00:19 -08:00
601 changed files with 3994 additions and 7247 deletions

View file

@ -1,8 +1,8 @@
[alias]
xtask = "run --package xtask --"
# @fb-only
# @fb-only
# @fb-only: [build]
# @fb-only: target-dir = "../../../buck-out/elp"
[profile.release]
codegen-units = 1

View file

@ -30,7 +30,7 @@ jobs:
strategy:
fail-fast: false
matrix:
platform-arch: [ubuntu-22.04-x64, ubuntu-22.04-arm, macos-13-x64, macos-latest-arm, windows-2022-x64]
platform-arch: [ubuntu-22.04-x64, ubuntu-22.04-arm, macos-15-x64, macos-latest-arm, windows-2022-x64]
otp-version: [26.2, 27.3, 28.0]
include:
- otp-version: 26.2
@ -55,8 +55,8 @@ jobs:
os: linux
target: aarch64-unknown-linux-gnu
vscode-target: linux-arm64
- platform-arch: macos-13-x64
platform: macos-13
- platform-arch: macos-15-x64
platform: macos-15-intel
os: macos
target: x86_64-apple-darwin
vscode-target: darwin-x64
@ -97,6 +97,8 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y crossbuild-essential-arm64
- name: Install Buck2
uses: dtolnay/install-buck2@latest
- id: setup-erlang
uses: ./.github/actions/setup-erlang
with:
@ -135,7 +137,7 @@ jobs:
- name: Test elp
# Do not run the tests in case of cross-compilation or on Windows
if: matrix.platform-arch != 'macos-latest-arm' && matrix.os != 'windows'
run: 'cargo test --no-default-features --workspace --target ${{ matrix.target }}'
run: 'cargo test --workspace --target ${{ matrix.target }}'
- name: Build elp (No Windows)
if: matrix.os != 'windows'
run: 'cargo build --release --target ${{ matrix.target }} --config target.aarch64-unknown-linux-gnu.linker=\"aarch64-linux-gnu-gcc\"'

12
.vscode/tasks.json vendored
View file

@ -4,7 +4,7 @@
{
"label": "ELP: build (debug)",
"type": "shell",
// @fb-only
// @fb-only: "command": "./meta/cargo.sh build",
"command": "cargo build", // @oss-only
"group": {
"kind": "build",
@ -19,7 +19,7 @@
{
"label": "ELP: build (release)",
"type": "shell",
// @fb-only
// @fb-only: "command": "./meta/cargo.sh build --release",
"command": "cargo build --release", // @oss-only
"group": {
"kind": "build",
@ -34,7 +34,7 @@
{
"label": "ELP: build (release-thin)",
"type": "shell",
// @fb-only
// @fb-only: "command": "./meta/cargo.sh build --profile release-thin --bins",
"command": "cargo build --profile release-thin --bins", // @oss-only
"group": {
"kind": "build",
@ -49,7 +49,7 @@
{
"label": "ELP: run clippy on workspace",
"type": "shell",
// @fb-only
// @fb-only: "command": "./meta/clippy.sh --workspace --tests",
"command": "cargo clippy --workspace --tests", // @oss-only
"group": {
"kind": "build",
@ -64,7 +64,7 @@
{
"label": "ELP: run clippy on workspace, apply fixes",
"type": "shell",
// @fb-only
// @fb-only: "command": "./meta/clippy.sh --workspace --tests --fix",
"command": "cargo clippy --workspace --tests --fix", // @oss-only
"group": {
"kind": "build",
@ -79,7 +79,7 @@
{
"label": "ELP: run tests on workspace",
"type": "shell",
// @fb-only
// @fb-only: "command": "./meta/cargo.sh test --workspace",
"command": "cargo test --workspace", // @oss-only
"group": {
"kind": "build",

View file

@ -87,6 +87,7 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static {
let (fixture, change) = ChangeFixture::parse(fixture_str);
let mut db = Self::default();
change.apply(&mut db, &|path| fixture.resolve_file_id(path));
fixture.validate(&db);
(db, fixture)
}
}
@ -101,6 +102,7 @@ pub struct ChangeFixture {
pub diagnostics_enabled: DiagnosticsEnabled,
pub tags: FxHashMap<FileId, Vec<(TextRange, Option<String>)>>,
pub annotations: FxHashMap<FileId, Vec<(TextRange, String)>>,
pub expect_parse_errors: bool,
}
struct Builder {
@ -172,6 +174,7 @@ impl ChangeFixture {
let FixtureWithProjectMeta {
fixture,
mut diagnostics_enabled,
expect_parse_errors,
} = fixture_with_meta.clone();
let builder = Builder::new(diagnostics_enabled.clone());
@ -344,6 +347,7 @@ impl ChangeFixture {
diagnostics_enabled,
tags,
annotations,
expect_parse_errors,
},
change,
project,
@ -405,6 +409,64 @@ impl ChangeFixture {
.get(&VfsPath::from(path.clone()))
.cloned()
}
/// Validate all files in the fixture for syntax errors.
/// Panics with context if any syntax errors are found.
/// Skips validation if `expect_parse_errors` is set to true.
#[track_caller]
pub fn validate<DB: SourceDatabaseExt>(&self, db: &DB) {
if self.expect_parse_errors {
return;
}
let mut errors_found = Vec::new();
for file_id in &self.files {
let parse = db.parse(*file_id);
let errors = parse.errors();
if !errors.is_empty() {
let path = self
.files_by_path
.iter()
.find_map(|(vfs_path, id)| {
if id == file_id {
Some(
vfs_path
.as_path()
.map(|p| p.to_string())
.unwrap_or_else(|| format!("{:?}", vfs_path)),
)
} else {
None
}
})
.unwrap_or_else(|| format!("FileId({:?})", file_id));
let file_text = SourceDatabaseExt::file_text(db, *file_id);
let tree = parse.tree();
errors_found.push((path, file_text.to_string(), errors.to_vec(), tree));
}
}
if !errors_found.is_empty() {
let mut message =
String::from("Fixture validation failed: syntax errors found in test fixture\n\n");
for (path, text, errors, tree) in errors_found {
message.push_str(&format!("File: {}\n", path));
message.push_str(&format!("Errors: {:?}\n", errors));
message.push_str(&format!("Content:\n{}\n", text));
message.push_str(&format!("Parse Tree:\n{:#?}\n", tree));
message.push_str("---\n");
}
message.push_str(
"If this is expected, add `//- expect_parse_errors` to the start of the fixture\n",
);
panic!("{}", message);
}
}
}
fn inc_file_id(file_id: &mut FileId) {

View file

@ -32,7 +32,7 @@ mod module_index;
// Public API
pub mod fixture;
// @fb-only
// @fb-only: mod meta_only;
pub mod test_utils;
pub use change::Change;
pub use elp_project_model::AppType;
@ -476,7 +476,7 @@ static ref IGNORED_SOURCES: Vec<Regex> = {
let regexes: Vec<Vec<Regex>> = vec![
vec![Regex::new(r"^.*_SUITE_data/.+$").unwrap()],
//ignore sources goes here
// @fb-only
// @fb-only: meta_only::ignored_sources_regexes()
];
regexes.into_iter().flatten().collect::<Vec<Regex>>()
};

View file

@ -8,8 +8,8 @@
* above-listed licenses.
*/
// @fb-only
// @fb-only
// @fb-only: /// Types as defined in https://www.internalfb.com/intern/wiki/Linting/adding-linters/#flow-type
// @fb-only: /// and https://www.internalfb.com/code/fbsource/[1238f73dac0efd4009443fee6a345a680dc9401b]/whatsapp/server/erl/tools/lint/arcanist.py?lines=17
use std::path::Path;
use serde::Serialize;

View file

@ -72,6 +72,17 @@ pub struct ParseAllElp {
/// Report system memory usage and other statistics
#[bpaf(long("report-system-stats"))]
pub report_system_stats: bool,
/// Minimum severity level to report. Valid values: error, warning, weak_warning, information
#[bpaf(
argument("SEVERITY"),
complete(severity_completer),
fallback(None),
guard(
severity_guard,
"Please use error, warning, weak_warning, or information"
)
)]
pub severity: Option<String>,
}
#[derive(Clone, Debug, Bpaf)]
@ -144,8 +155,6 @@ pub struct EqwalizeAll {
/// Also eqwalize opted-in generated modules from project (deprecated)
#[bpaf(hide)]
pub include_generated: bool,
/// Also eqwalize test modules from project
pub include_tests: bool,
/// Exit with a non-zero status code if any errors are found
pub bail_on_error: bool,
/// Print statistics when done
@ -162,8 +171,6 @@ pub struct EqwalizeTarget {
/// Also eqwalize opted-in generated modules from application (deprecated)
#[bpaf(hide)]
pub include_generated: bool,
/// Also eqwalize test modules from project
pub include_tests: bool,
/// Exit with a non-zero status code if any errors are found
pub bail_on_error: bool,
/// target, like //erl/chatd/...
@ -182,8 +189,6 @@ pub struct EqwalizeApp {
/// Also eqwalize opted-in generated modules from project (deprecated)
#[bpaf(hide)]
pub include_generated: bool,
/// Also eqwalize test modules from project
pub include_tests: bool,
/// Run with rebar
pub rebar: bool,
/// Exit with a non-zero status code if any errors are found
@ -206,8 +211,6 @@ pub struct EqwalizeStats {
/// Also eqwalize opted-in generated modules from project (deprecated)
#[bpaf(hide)]
pub include_generated: bool,
/// Also eqwalize test modules from project
pub include_tests: bool,
/// If specified, use the provided CLI severity mapping instead of the default one
pub use_cli_severity: bool,
}
@ -783,6 +786,25 @@ fn format_guard(format: &Option<String>) -> bool {
}
}
fn severity_completer(_: &Option<String>) -> Vec<(String, Option<String>)> {
vec![
("error".to_string(), None),
("warning".to_string(), None),
("weak_warning".to_string(), None),
("information".to_string(), None),
]
}
fn severity_guard(severity: &Option<String>) -> bool {
match severity {
None => true,
Some(s) if s == "error" || s == "warning" || s == "weak_warning" || s == "information" => {
true
}
_ => false,
}
}
fn macros_completer(_: &Option<String>) -> Vec<(String, Option<String>)> {
vec![
("expand".to_string(), None),
@ -889,7 +911,7 @@ impl Lint {
/// To prevent flaky test results we allow disabling streaming when applying fixes
pub fn skip_stream_print(&self) -> bool {
self.apply_fix && self.no_stream
self.apply_fix || self.no_stream
}
}

View file

@ -57,6 +57,35 @@ use crate::args::ParseAllElp;
use crate::reporting;
use crate::reporting::print_memory_usage;
fn parse_severity(severity: &str) -> Option<diagnostics::Severity> {
match severity {
"error" => Some(diagnostics::Severity::Error),
"warning" => Some(diagnostics::Severity::Warning),
"weak_warning" => Some(diagnostics::Severity::WeakWarning),
"information" => Some(diagnostics::Severity::Information),
_ => None,
}
}
fn severity_rank(severity: diagnostics::Severity) -> u8 {
match severity {
diagnostics::Severity::Error => 1,
diagnostics::Severity::Warning => 2,
diagnostics::Severity::WeakWarning => 3,
diagnostics::Severity::Information => 4,
}
}
fn meets_severity_threshold(
diag_severity: diagnostics::Severity,
min_severity: Option<diagnostics::Severity>,
) -> bool {
match min_severity {
None => true,
Some(min) => severity_rank(diag_severity) <= severity_rank(min),
}
}
#[derive(Debug)]
struct ParseResult {
name: String,
@ -147,6 +176,19 @@ pub fn parse_all(
let memory_end = MemoryUsage::now();
let memory_used = memory_end - memory_start;
let min_severity = args
.severity
.as_ref()
.and_then(|s| parse_severity(s.as_str()));
res.retain(|parse_result| {
parse_result
.diagnostics
.diagnostics_for(parse_result.file_id)
.iter()
.any(|diag| meets_severity_threshold(diag.severity, min_severity))
});
if res.is_empty() {
if args.is_format_normal() {
writeln!(cli, "No errors reported")?;
@ -165,6 +207,7 @@ pub fn parse_all(
for diags in res {
let mut combined: Vec<diagnostics::Diagnostic> =
diags.diagnostics.diagnostics_for(diags.file_id);
combined.retain(|diag| meets_severity_threshold(diag.severity, min_severity));
if args.is_format_normal() {
writeln!(cli, " {}: {}", diags.name, combined.len())?;
}

View file

@ -186,10 +186,7 @@ pub fn do_eqwalize_all(
.par_bridge()
.progress_with(pb.clone())
.map_with(analysis.clone(), |analysis, (name, _source, file_id)| {
if analysis
.should_eqwalize(file_id, args.include_tests)
.unwrap()
&& !otp_file_to_ignore(analysis, file_id)
if analysis.should_eqwalize(file_id).unwrap() && !otp_file_to_ignore(analysis, file_id)
{
if args.stats {
add_stat(name.to_string());
@ -269,9 +266,7 @@ pub fn do_eqwalize_app(
.iter_own()
.filter_map(|(_name, _source, file_id)| {
if analysis.file_app_name(file_id).ok()? == Some(AppName(args.app.clone()))
&& analysis
.should_eqwalize(file_id, args.include_tests)
.unwrap()
&& analysis.should_eqwalize(file_id).unwrap()
&& !otp_file_to_ignore(analysis, file_id)
{
Some(file_id)
@ -339,9 +334,7 @@ pub fn eqwalize_target(
let vfs_path = VfsPath::from(src.clone());
if let Some((file_id, _)) = loaded.vfs.file_id(&vfs_path) {
at_least_one_found = true;
if analysis
.should_eqwalize(file_id, args.include_tests)
.unwrap()
if analysis.should_eqwalize(file_id).unwrap()
&& !otp_file_to_ignore(analysis, file_id)
{
file_ids.push(file_id);
@ -408,9 +401,7 @@ pub fn eqwalize_stats(
.par_bridge()
.progress_with(pb.clone())
.map_with(analysis.clone(), |analysis, (name, _source, file_id)| {
if analysis
.should_eqwalize(file_id, args.include_tests)
.expect("cancelled")
if analysis.should_eqwalize(file_id).expect("cancelled")
&& !otp_file_to_ignore(analysis, file_id)
{
analysis

View file

@ -84,7 +84,7 @@ const REC_ARITY: u32 = 99;
const HEADER_ARITY: u32 = 100;
const FACTS_FILE: &str = "facts.json";
// @fb-only
// @fb-only: mod meta_only;
#[derive(Serialize, Debug, Eq, Hash, PartialEq, Clone)]
struct GleanFileId(u32);
@ -994,7 +994,7 @@ impl GleanIndexer {
.filter(|text| !text.is_empty())
});
// @fb-only
// @fb-only: let exdoc_link = elp_ide::meta_only::exdoc_links::module_exdoc_link(&module, &sema);
let exdoc_link: Option<String> = None; // @oss-only
ModuleFact::new(
@ -1532,7 +1532,7 @@ impl GleanIndexer {
}) => {
let def = macro_def.as_ref()?;
let mut resolved = Self::resolve_macro_v2(sema, def, source_file, ctx)?;
// @fb-only
// @fb-only: meta_only::resolve_macro_expansion(sema, *expansion, ctx, &mut resolved);
Some(resolved)
}
hir::AnyExpr::Pat(Pat::MacroCall { macro_def, .. })
@ -1560,7 +1560,7 @@ impl GleanIndexer {
vars: FxHashMap<&Location, &String>,
) -> Vec<VarDecl> {
let mut result = vec![];
if !db.is_eqwalizer_enabled(file_id, false) {
if !db.is_eqwalizer_enabled(file_id) {
return result;
}
let module_diagnostics = db.eqwalizer_diagnostics_by_project(project_id, vec![file_id]);
@ -1875,9 +1875,9 @@ impl GleanIndexer {
let source_file = sema.parse(file_id);
let range = Self::find_range(sema, ctx, &source_file, &expr_source)?;
// @fb-only
// @fb-only
// @fb-only
// @fb-only: use elp_ide::meta_only::wam_links;
// @fb-only: let wam_ctx = wam_links::WamEventCtx::new(sema.db.upcast());
// @fb-only: let wam_url = wam_ctx.build_wam_link(name).map(|link| link.url());
let wam_url = None; // @oss-only
Some(XRef {
@ -2335,10 +2335,10 @@ mod tests {
fn xref_types_test() {
let spec = r#"
//- /glean/app_glean/src/glean_module81.erl
-type small() :: #{non_neg_integer() | infinity}.
-type small() :: {non_neg_integer() | infinity}.
//- /glean/app_glean/src/glean_module8.erl
-type huuuge() :: #{non_neg_integer() | infinity}.
-type huuuge() :: {non_neg_integer() | infinity}.
-spec baz(
A :: huuuge(),
%% ^^^^^^ glean_module8/huuuge/0
@ -2393,10 +2393,10 @@ mod tests {
fn xref_types_v2_test() {
let spec = r#"
//- /glean/app_glean/src/glean_module81.erl
-type small() :: #{non_neg_integer() | infinity}.
-type small() :: {non_neg_integer() | infinity}.
//- /glean/app_glean/src/glean_module8.erl
-type huuuge() :: #{non_neg_integer() | infinity}.
-type huuuge() :: {non_neg_integer() | infinity}.
-spec baz(
A :: huuuge(),
%% ^^^^^^ glean_module8.erl/type/huuuge/0

View file

@ -295,7 +295,7 @@ pub fn do_codemod(
let res;
let streamed_err_in_diag;
let mut any_diagnostics_printed = false;
let initial_diags = {
let mut initial_diags = {
// We put this in its own block so that analysis is
// freed before we apply lints. To apply lints
// recursively, we need to update the underlying
@ -394,30 +394,54 @@ pub fn do_codemod(
let mut err_in_diag = streamed_err_in_diag;
// At this point, the analysis variable from above is dropped
// Print "No diagnostics reported" if no diagnostics were found after filtering
if !any_diagnostics_printed {
if args.is_format_normal() {
writeln!(cli, "No diagnostics reported")?;
// When streaming is disabled (--no-stream) and we're not applying fixes,
// we need to print diagnostics now since they weren't printed during streaming
if args.no_stream && !args.apply_fix && !initial_diags.is_empty() {
let analysis = loaded.analysis();
let mut module_count = 0;
initial_diags.sort_by(|(a, _, _), (b, _, _)| a.cmp(b));
for result in &initial_diags {
let printed = print_diagnostic_result(
cli,
&analysis,
diagnostics_config,
args,
loaded,
&args.module,
&mut err_in_diag,
&mut module_count,
result,
)?;
any_diagnostics_printed = any_diagnostics_printed || printed;
}
} else {
if args.apply_fix && diagnostics_config.enabled.all_enabled() {
}
// Handle apply_fix case separately since it needs to filter diagnostics anyway
if args.apply_fix {
if diagnostics_config.enabled.all_enabled() {
bail!(
"We cannot apply fixes if all diagnostics enabled. Perhaps provide --diagnostic-filter"
);
}
if args.apply_fix && !diagnostics_config.enabled.all_enabled() {
let mut initial_diags = {
let analysis = loaded.analysis();
filter_diagnostics(
&analysis,
&args.module,
Some(&diagnostics_config.enabled),
&initial_diags,
&FxHashSet::default(),
)?
};
let mut filtered_diags = {
let analysis = loaded.analysis();
filter_diagnostics(
&analysis,
&args.module,
Some(&diagnostics_config.enabled),
&initial_diags,
&FxHashSet::default(),
)?
};
if filtered_diags.is_empty() {
if args.is_format_normal() {
writeln!(cli, "No diagnostics reported")?;
}
} else {
if args.skip_stream_print() {
initial_diags.sort_by(|(a, _, _), (b, _, _)| a.cmp(b));
filtered_diags.sort_by(|(a, _, _), (b, _, _)| a.cmp(b));
let module_count: &mut i32 = &mut 0;
let has_diagnostics: &mut bool = &mut false;
if args.is_format_json() {
@ -428,7 +452,7 @@ pub fn do_codemod(
&mut err_in_diag,
module_count,
has_diagnostics,
&initial_diags,
&filtered_diags,
)?;
} else {
{
@ -442,7 +466,7 @@ pub fn do_codemod(
&mut err_in_diag,
module_count,
has_diagnostics,
&initial_diags,
&filtered_diags,
)?;
// Analysis is dropped here
}
@ -456,7 +480,7 @@ pub fn do_codemod(
&mut loaded.vfs,
args,
&mut changed_files,
initial_diags,
filtered_diags,
);
// We handle the fix application result here, so
// the overall status of whether error-severity
@ -468,8 +492,19 @@ pub fn do_codemod(
writeln!(cli, "Apply fix failed: {err:#}").ok();
}
};
if err_in_diag {
bail!("Errors found")
}
}
if err_in_diag {
} else {
// Non-apply-fix case: rely on any_diagnostics_printed which is set
// correctly based on filtered diagnostics during streaming/batch printing
if !any_diagnostics_printed {
if args.is_format_normal() {
writeln!(cli, "No diagnostics reported")?;
}
} else if err_in_diag {
bail!("Errors found")
}
}

View file

@ -40,7 +40,7 @@ mod erlang_service_cli;
mod explain_cli;
mod glean;
mod lint_cli;
// @fb-only
// @fb-only: mod meta_only;
mod reporting;
mod shell;
mod ssr_cli;
@ -110,7 +110,7 @@ fn setup_cli_telemetry(args: &Args) {
}
_ => {
// Initialize CLI telemetry, if used
// @fb-only
// @fb-only: meta_only::initialize_telemetry();
}
}
}
@ -288,7 +288,7 @@ mod tests {
let (_stdout, stderr, code) = elp(args_vec![
"parse-all",
"--project",
"../../test_projects/standard",
"../../test/test_projects/standard",
"--to",
tmp.path(),
]);
@ -306,7 +306,7 @@ mod tests {
fn parse_all_complete(project: &str) -> Result<i32> {
// Just check the command returns.
let project_path = format!("../../test_projects/{project}");
let project_path = format!("../../test/test_projects/{project}");
let tmp = Builder::new().prefix("elp_parse_all_").tempdir().unwrap();
let (_stdout, _stderr, code) = elp(args_vec![
"parse-all",
@ -443,33 +443,34 @@ mod tests {
})
.unwrap();
let exp_path = expect_file!(format!(
"../resources/test/{}/{}/{}.pretty",
project,
app,
module.as_str(),
));
let (stdout, _) = cli.to_strings();
let otp_version = OTP_VERSION.as_ref().expect("MISSING OTP VERSION");
let otp_version_regex =
regex::bytes::Regex::new(&format!("{}OTPVersionDependent", "@"))
.unwrap();
regex::bytes::Regex::new(&format!("{}OTP([0-9]+)Only", "@")).unwrap();
let contents = analysis.file_text(file_id).unwrap();
let otp_version_dependent = otp_version_regex
.is_match(&contents.as_bytes()[0..(2001.min(contents.len()))]);
let exp_path = {
if otp_version_dependent {
expect_file!(format!(
"../resources/test/{}/{}/{}-OTP-{}.pretty",
project,
app,
module.as_str(),
otp_version,
))
} else {
expect_file!(format!(
"../resources/test/{}/{}/{}.pretty",
project,
app,
module.as_str(),
))
let otp_version_capture = otp_version_regex
.captures(&contents.as_bytes()[0..(2001.min(contents.len()))]);
if let Some((_, [otp_version_only])) =
otp_version_capture.map(|cap| cap.extract())
{
if otp_version_only == otp_version.as_bytes() {
assert_normalised_file(
exp_path,
&stdout,
project_path.into(),
false,
);
}
};
let (stdout, _) = cli.to_strings();
assert_normalised_file(exp_path, &stdout, project_path.into(), false);
} else {
assert_normalised_file(exp_path, &stdout, project_path.into(), false);
}
}
}
EqwalizerDiagnostics::NoAst { module } => {
@ -604,10 +605,7 @@ mod tests {
fn eqwalize_target_diagnostics_match_snapshot_pretty() {
if cfg!(feature = "buck") {
simple_snapshot(
args_vec![
"eqwalize-target",
"//whatsapp/elp/test_projects/standard:app_a",
],
args_vec!["eqwalize-target", "//standard:app_a",],
"standard",
expect_file!("../resources/test/standard/eqwalize_target_diagnostics.pretty"),
true,
@ -671,6 +669,24 @@ mod tests {
);
}
#[test_case(false ; "rebar")]
#[test_case(true ; "buck")]
fn parse_all_diagnostics_severity(buck: bool) {
simple_snapshot_expect_error(
args_vec![
"parse-elp",
"--module",
"diagnostics",
"--severity",
"error"
],
"diagnostics",
expect_file!("../resources/test/diagnostics/parse_all_diagnostics_error.stdout"),
buck,
None,
);
}
#[test_case(false ; "rebar")]
#[test_case(true ; "buck")]
fn parse_elp_file_attribute(buck: bool) {
@ -954,7 +970,9 @@ mod tests {
assert!(tmp_file.clone().exists());
let content = fs::read_to_string(tmp_file).unwrap();
let mut buck_config = BuckConfig::default();
buck_config.buck_root = Some(AbsPathBuf::assert_utf8(current_dir().unwrap()));
buck_config.buck_root = Some(AbsPathBuf::assert_utf8(
current_dir().unwrap().join(path_str.clone()),
));
let prelude_cell = get_prelude_cell(&buck_config).expect("could not get prelude");
let prelude_cell = prelude_cell.strip_prefix("/").unwrap();
let content = content.replace(prelude_cell, "/[prelude]/");
@ -966,38 +984,13 @@ mod tests {
Some(AbsPathBuf::assert(Utf8PathBuf::from_path_buf(abs).unwrap()));
let content = normalise_prelude_path(content, buck_config);
let content = sort_json(&content);
expect![[r#"
{
"apps": [
{
"name": "test_exec",
"dir": "/[prelude]//erlang/common_test/test_exec/src",
"src_dirs": [
""
],
"extra_src_dirs": [],
"include_dirs": [],
"macros": {}
},
{
"name": "diagnostics_app_a",
"dir": "app_a",
"src_dirs": [
"src"
],
"extra_src_dirs": [],
"include_dirs": [
"include"
],
"macros": {
"COMMON_TEST": "true",
"TEST": "true"
}
},
{
"name": "app_a_SUITE",
"dir": "app_a/test",
"src_dirs": [],
"extra_src_dirs": [
""
],
@ -1005,61 +998,88 @@ mod tests {
"macros": {
"COMMON_TEST": "true",
"TEST": "true"
}
},
"name": "app_a_SUITE",
"src_dirs": []
},
{
"name": "common",
"dir": "/[prelude]//erlang/common_test/common",
"dir": "/[prelude]//erlang/common_test/test_exec/src",
"extra_src_dirs": [],
"include_dirs": [],
"macros": {},
"name": "test_exec",
"src_dirs": [
"src"
],
""
]
},
{
"dir": "/[prelude]//erlang/common_test/common",
"extra_src_dirs": [],
"include_dirs": [
"include"
],
"macros": {}
"macros": {},
"name": "common",
"src_dirs": [
"src"
]
},
{
"name": "cth_hooks",
"dir": "/[prelude]//erlang/common_test/cth_hooks/src",
"src_dirs": [
""
],
"extra_src_dirs": [],
"include_dirs": [
""
],
"macros": {}
"macros": {},
"name": "cth_hooks",
"src_dirs": [
""
]
},
{
"name": "buck2_shell_utils",
"dir": "/[prelude]//erlang/shell/src",
"src_dirs": [
""
],
"extra_src_dirs": [],
"include_dirs": [],
"macros": {}
"macros": {},
"name": "buck2_shell_utils",
"src_dirs": [
""
]
},
{
"dir": "app_a",
"extra_src_dirs": [],
"include_dirs": [
"include"
],
"macros": {
"COMMON_TEST": "true",
"TEST": "true"
},
"name": "diagnostics_app_a",
"src_dirs": [
"src"
]
},
{
"name": "test_binary",
"dir": "/[prelude]//erlang/common_test/test_binary/src",
"src_dirs": [
""
],
"extra_src_dirs": [],
"include_dirs": [],
"macros": {}
"macros": {},
"name": "test_binary",
"src_dirs": [
""
]
},
{
"name": "test_cli_lib",
"dir": "/[prelude]//erlang/common_test/test_cli_lib/src",
"src_dirs": [
""
],
"extra_src_dirs": [],
"include_dirs": [],
"macros": {}
"macros": {},
"name": "test_cli_lib",
"src_dirs": [
""
]
}
],
"deps": []
@ -1074,6 +1094,12 @@ mod tests {
content.replace(prelude_cell, "/[prelude]/")
}
fn sort_json(content: &str) -> String {
let mut json: serde_json::Value = serde_json::from_str(content).unwrap();
json.sort_all_objects();
serde_json::to_string_pretty(&json).unwrap()
}
#[test]
#[ignore]
fn build_info_json_buck_bxl_generated() {
@ -1087,7 +1113,7 @@ mod tests {
"--to",
tmp_file.clone(),
"--project",
path_str
path_str.clone()
];
let (stdout, stderr, code) = elp(args);
assert_eq!(
@ -1102,7 +1128,9 @@ mod tests {
assert!(tmp_file.clone().exists());
let content = fs::read_to_string(tmp_file).unwrap();
let mut buck_config = BuckConfig::default();
buck_config.buck_root = Some(AbsPathBuf::assert_utf8(current_dir().unwrap()));
buck_config.buck_root = Some(AbsPathBuf::assert_utf8(
current_dir().unwrap().join(path_str.clone()),
));
let prelude_cell = get_prelude_cell(&buck_config).expect("could not get prelude");
let prelude_cell = prelude_cell.strip_prefix("/").unwrap();
let content = content.replace(prelude_cell, "/[prelude]/");
@ -1426,7 +1454,7 @@ mod tests {
"lint",
"--experimental",
"--config-file",
"../../test_projects/linter/does_not_exist.toml"
"../../test/test_projects/linter/does_not_exist.toml"
],
"linter",
expect_file!("../resources/test/linter/parse_elp_lint_custom_config_invalid_output.stdout"),
@ -1438,7 +1466,7 @@ mod tests {
&[],
false,
Some(expect![[r#"
unable to read "../../test_projects/linter/does_not_exist.toml": No such file or directory (os error 2)
unable to read "../../test/test_projects/linter/does_not_exist.toml": No such file or directory (os error 2)
"#]]),
)
.expect("bad test");
@ -1454,7 +1482,7 @@ mod tests {
"lint",
"--experimental",
"--config-file",
"../../test_projects/linter/elp_lint_test1.toml"
"../../test/test_projects/linter/elp_lint_test1.toml"
],
"linter",
expect_file!("../resources/test/linter/parse_elp_lint_custom_config_output.stdout"),
@ -1480,7 +1508,7 @@ mod tests {
"lint",
"--experimental",
"--config-file",
"../../test_projects/linter/elp_lint_adhoc.toml",
"../../test/test_projects/linter/elp_lint_adhoc.toml",
"--module",
"app_b",
"--apply-fix",
@ -1511,7 +1539,7 @@ mod tests {
"--diagnostic-ignore",
"W0011",
"--config-file",
"../../test_projects/linter/elp_lint_test_ignore.toml"
"../../test/test_projects/linter/elp_lint_test_ignore.toml"
],
"linter",
expect_file!("../resources/test/linter/parse_elp_lint_ignore.stdout"),
@ -1555,7 +1583,7 @@ mod tests {
&[],
false,
Some(expect![[r#"
failed to read "../../test_projects/linter_bad_config/.elp_lint.toml":expected a right bracket, found an identifier at line 6 column 4
failed to read "../../test/test_projects/linter_bad_config/.elp_lint.toml":expected a right bracket, found an identifier at line 6 column 4
"#]]),
)
.expect("bad test");
@ -1573,6 +1601,20 @@ mod tests {
);
}
#[test_case(false ; "rebar")]
#[test_case(true ; "buck")]
fn lint_no_stream_produces_output(buck: bool) {
if otp::supports_eep66_sigils() {
simple_snapshot_expect_error(
args_vec!["lint", "--no-stream"],
"diagnostics",
expect_file!("../resources/test/diagnostics/lint_no_stream.stdout"),
buck,
None,
);
}
}
#[test_case(false ; "rebar")]
#[test_case(true ; "buck")]
fn lint_no_diagnostics_filter_all_enabled_json(buck: bool) {
@ -1607,7 +1649,7 @@ mod tests {
args_vec![
"lint",
"--config-file",
"../../test_projects/linter/elp_lint_test2.toml"
"../../test/test_projects/linter/elp_lint_test2.toml"
],
"linter",
expect_file!("../resources/test/linter/parse_elp_lint_explicit_enable_output.stdout"),
@ -1910,7 +1952,8 @@ mod tests {
simple_snapshot_expect_stderror(
args_vec!["lint",],
"buck_bad_config",
expect_file!("../resources/test/buck_bad_config/bxl_error_message.stdout"),
// @fb-only: expect_file!("../resources/test/buck_bad_config/bxl_error_message.stdout"),
expect_file!("../resources/test/buck_bad_config/bxl_error_message_oss.stdout"), // @oss-only
true,
None,
true,
@ -1925,7 +1968,7 @@ mod tests {
"lint",
"--no-stream"
"--config-file",
"../../test_projects/linter/elp_lint_warnings_as_errors.toml"
"../../test/test_projects/linter/elp_lint_warnings_as_errors.toml"
],
"linter",
expect_file!("../resources/test/linter/warnings_as_errors.stdout"),
@ -1940,7 +1983,7 @@ mod tests {
args_vec![
"lint",
"--config-file",
"../../test_projects/linter/elp_lint_custom_function_matches.toml",
"../../test/test_projects/linter/elp_lint_custom_function_matches.toml",
"--module",
"custom_function_matches"
],
@ -1957,7 +2000,7 @@ mod tests {
args_vec![
"lint",
"--config-file",
"../../test_projects/xref/elp_lint_unavailable_type.toml",
"../../test/test_projects/xref/elp_lint_unavailable_type.toml",
"--module",
"unavailable_type"
],
@ -1974,7 +2017,7 @@ mod tests {
args_vec![
"lint",
"--config-file",
"../../test_projects/linter/elp_lint_ssr_adhoc.toml",
"../../test/test_projects/linter/elp_lint_ssr_adhoc.toml",
],
"linter",
expect_file!("../resources/test/linter/ssr_ad_hoc.stdout"),
@ -1989,7 +2032,7 @@ mod tests {
args_vec![
"lint",
"--config-file",
"../../test_projects/linter/elp_lint_ssr_adhoc_parse_fail.toml",
"../../test/test_projects/linter/elp_lint_ssr_adhoc_parse_fail.toml",
],
"linter",
expect_file!("../resources/test/linter/ssr_ad_hoc_parse_fail.stdout"),
@ -2175,6 +2218,41 @@ mod tests {
#[test_case(false ; "rebar")]
#[test_case(true ; "buck")]
fn ssr_exclude_generated_by_default(buck: bool) {
simple_snapshot(
args_vec!["ssr", "--module", "erlang_diagnostics_errors_gen", "ok"],
"diagnostics",
expect_file!("../resources/test/diagnostics/ssr_exclude_generated.stdout"),
buck,
None,
);
}
#[test_case(false ; "rebar")]
#[test_case(true ; "buck")]
fn ssr_include_generated_when_requested(buck: bool) {
simple_snapshot(
args_vec![
"ssr",
"--module",
"erlang_diagnostics_errors_gen",
"--include-generated",
"ok"
],
"diagnostics",
expect_file!("../resources/test/diagnostics/ssr_include_generated.stdout"),
buck,
None,
);
}
#[test_case(false ; "rebar")]
#[test_case(true ; "buck")]
// We cannot use `should_panic` for this test, since the OSS CI runs with the `buck` feature disabled.
// When this happens the test is translated into a no-op, which does not panic.
// TODO(T248259687): Switch to should_panic once Buck2 is available on GitHub.
// Or remove the ignore once hierarchical support is implemented.
#[ignore] // Support for hierarchical config is not implemented yet
fn lint_hierarchical_config_basic(buck: bool) {
simple_snapshot_sorted(
args_vec!["lint", "--read-config"],
@ -2185,6 +2263,18 @@ mod tests {
);
}
#[test_case(false ; "rebar")]
#[test_case(true ; "buck")]
fn lint_linter_config_basic(buck: bool) {
simple_snapshot_sorted(
args_vec!["lint", "--read-config", "--no-stream"],
"linter_config",
expect_file!("../resources/test/linter_config/basic.stdout"),
buck,
None,
);
}
#[test_case(false ; "rebar")]
#[test_case(true ; "buck")]
fn eqwalizer_tests_check(buck: bool) {
@ -3046,7 +3136,7 @@ mod tests {
}
fn project_path(project: &str) -> String {
format!("../../test_projects/{project}")
format!("../../test/test_projects/{project}")
}
fn strip_ansi_codes(s: &str) -> String {

View file

@ -227,9 +227,6 @@ impl Reporter for JsonReporter<'_> {
diagnostics: &[EqwalizerDiagnostic],
) -> Result<()> {
let line_index = self.analysis.line_index(file_id)?;
// Pass include_Tests = false so that errors for tests files that are not opted-in are tagged as
// arc_types::Severity::Disabled and don't break CI.
let eqwalizer_enabled = self.analysis.is_eqwalizer_enabled(file_id, false).unwrap();
let file_path = &self.loaded.vfs.file_path(file_id);
let root_path = &self
.analysis
@ -238,12 +235,8 @@ impl Reporter for JsonReporter<'_> {
.root_dir;
let relative_path = get_relative_path(root_path, file_path);
for diagnostic in diagnostics {
let diagnostic = convert::eqwalizer_to_arc_diagnostic(
diagnostic,
&line_index,
relative_path,
eqwalizer_enabled,
);
let diagnostic =
convert::eqwalizer_to_arc_diagnostic(diagnostic, &line_index, relative_path);
let diagnostic = serde_json::to_string(&diagnostic)?;
writeln!(self.cli, "{diagnostic}")?;
}

View file

@ -157,10 +157,9 @@ impl ShellCommand {
}
"eqwalize-app" => {
let include_generated = options.contains(&"--include-generated");
let include_tests = options.contains(&"--include-tests");
if let Some(other) = options
.into_iter()
.find(|&opt| opt != "--include-generated" && opt != "--include-tests")
.find(|&opt| opt != "--include-generated")
{
return Err(ShellError::UnexpectedOption(
"eqwalize-app".into(),
@ -177,7 +176,6 @@ impl ShellCommand {
rebar,
app: app.into(),
include_generated,
include_tests,
bail_on_error: false,
})));
}
@ -185,10 +183,9 @@ impl ShellCommand {
}
"eqwalize-all" => {
let include_generated = options.contains(&"--include-generated");
let include_tests = options.contains(&"--include-tests");
if let Some(other) = options
.into_iter()
.find(|&opt| opt != "--include-generated" && opt != "--include-tests")
.find(|&opt| opt != "--include-generated")
{
return Err(ShellError::UnexpectedOption(
"eqwalize-all".into(),
@ -204,7 +201,6 @@ impl ShellCommand {
rebar,
format: None,
include_generated,
include_tests,
bail_on_error: false,
stats: false,
list_modules: false,
@ -226,10 +222,8 @@ COMMANDS:
eqwalize <modules> Eqwalize specified modules
--clause-coverage Use experimental clause coverage checker
eqwalize-all Eqwalize all modules in the current project
--include-tests Also eqwalize test modules from project
--clause-coverage Use experimental clause coverage checker
eqwalize-app <app> Eqwalize all modules in specified application
--include-tests Also eqwalize test modules from project
--clause-coverage Use experimental clause coverage checker
";

View file

@ -401,6 +401,9 @@ fn do_parse_one(
name: &str,
args: &Ssr,
) -> Result<Option<(String, FileId, Vec<diagnostics::Diagnostic>)>> {
if !args.include_generated && db.is_generated(file_id)? {
return Ok(None);
}
if !args.include_tests && db.is_test_suite_or_test_helper(file_id)?.unwrap_or(false) {
return Ok(None);
}

View file

@ -30,7 +30,7 @@ use serde::de::DeserializeOwned;
use serde_json::json;
use crate::from_json;
// @fb-only
// @fb-only: use crate::meta_only;
// Defines the server-side configuration of ELP. We generate *parts*
// of VS Code's `package.json` config from this.
@ -180,7 +180,7 @@ impl Config {
return;
}
self.data = ConfigData::from_json(json);
// @fb-only
// @fb-only: meta_only::harmonise_gks(self);
}
pub fn update_gks(&mut self, json: serde_json::Value) {

View file

@ -126,18 +126,11 @@ pub fn eqwalizer_to_arc_diagnostic(
d: &EqwalizerDiagnostic,
line_index: &LineIndex,
relative_path: &Path,
eqwalizer_enabled: bool,
) -> arc_types::Diagnostic {
let pos = position(line_index, d.range.start());
let line_num = pos.line + 1;
let character = Some(pos.character + 1);
let severity = if eqwalizer_enabled {
arc_types::Severity::Error
} else {
// We use Severity::Disabled so that diagnostics are reported in cont lint
// but not in CI.
arc_types::Severity::Disabled
};
let severity = arc_types::Severity::Error;
// formatting: https://fburl.com/max_wiki_link_to_phabricator_rich_text
let explanation = match &d.explanation {
Some(s) => format!("```\n{s}\n```"),

View file

@ -37,7 +37,7 @@ pub mod line_endings;
pub mod lsp_ext;
mod mem_docs;
pub mod memory_usage;
// @fb-only
// @fb-only: mod meta_only;
mod op_queue;
mod project_loader;
pub mod reload;
@ -108,7 +108,7 @@ pub fn otp_file_to_ignore(db: &Analysis, file_id: FileId) -> bool {
"redbug_dtop",
]
.iter()
// @fb-only
// @fb-only: .chain(meta_only::FILES_TO_IGNORE.iter())
.map(SmolStr::new)
.collect();
}

View file

@ -0,0 +1 @@
Project Initialisation Failed: invalid or missing buck 2 configuration

View file

@ -0,0 +1,138 @@
Reporting all diagnostics codes
Diagnostics reported:
app_a/src/app_a.erl:52:3-52:23::[Warning] [W0006] this statement has no effect
app_a/src/app_a.erl:3:10-3:21::[WeakWarning] [W0037] Unspecific include.
app_a/src/app_a.erl:27:3-27:9::[Warning] [W0017] Function 'foo:ok/0' is undefined.
app_a/src/app_a.erl:28:4-28:11::[Warning] [W0017] Function 'mod:foo/0' is undefined.
app_a/src/app_a.erl:72:4-72:11::[Warning] [W0017] Function 'foo:bar/2' is undefined.
app_a/src/app_a.erl:37:11-37:28::[Warning] [W0017] Function 'mod_name:fun_name/2' is undefined.
app_a/src/app_a.erl:58:11-58:24::[WeakWarning] [W0051] Binary string can be written using sigil syntax.
app_a/src/app_a.erl:4:1-4:41::[Warning] [W0020] Unused file: inets/include/httpd.hrl
app_a/src/app_a.erl:39:7-39:28::[Error] [L1267] variable 'A' shadowed in 'named fun'
app_a/src/app_a.erl:55:32-55:35::[Error] [L1295] type uri/0 undefined
app_a/src/app_a.erl:56:20-56:26::[Error] [L1295] type binary/1 undefined
app_a/src/app_a.erl:72:3-72:34::[Error] [L1252] record record undefined
app_a/src/app_a.erl:75:5-75:16::[Error] [L1252] record record undefined
app_a/src/app_a.erl:35:1-35:2::[Warning] [L1230] function g/1 is unused
app_a/src/app_a.erl:35:3-35:4::[Warning] [L1268] variable 'A' is unused
app_a/src/app_a.erl:36:3-36:4::[Warning] [L1268] variable 'F' is unused
app_a/src/app_a.erl:37:3-37:4::[Warning] [L1268] variable 'G' is unused
app_a/src/app_a.erl:38:3-38:4::[Warning] [L1268] variable 'H' is unused
app_a/src/app_a.erl:39:3-39:4::[Warning] [L1268] variable 'I' is unused
app_a/src/app_a.erl:39:7-39:28::[Warning] [L1268] variable 'A' is unused
app_a/src/app_a.erl:41:1-41:2::[Warning] [L1230] function h/0 is unused
app_a/src/app_a.erl:45:1-45:2::[Warning] [L1230] function i/0 is unused
app_a/src/app_a.erl:50:1-50:2::[Warning] [L1230] function j/2 is unused
app_a/src/app_a.erl:50:15-50:16::[Warning] [L1268] variable 'A' is unused
app_a/src/app_a.erl:50:23-50:24::[Warning] [L1268] variable 'B' is unused
app_a/src/app_a.erl:55:1-55:46::[Warning] [L1296] type session(_) is unused
app_a/src/app_a.erl:55:1-55:46::[Warning] [L1313] opaque type session(_) is not exported
app_a/src/app_a.erl:56:7-56:13::[Warning] [L1296] type source(_) is unused
app_a/src/app_a.erl:58:1-58:4::[Warning] [L1230] function map/2 is unused
app_a/src/app_a.erl:60:1-60:9::[Warning] [L1230] function with_dot/0 is unused
app_a/src/app_a.erl:62:1-62:9::[Warning] [L1230] function lang_dir/1 is unused
app_a/src/app_a.erl:66:1-66:7::[Warning] [L1230] function escape/1 is unused
app_a/src/app_a.erl:66:13-66:17::[Warning] [L1268] variable 'T' is unused
app_a/src/app_a.erl:67:9-67:25::[Warning] [L1260] record all_configs_file is unused
app_a/src/app_a.erl:71:1-71:2::[Warning] [L1230] function k/0 is unused
app_a/src/app_a.erl:74:1-74:2::[Warning] [L1230] function l/1 is unused
app_a/src/app_a.erl:77:1-77:2::[Warning] [L1230] function m/0 is unused
app_a/src/broken_parse_trans.erl:10:21-10:22::[Error] [L1256] field b undefined in record a
app_a/src/broken_parse_trans.erl:10:32-10:33::[Error] [L1262] variable 'B' is unbound
app_a/src/cascading.erl:9:5-9:6::[Error] [W0004] Missing ')'
3:10-3:15: function foo/0 undefined
6:10-6:15: function foo/0 undefined
8:7-8:10: spec for undefined function foo/0
app_a/src/diagnostics.erl:3:10-3:27::[WeakWarning] [W0037] Unspecific include.
app_a/src/diagnostics.erl:4:10-4:34::[WeakWarning] [W0037] Unspecific include.
app_a/src/diagnostics.erl:12:8-12:12::[Warning] [W0060] Match on a bound variable
app_a/src/diagnostics.erl:4:1-4:36::[Error] [L0000] Issue in included file
[app_a/include/broken_diagnostics.hrl] 1:8-1:15: P1702: bad attribute
[app_a/include/broken_diagnostics.hrl] 3:6-3:15: P1702: bad attribute
app_a/src/diagnostics.erl:6:31-6:45::[Error] [L1295] type undefined_type/0 undefined
app_a/src/diagnostics.erl:7:1-7:5::[Warning] [L1230] function main/1 is unused
app_a/src/diagnostics.erl:10:1-10:4::[Warning] [L1230] function foo/0 is unused
app_a/src/lint_recursive.erl:23:5-23:14::[Warning] [W0006] this statement has no effect
app_a/src/lint_recursive.erl:6:5-6:7::[Warning] [W0006] this statement has no effect
app_a/src/lint_recursive.erl:14:5-14:12::[Warning] [L1268] variable 'Config1' is unused
app_a/src/lint_recursive.erl:19:5-19:12::[Warning] [L1268] variable 'Config1' is unused
app_a/src/lints.erl:5:1-5:14::[Error] [P1700] head mismatch 'head_mismatcX' vs 'head_mismatch'
4:1-4:14: Mismatched clause name
app_a/src/lints.erl:4:22-4:23::[Warning] [W0018] Unexpected ';'
app_a/src/lints.erl:2:10-2:25::[Error] [L1227] function head_mismatch/1 undefined
app_a/src/otp27_docstrings.erl:34:9-34:24::[Warning] [W0002] Unused macro (THIS_IS_THE_END)
app_a/src/otp27_docstrings.erl:24:5-24:6::[Warning] [W0060] Match on a bound variable
app_a/src/otp27_docstrings.erl:30:5-30:6::[Warning] [W0060] Match on a bound variable
app_a/src/otp27_sigils.erl:11:6-11:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:12:5-12:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:13:5-13:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:14:5-14:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:15:5-15:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:17:6-17:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:18:5-18:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:19:5-19:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:20:5-20:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:21:5-21:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:23:6-23:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:24:5-24:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:25:5-25:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:26:5-26:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:27:5-27:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:29:6-29:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:30:5-30:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:31:5-31:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:32:5-32:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:33:5-33:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:35:6-35:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:36:5-36:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:37:5-37:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:38:5-38:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:39:5-39:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:41:6-41:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:42:5-42:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:43:5-43:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:44:5-44:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:45:5-45:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:47:6-47:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:48:5-48:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:49:5-49:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:50:5-50:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:51:5-51:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:53:6-53:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:53:6-53:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:54:5-54:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:54:5-54:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:55:5-55:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:55:5-55:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:56:5-56:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:57:5-57:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:59:6-59:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:60:5-60:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:61:5-61:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:62:5-62:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:63:5-63:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:65:6-65:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:66:5-66:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:67:5-67:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:68:5-68:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:69:5-69:24::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:76:5-79:8::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:76:5-79:8::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:80:5-84:9::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:80:5-84:9::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:85:5-89:10::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:85:5-89:10::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:90:5-94:11::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:95:5-99:12::[Warning] [W0006] this statement has no effect
app_a/src/otp27_sigils.erl:102:5-102:24::[WeakWarning] [W0051] Binary string can be written using sigil syntax.
app_a/src/otp27_sigils.erl:128:9-128:24::[Warning] [W0002] Unused macro (THIS_IS_THE_END)
app_a/src/otp27_sigils.erl:112:4-112:5::[Error] [P1711] syntax error before: X
4:15-4:18: function g/0 undefined
74:7-74:8: spec for undefined function g/0
app_a/src/otp27_sigils.erl:71:5-71:6::[Warning] [L1268] variable 'X' is unused
app_a/src/otp_7655.erl:5:1-5:28::[Error] [L1201] no module definition
app_a/src/parse_error_a_cascade.erl:10:20-11:1::[Error] [W0004] Missing 'atom'
6:6-6:11: function bar/0 undefined
app_a/src/suppressed.erl:8:5-8:9::[Warning] [L1268] variable 'Life' is unused
app_a/src/syntax.erl:5:46-5:47::[Error] [P1711] syntax error before: ')'
app_a/src/syntax.erl:11:9-11:10::[Error] [W0004] Missing ')'

View file

@ -1,9 +1,10 @@
module specified: diagnostics
Diagnostics reported in 1 modules:
diagnostics: 6
diagnostics: 7
2:9-2:26::[Hint] [W0037] Unspecific include.
3:0-3:35::[Error] [L0000] Issue in included file
3:9-3:33::[Hint] [W0037] Unspecific include.
5:30-5:44::[Error] [L1295] type undefined_type/0 undefined
6:0-6:4::[Warning] [L1230] function main/1 is unused
9:0-9:3::[Warning] [L1230] function foo/0 is unused
11:7-11:11::[Warning] [W0060] Match on a bound variable

View file

@ -0,0 +1,5 @@
module specified: diagnostics
Diagnostics reported in 1 modules:
diagnostics: 2
3:0-3:35::[Error] [L0000] Issue in included file
5:30-5:44::[Error] [L1295] type undefined_type/0 undefined

View file

@ -4,3 +4,4 @@
{"path":"app_a/src/diagnostics.erl","line":6,"char":31,"code":"ELP","severity":"error","name":"L1295 (L1295)","original":null,"replacement":null,"description":"type undefined_type/0 undefined\n\nFor more information see: /erlang-error-index/l/L1295","docPath":null}
{"path":"app_a/src/diagnostics.erl","line":7,"char":1,"code":"ELP","severity":"warning","name":"L1230 (L1230)","original":null,"replacement":null,"description":"function main/1 is unused\n\nFor more information see: /erlang-error-index/l/L1230","docPath":null}
{"path":"app_a/src/diagnostics.erl","line":10,"char":1,"code":"ELP","severity":"warning","name":"L1230 (L1230)","original":null,"replacement":null,"description":"function foo/0 is unused\n\nFor more information see: /erlang-error-index/l/L1230","docPath":null}
{"path":"app_a/src/diagnostics.erl","line":12,"char":8,"code":"ELP","severity":"warning","name":"W0060 (bound_var_in_lhs)","original":null,"replacement":null,"description":"Match on a bound variable\n\nFor more information see: /erlang-error-index/w/W0060","docPath":"website/docs/erlang-error-index/w/W0060.md"}

View file

@ -1,4 +1,6 @@
module specified: otp27_docstrings
Diagnostics reported in 1 modules:
otp27_docstrings: 1
otp27_docstrings: 3
23:4-23:5::[Warning] [W0060] Match on a bound variable
29:4-29:5::[Warning] [W0060] Match on a bound variable
33:8-33:23::[Warning] [W0002] Unused macro (THIS_IS_THE_END)

View file

@ -0,0 +1,2 @@
module specified: erlang_diagnostics_errors_gen
No matches found

View file

@ -0,0 +1,5 @@
module specified: erlang_diagnostics_errors_gen
erlang_diagnostics_errors_gen: 1
6:5-6:7::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: ok.
Matches found in 1 modules

View file

@ -1,11 +1,10 @@
Usage: [--project PROJECT] [--as PROFILE] [[--format FORMAT]] [--rebar] [--include-tests] [--bail-on-error] [--stats] [--list-modules]
Usage: [--project PROJECT] [--as PROFILE] [[--format FORMAT]] [--rebar] [--bail-on-error] [--stats] [--list-modules]
Available options:
--project <PROJECT> Path to directory with project, or to a JSON file (defaults to `.`)
--as <PROFILE> Rebar3 profile to pickup (default is test)
--format <FORMAT> Show diagnostics in JSON format
--rebar Run with rebar
--include-tests Also eqwalize test modules from project
--bail-on-error Exit with a non-zero status code if any errors are found
--stats Print statistics when done
--list-modules When printing statistics, include the list of modules parsed

View file

@ -1,4 +1,4 @@
Usage: [--project PROJECT] [--as PROFILE] [--include-tests] [--rebar] [--bail-on-error] <APP>
Usage: [--project PROJECT] [--as PROFILE] [--rebar] [--bail-on-error] <APP>
Available positional items:
<APP> app name
@ -6,7 +6,6 @@ Available positional items:
Available options:
--project <PROJECT> Path to directory with project, or to a JSON file (defaults to `.`)
--as <PROFILE> Rebar3 profile to pickup (default is test)
--include-tests Also eqwalize test modules from project
--rebar Run with rebar
--bail-on-error Exit with a non-zero status code if any errors are found
-h, --help Prints help information

View file

@ -1,10 +1,9 @@
Usage: [--project PROJECT] [--include-tests] [--bail-on-error] <TARGET>
Usage: [--project PROJECT] [--bail-on-error] <TARGET>
Available positional items:
<TARGET> target, like //erl/chatd/...
Available options:
--project <PROJECT> Path to directory with project, or to a JSON file (defaults to `.`)
--include-tests Also eqwalize test modules from project
--bail-on-error Exit with a non-zero status code if any errors are found
-h, --help Prints help information

View file

@ -1,14 +0,0 @@
error: incorrect_return_type_in_cb_implementation (See https://fb.me/eqwalizer_errors#incorrect_return_type_in_cb_implementation)
┌─ check/src/callbacks3_neg.erl:12:1
12 │ -behavior(gen_server).
│ ^^^^^^^^^^^^^^^^^^^^^
│ │
│ Incorrect return type for implementation of gen_server:handle_cast/2. Expected: {'noreply', term()} | {'noreply', term(), timeout() | 'hibernate' | {'continue', term()}} | {'stop', term(), term()}, Got: 'wrong_ret'.
'wrong_ret' is not compatible with {'noreply', term()} | {'noreply', term(), timeout() | 'hibernate' | {'continue', term()}} | {'stop', term(), term()}
because
'wrong_ret' is not compatible with {'noreply', term()}
1 ERROR

View file

@ -1,14 +0,0 @@
error: incorrect_return_type_in_cb_implementation (See https://fb.me/eqwalizer_errors#incorrect_return_type_in_cb_implementation)
┌─ check/src/callbacks3_neg.erl:12:1
12 │ -behavior(gen_server).
│ ^^^^^^^^^^^^^^^^^^^^^
│ │
│ Incorrect return type for implementation of gen_server:handle_cast/2. Expected: {'noreply', term()} | {'noreply', term(), timeout() | 'hibernate' | {'continue', term()}} | {'stop', term(), term()}, Got: 'wrong_ret'.
'wrong_ret' is not compatible with {'noreply', term()} | {'noreply', term(), timeout() | 'hibernate' | {'continue', term()}} | {'stop', term(), term()}
because
'wrong_ret' is not compatible with {'noreply', term()}
1 ERROR

View file

@ -1,14 +0,0 @@
error: incorrect_return_type_in_cb_implementation (See https://fb.me/eqwalizer_errors#incorrect_return_type_in_cb_implementation)
┌─ check/src/callbacks3_neg.erl:12:1
12 │ -behavior(gen_server).
│ ^^^^^^^^^^^^^^^^^^^^^
│ │
│ Incorrect return type for implementation of gen_server:handle_cast/2. Expected: {'noreply', term()} | {'noreply', term(), gen_server:action()} | {'stop', term(), term()}, Got: 'wrong_ret'.
'wrong_ret' is not compatible with {'noreply', term()} | {'noreply', term(), gen_server:action()} | {'stop', term(), term()}
because
'wrong_ret' is not compatible with {'noreply', term()}
1 ERROR

View file

@ -0,0 +1,27 @@
error: incorrect_return_type_in_cb_implementation (See https://fb.me/eqwalizer_errors#incorrect_return_type_in_cb_implementation)
┌─ check/src/callbacks3_neg.erl:13:1
13 │ -behavior(gen_server).
│ ^^^^^^^^^^^^^^^^^^^^^ Incorrect return type for implementation of gen_server:handle_cast/2.
Expected: {'noreply', term()} | {'noreply', term(), timeout() | 'hibernate' | {'continue', term()}} | {'stop', term(), term()}
Got: 'wrong_ret'
error: incorrect_return_type_in_cb_implementation (See https://fb.me/eqwalizer_errors#incorrect_return_type_in_cb_implementation)
┌─ check/src/callbacks3_neg.erl:13:1
13 │ -behavior(gen_server).
│ ^^^^^^^^^^^^^^^^^^^^^
│ │
│ Incorrect return type for implementation of gen_server:handle_info/2.
Expected: {'noreply', term()} | {'noreply', term(), timeout() | 'hibernate' | {'continue', term()}} | {'stop', term(), term()}
Got: {'noreply', 'ok', 'wrong_atom'}
Because in the expression's type:
{ 'noreply', 'ok',
Here the type is: 'wrong_atom'
Context expects type: 'infinity' | number() | 'hibernate' | {'continue', term()}
No candidate matches in the expected union.
}
2 ERRORS

View file

@ -70,4 +70,20 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types
Expression has type: #{a := dynamic(), dynamic() => dynamic()}
Context expected type: 'err'
5 ERRORS
error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types)
┌─ eqwater/src/eqwater_maps.erl:70:29
70 │ (_, #{a := V}) -> is_ok(V)
│ ^
│ │
│ V.
Expression has type: 'ok' | 'err'
Context expected type: 'ok'
Because in the expression's type:
Here the type is a union type with some valid candidates: 'ok'
However the following candidate: 'err'
Differs from the expected type: 'ok'
6 ERRORS

View file

@ -1,6 +1,5 @@
Diagnostics reported:
Reporting all diagnostics codes
app_a/src/app_a.erl:4:9-4:16::[Warning] [W0002] Unused macro (MACRO_B)
app_a/src/app_a.erl:6:1-6:5::[Warning] [L1230] function main/0 is unused
app_b/src/app_b.erl:4:9-4:16::[Warning] [W0002] Unused macro (MACRO_B)
app_b/src/app_b.erl:6:1-6:5::[Warning] [L1230] function main/0 is unused
app_b/src/app_b.erl:6:1-6:5::[Warning] [L1230] function main/0 is unused

View file

@ -1,3 +1,3 @@
Diagnostics reported:
app_a/src/app_a_unused_param.erl:5:5-5:6::[Warning] [W0010] this variable is unused
app_a/src/app_a.erl:9:6-9:7::[Warning] [W0010] this variable is unused
app_a/src/app_a_unused_param.erl:5:5-5:6::[Warning] [W0010] this variable is unused

View file

@ -5,6 +5,8 @@
{"path":"app_a/src/app_a.erl","line":5,"char":5,"code":"ELP","severity":"warning","name":"W0011 (application_get_env)","original":null,"replacement":null,"description":"module `app_a` belongs to app `app_a`, but reads env for `misc`\n\nFor more information see: /erlang-error-index/w/W0011","docPath":"website/docs/erlang-error-index/w/W0011.md"}
{"path":"app_a/src/app_a.erl","line":8,"char":7,"code":"ELP","severity":"warning","name":"W0018 (unexpected_semi_or_dot)","original":null,"replacement":null,"description":"Unexpected ';'\n\nFor more information see: /erlang-error-index/w/W0018","docPath":"website/docs/erlang-error-index/w/W0018.md"}
{"path":"app_a/src/app_a.erl","line":9,"char":1,"code":"ELP","severity":"error","name":"P1700 (head_mismatch)","original":null,"replacement":null,"description":"head mismatch 'fooX' vs 'food'\n\nFor more information see: /erlang-error-index/p/P1700","docPath":null}
{"path":"app_a/src/app_a_ssr.erl","line":7,"char":6,"code":"ELP","severity":"warning","name":"W0060 (bound_var_in_lhs)","original":null,"replacement":null,"description":"Match on a bound variable\n\nFor more information see: /erlang-error-index/w/W0060","docPath":"website/docs/erlang-error-index/w/W0060.md"}
{"path":"app_a/src/app_a_ssr.erl","line":8,"char":6,"code":"ELP","severity":"warning","name":"W0060 (bound_var_in_lhs)","original":null,"replacement":null,"description":"Match on a bound variable\n\nFor more information see: /erlang-error-index/w/W0060","docPath":"website/docs/erlang-error-index/w/W0060.md"}
{"path":"app_a/src/app_a_unused_param.erl","line":5,"char":5,"code":"ELP","severity":"warning","name":"L1268 (L1268)","original":null,"replacement":null,"description":"variable 'X' is unused\n\nFor more information see: /erlang-error-index/l/L1268","docPath":null}
{"path":"app_a/src/custom_function_matches.erl","line":13,"char":5,"code":"ELP","severity":"warning","name":"W0017 (undefined_function)","original":null,"replacement":null,"description":"Function 'excluded:function/0' is undefined.\n\nFor more information see: /erlang-error-index/w/W0017","docPath":"website/docs/erlang-error-index/w/W0017.md"}
{"path":"app_a/src/custom_function_matches.erl","line":14,"char":5,"code":"ELP","severity":"warning","name":"W0017 (undefined_function)","original":null,"replacement":null,"description":"Function 'not_excluded:function/0' is undefined.\n\nFor more information see: /erlang-error-index/w/W0017","docPath":"website/docs/erlang-error-index/w/W0017.md"}

View file

@ -8,6 +8,8 @@ app_a/src/app_a.erl:20:1-20:4::[Warning] [L1230] function bat/2 is unused
app_a/src/app_a.erl:5:5-5:35::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc`
app_a/src/app_a.erl:8:7-8:8::[Warning] [W0018] Unexpected ';'
app_a/src/app_a.erl:9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food'
app_a/src/app_a_ssr.erl:7:6-7:7::[Warning] [W0060] Match on a bound variable
app_a/src/app_a_ssr.erl:8:6-8:7::[Warning] [W0060] Match on a bound variable
app_a/src/app_a_unused_param.erl:5:5-5:6::[Warning] [L1268] variable 'X' is unused
app_a/src/custom_function_matches.erl:13:5-13:22::[Warning] [W0017] Function 'excluded:function/0' is undefined.
app_a/src/custom_function_matches.erl:14:5-14:26::[Warning] [W0017] Function 'not_excluded:function/0' is undefined.

View file

@ -8,6 +8,8 @@ app_a/src/app_a.erl:20:1-20:4::[Error] [L1230] function bat/2 is unused
app_a/src/app_a.erl:5:5-5:35::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc`
app_a/src/app_a.erl:8:7-8:8::[Warning] [W0018] Unexpected ';'
app_a/src/app_a.erl:9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food'
app_a/src/app_a_ssr.erl:7:6-7:7::[Warning] [W0060] Match on a bound variable
app_a/src/app_a_ssr.erl:8:6-8:7::[Warning] [W0060] Match on a bound variable
app_a/src/app_a_unused_param.erl:5:5-5:6::[Error] [L1268] variable 'X' is unused
app_a/src/custom_function_matches.erl:13:5-13:22::[Warning] [W0017] Function 'excluded:function/0' is undefined.
app_a/src/custom_function_matches.erl:14:5-14:26::[Warning] [W0017] Function 'not_excluded:function/0' is undefined.

View file

@ -0,0 +1,5 @@
Diagnostics reported:
Reporting all diagnostics codes
app_a/src/app_a.erl:3:9-3:16::[Warning] [W0002] Unused macro (MACRO_A)
app_a/src/app_a.erl:4:9-4:14::[Warning] [L1260] record rec_a is unused
app_b/src/app_b.erl:3:9-3:16::[Warning] [W0002] Unused macro (MACRO_B)

View file

@ -1,4 +1,4 @@
Usage: [--project PROJECT] [--module MODULE] [--file ARG] [--to TO] [--no-diags] [--experimental] [--as PROFILE] [--dump-includes] [--rebar] [--include-generated] [--serial] [--use-cli-severity] [[--format FORMAT]] [--report-system-stats]
Usage: [--project PROJECT] [--module MODULE] [--file ARG] [--to TO] [--no-diags] [--experimental] [--as PROFILE] [--dump-includes] [--rebar] [--include-generated] [--serial] [--use-cli-severity] [[--format FORMAT]] [--report-system-stats] [[--severity SEVERITY]]
Available options:
--project <PROJECT> Path to directory with project, or to a JSON file (defaults to `.`)
@ -15,4 +15,5 @@ Available options:
--use-cli-severity If specified, use the provided CLI severity mapping instead of the default one
--format <FORMAT> Show diagnostics in JSON format
--report-system-stats Report system memory usage and other statistics
--severity <SEVERITY> Minimum severity level to report. Valid values: error, warning, weak_warning, information
-h, --help Prints help information

View file

@ -192,6 +192,14 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types
Expression has type: 'wrong_ret'
Context expected type: 'error'
error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types)
┌─ app_a/test/app_a_test_helpers_not_opted_in.erl:5:11
5 │ fail() -> error.
│ ^^^^^ 'error'.
Expression has type: 'error'
Context expected type: 'ok'
error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types)
┌─ app_b/src/app_b.erl:16:5
@ -200,4 +208,4 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types
Expression has type: [T]
Context expected type: T
20 ERRORS
21 ERRORS

View file

@ -17,4 +17,5 @@
{"path":"app_a/src/app_a_mod2.erl","line":22,"char":1,"code":"ELP","severity":"error","name":"eqWAlizer: type_alias_is_non_productive","original":null,"replacement":null,"description":"```lang=error,counterexample\n\nrecursive type invalid/0 is not productive\n```\n\n> [docs on `type_alias_is_non_productive`](https://fb.me/eqwalizer_errors#type_alias_is_non_productive)","docPath":null}
{"path":"app_a/src/app_a_mod2.erl","line":31,"char":9,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"'an_atom'","replacement":null,"description":"```lang=error,counterexample\n`'an_atom'`.\n\nExpression has type: 'an_atom'\nContext expected type: number()\n```\n\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null}
{"path":"app_a/test/app_a_test_helpers.erl","line":6,"char":11,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"'wrong_ret'","replacement":null,"description":"```lang=error,counterexample\n`'wrong_ret'`.\n\nExpression has type: 'wrong_ret'\nContext expected type: 'error'\n```\n\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null}
{"path":"app_a/test/app_a_test_helpers_not_opted_in.erl","line":5,"char":11,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"'error'","replacement":null,"description":"```lang=error,counterexample\n`'error'`.\n\nExpression has type: 'error'\nContext expected type: 'ok'\n```\n\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null}
{"path":"app_b/src/app_b.erl","line":16,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"L","replacement":null,"description":"```lang=error,counterexample\n`L`.\n\nExpression has type: [T]\nContext expected type: T\n```\n\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null}

View file

@ -192,6 +192,14 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types
Expression has type: 'wrong_ret'
Context expected type: 'error'
error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types)
┌─ app_a/test/app_a_test_helpers_not_opted_in.erl:5:11
5 │ fail() -> error.
│ ^^^^^ 'error'.
Expression has type: 'error'
Context expected type: 'ok'
error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types)
┌─ app_b/src/app_b.erl:16:5
@ -200,4 +208,4 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types
Expression has type: [T]
Context expected type: T
20 ERRORS
21 ERRORS

View file

@ -17,4 +17,5 @@
{"path":"app_a/src/app_a_mod2.erl","line":22,"char":1,"code":"ELP","severity":"error","name":"eqWAlizer: type_alias_is_non_productive","original":null,"replacement":null,"description":"```lang=error,counterexample\n\nrecursive type invalid/0 is not productive\n```\n\n> [docs on `type_alias_is_non_productive`](https://fb.me/eqwalizer_errors#type_alias_is_non_productive)","docPath":null}
{"path":"app_a/src/app_a_mod2.erl","line":31,"char":9,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"'an_atom'","replacement":null,"description":"```lang=error,counterexample\n`'an_atom'`.\n\nExpression has type: 'an_atom'\nContext expected type: number()\n```\n\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null}
{"path":"app_a/test/app_a_test_helpers.erl","line":6,"char":11,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"'wrong_ret'","replacement":null,"description":"```lang=error,counterexample\n`'wrong_ret'`.\n\nExpression has type: 'wrong_ret'\nContext expected type: 'error'\n```\n\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null}
{"path":"app_a/test/app_a_test_helpers_not_opted_in.erl","line":5,"char":11,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"'error'","replacement":null,"description":"```lang=error,counterexample\n`'error'`.\n\nExpression has type: 'error'\nContext expected type: 'ok'\n```\n\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null}
{"path":"app_b/src/app_b.erl","line":16,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"L","replacement":null,"description":"```lang=error,counterexample\n`L`.\n\nExpression has type: [T]\nContext expected type: T\n```\n\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null}

View file

@ -192,4 +192,12 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types
Expression has type: 'wrong_ret'
Context expected type: 'error'
19 ERRORS
error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types)
┌─ app_a/test/app_a_test_helpers_not_opted_in.erl:5:11
5 │ fail() -> error.
│ ^^^^^ 'error'.
Expression has type: 'error'
Context expected type: 'ok'
20 ERRORS

View file

@ -192,4 +192,12 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types
Expression has type: 'wrong_ret'
Context expected type: 'error'
19 ERRORS
error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types)
┌─ app_a/test/app_a_test_helpers_not_opted_in.erl:5:11
5 │ fail() -> error.
│ ^^^^^ 'error'.
Expression has type: 'error'
Context expected type: 'ok'
20 ERRORS

View file

@ -1,5 +1,5 @@
Reporting all diagnostics codes
module specified: unavailable_type
Diagnostics reported:
app_a/src/unavailable_type.erl:10:43-10:58::[Warning] [W0059] The type 'app_c:my_type_c/0' is defined in application 'app_c', but the application is not a dependency of 'app_a' (defined in 'fbcode//whatsapp/elp/test_projects/xref:app_a').
app_a/src/unavailable_type.erl:6:16-6:31::[Warning] [W0059] The type 'app_c:my_type_c/0' is defined in application 'app_c', but the application is not a dependency of 'app_a' (defined in 'fbcode//whatsapp/elp/test_projects/xref:app_a').
app_a/src/unavailable_type.erl:10:43-10:58::[Warning] [W0059] The type 'app_c:my_type_c/0' is defined in application 'app_c', but the application is not a dependency of 'app_a' (defined in 'root//xref:app_a').
app_a/src/unavailable_type.erl:6:16-6:31::[Warning] [W0059] The type 'app_c:my_type_c/0' is defined in application 'app_c', but the application is not a dependency of 'app_a' (defined in 'root//xref:app_a').

View file

@ -2073,7 +2073,7 @@ impl Server {
};
for (_, _, file_id) in module_index.iter_own() {
match snapshot.analysis.should_eqwalize(file_id, false) {
match snapshot.analysis.should_eqwalize(file_id) {
Ok(true) => {
files.push(file_id);
}

View file

@ -33,7 +33,7 @@ use super::FILE_WATCH_LOGGER_NAME;
use super::logger::LspLogger;
use crate::config::Config;
use crate::from_json;
// @fb-only
// @fb-only: use crate::meta_only::get_log_dir;
use crate::server::Handle;
use crate::server::LOGGER_NAME;
use crate::server::Server;
@ -126,7 +126,7 @@ impl ServerSetup {
// Set up a logger for tracking down why we are seeing stale
// results when branches are switched, as per T218973130
// @fb-only
// @fb-only: let log_dir = get_log_dir();
let log_dir = format!("{}/elp", std::env::temp_dir().display()); // @oss-only
let _ = fs::create_dir_all(&log_dir);
let log_file = format!(

View file

@ -36,9 +36,11 @@ use parking_lot::Mutex;
use parking_lot::RwLock;
use serde::Deserialize;
use serde::Serialize;
use vfs::AnchoredPathBuf;
use crate::config::Config;
use crate::convert;
use crate::convert::url_from_abs_path;
use crate::line_endings::LineEndings;
use crate::mem_docs::MemDocs;
use crate::server::EqwalizerTypes;
@ -186,6 +188,14 @@ impl Snapshot {
self.line_ending_map.read()[&id]
}
pub(crate) fn anchored_path(&self, path: &AnchoredPathBuf) -> Option<Url> {
let mut base = self.vfs.read().file_path(path.anchor).clone();
base.pop();
let path = base.join(&path.path)?;
let path = path.as_path()?;
Some(url_from_abs_path(path))
}
pub fn update_cache_for_file(
&self,
file_id: FileId,
@ -193,7 +203,7 @@ impl Snapshot {
) -> Result<()> {
let _ = self.analysis.def_map(file_id)?;
if optimize_for_eqwalizer {
let should_eqwalize = self.analysis.should_eqwalize(file_id, false)?;
let should_eqwalize = self.analysis.should_eqwalize(file_id)?;
if should_eqwalize {
let _ = self.analysis.module_ast(file_id)?;
}
@ -242,7 +252,7 @@ impl Snapshot {
let file_ids: Vec<FileId> = module_index
.iter_own()
.filter_map(|(_, _, file_id)| {
if let Ok(true) = self.analysis.should_eqwalize(file_id, false) {
if let Ok(true) = self.analysis.should_eqwalize(file_id) {
Some(file_id)
} else {
None

View file

@ -10,6 +10,7 @@
//! Conversion of rust-analyzer specific types to lsp_types equivalents.
use std::mem;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;
@ -47,6 +48,7 @@ use elp_ide::elp_ide_db::elp_base_db::FileId;
use elp_ide::elp_ide_db::elp_base_db::FilePosition;
use elp_ide::elp_ide_db::elp_base_db::FileRange;
use elp_ide::elp_ide_db::rename::RenameError;
use elp_ide::elp_ide_db::source_change::FileSystemEdit;
use elp_ide::elp_ide_db::source_change::SourceChange;
use elp_ide_db::text_edit::Indel;
use elp_ide_db::text_edit::TextEdit;
@ -121,9 +123,9 @@ pub(crate) fn optional_versioned_text_document_identifier(
pub(crate) fn text_document_edit(
snap: &Snapshot,
file_id: FileId,
text_document: lsp_types::OptionalVersionedTextDocumentIdentifier,
edit: TextEdit,
) -> Result<lsp_types::TextDocumentEdit> {
let text_document = optional_versioned_text_document_identifier(snap, file_id);
let line_index = snap.analysis.line_index(file_id)?;
let line_endings = snap.line_endings(file_id);
let edits: Vec<lsp_types::OneOf<lsp_types::TextEdit, lsp_types::AnnotatedTextEdit>> = edit
@ -131,34 +133,131 @@ pub(crate) fn text_document_edit(
.map(|it| lsp_types::OneOf::Left(text_edit(&line_index, line_endings, it)))
.collect();
// if snap.analysis.is_library_file(file_id)? && snap.config.change_annotation_support() {
// for edit in &mut edits {
// edit.annotation_id = Some(outside_workspace_annotation_id())
// }
// }
Ok(lsp_types::TextDocumentEdit {
text_document,
edits,
})
}
pub(crate) fn text_document_ops(
snap: &Snapshot,
file_system_edit: FileSystemEdit,
) -> Cancellable<Vec<lsp_types::DocumentChangeOperation>> {
let mut ops = Vec::new();
match file_system_edit {
FileSystemEdit::CreateFile {
dst,
initial_contents,
} => {
if let Some(uri) = snap.anchored_path(&dst) {
let create_file = lsp_types::ResourceOp::Create(lsp_types::CreateFile {
uri: uri.clone(),
options: None,
annotation_id: None,
});
ops.push(lsp_types::DocumentChangeOperation::Op(create_file));
if !initial_contents.is_empty() {
let text_document =
lsp_types::OptionalVersionedTextDocumentIdentifier { uri, version: None };
let text_edit = lsp_types::TextEdit {
range: lsp_types::Range::default(),
new_text: initial_contents,
};
let edit_file = lsp_types::TextDocumentEdit {
text_document,
edits: vec![lsp_types::OneOf::Left(text_edit)],
};
ops.push(lsp_types::DocumentChangeOperation::Edit(edit_file));
}
} else {
log::warn!("create file failed: {:?}", dst);
}
}
FileSystemEdit::MoveFile { src, dst } => {
if let Some(new_uri) = snap.anchored_path(&dst) {
let old_uri = snap.file_id_to_url(src);
let rename_file = lsp_types::RenameFile {
old_uri,
new_uri,
options: None,
annotation_id: None,
};
ops.push(lsp_types::DocumentChangeOperation::Op(
lsp_types::ResourceOp::Rename(rename_file),
))
} else {
log::warn!("rename file failed: {:?} -> {:?}", src, dst);
}
}
}
Ok(ops)
}
pub(crate) fn workspace_edit(
snap: &Snapshot,
source_change: SourceChange,
mut source_change: SourceChange,
) -> Result<lsp_types::WorkspaceEdit> {
let mut edits: Vec<_> = vec![];
for (file_id, edit) in source_change.source_file_edits {
// let edit = snippet_text_document_edit(snap, source_change.is_snippet, file_id, edit)?;
let edit = text_document_edit(snap, file_id, edit)?;
edits.push(lsp_types::TextDocumentEdit {
text_document: edit.text_document,
edits: edit.edits.into_iter().collect(),
});
let mut document_changes: Vec<lsp_types::DocumentChangeOperation> = Vec::new();
// This is copying RA's order of operations, first file creates,
// then edits, then file moves.
// This allows us to apply edits to the file once it has
// moved. Except we have no FileId at that point
for op in &mut source_change.file_system_edits {
if let FileSystemEdit::CreateFile {
dst,
initial_contents,
} = op
{
// replace with a placeholder to avoid cloning the edit
let op = FileSystemEdit::CreateFile {
dst: dst.clone(),
initial_contents: mem::take(initial_contents),
};
let ops = text_document_ops(snap, op)?;
document_changes.extend_from_slice(&ops);
}
}
let document_changes = lsp_types::DocumentChanges::Edits(edits);
for op in source_change.file_system_edits {
if !matches!(op, FileSystemEdit::CreateFile { .. }) {
let ops = text_document_ops(snap, op)?;
document_changes.extend_from_slice(&ops);
}
}
for (file_id, edit) in source_change.source_file_edits {
let text_document = optional_versioned_text_document_identifier(snap, file_id);
let edit = text_document_edit(snap, file_id, text_document, edit)?;
document_changes.push(lsp_types::DocumentChangeOperation::Edit(
lsp_types::TextDocumentEdit {
text_document: edit.text_document,
edits: edit.edits.into_iter().collect(),
},
));
}
// Edits on renamed files. The LineIndex from the original can be used.
for (file_ref, edit) in source_change.new_file_edits {
if let Some(uri) = snap.anchored_path(&file_ref.clone().into()) {
let version = snap.url_file_version(&uri);
let text_document = lsp_types::OptionalVersionedTextDocumentIdentifier { uri, version };
let edit = text_document_edit(snap, file_ref.anchor, text_document, edit)?;
document_changes.push(lsp_types::DocumentChangeOperation::Edit(
lsp_types::TextDocumentEdit {
text_document: edit.text_document,
edits: edit.edits.into_iter().collect(),
},
));
} else {
log::warn!("new file edit failed: {:?}", file_ref);
}
}
let workspace_edit = lsp_types::WorkspaceEdit {
changes: None,
document_changes: Some(document_changes),
document_changes: Some(lsp_types::DocumentChanges::Operations(document_changes)),
change_annotations: None,
};
Ok(workspace_edit)
@ -182,10 +281,6 @@ pub(crate) fn code_action(
) -> Result<lsp_types::CodeActionOrCommand> {
let mut res = lsp_types::CodeAction {
title: assist.label.to_string(),
// group: assist
// .group
// .filter(|_| snap.config.code_action_group())
// .map(|gr| gr.0),
kind: Some(code_action_kind(assist.id.1)),
edit: None,
is_preferred: None,

View file

@ -31,7 +31,7 @@ mod tests {
#[test]
#[ignore]
fn test_success_case() {
let path_str = "../../test_projects/buck_tests";
let path_str = "../../test/test_projects/buck_tests";
let path: PathBuf = path_str.into();
let cli = Fake::default();
@ -65,7 +65,7 @@ mod tests {
let ast = analysis.module_ast(file_id).unwrap();
assert_eq!(ast.errors, vec![]);
let eq_enabled = analysis
.is_eqwalizer_enabled(file_id, false)
.is_eqwalizer_enabled(file_id)
.unwrap_or_else(|_| panic!("Failed to check if eqwalizer enabled for {module}"));
assert_eq!(eq_enabled, eqwalizer_enabled);
let project_data = analysis.project_data(file_id).unwrap();
@ -76,7 +76,7 @@ mod tests {
#[test]
#[ignore]
fn test_load_buck_targets() {
let path_str = "../../test_projects/buck_tests";
let path_str = "../../test/test_projects/buck_tests";
let path: PathBuf = path_str.into();
let (elp_config, buck_config) =

View file

@ -36,7 +36,7 @@ use crate::support::diagnostic_project;
fn test_run_mock_lsp() {
if cfg!(feature = "buck") {
let workspace_root = AbsPathBuf::assert(
Utf8Path::new(env!("CARGO_WORKSPACE_DIR")).join("test_projects/end_to_end"),
Utf8Path::new(env!("CARGO_WORKSPACE_DIR")).join("test/test_projects/end_to_end"),
);
// Sanity check
@ -70,7 +70,7 @@ fn test_run_mock_lsp() {
}
],
"textDocument": {
"uri": "file:///[..]/test_projects/end_to_end/assist_examples/src/head_mismatch.erl",
"uri": "file:///[..]/test/test_projects/end_to_end/assist_examples/src/head_mismatch.erl",
"version": 0
}
}
@ -99,7 +99,7 @@ fn test_run_mock_lsp() {
}
],
"textDocument": {
"uri": "file:///[..]/test_projects/end_to_end/assist_examples/src/head_mismatch.erl",
"uri": "file:///[..]/test/test_projects/end_to_end/assist_examples/src/head_mismatch.erl",
"version": 0
}
}
@ -128,7 +128,7 @@ fn test_run_mock_lsp() {
}
],
"textDocument": {
"uri": "file:///[..]/test_projects/end_to_end/assist_examples/src/head_mismatch.erl",
"uri": "file:///[..]/test/test_projects/end_to_end/assist_examples/src/head_mismatch.erl",
"version": 0
}
}
@ -157,7 +157,7 @@ fn test_run_mock_lsp() {
}
],
"textDocument": {
"uri": "file:///[..]/test_projects/end_to_end/assist_examples/src/head_mismatch.erl",
"uri": "file:///[..]/test/test_projects/end_to_end/assist_examples/src/head_mismatch.erl",
"version": 0
}
}
@ -175,7 +175,7 @@ fn test_run_mock_lsp() {
fn test_e2e_eqwalizer_module() {
if cfg!(feature = "buck") {
let workspace_root = AbsPathBuf::assert(
Utf8Path::new(env!("CARGO_WORKSPACE_DIR")).join("test_projects/standard"),
Utf8Path::new(env!("CARGO_WORKSPACE_DIR")).join("test/test_projects/standard"),
);
// Sanity check
@ -321,7 +321,7 @@ fn test_e2e_eqwalizer_module() {
"source": "eqWAlizer"
}
],
"uri": "file:///[..]/test_projects/standard/app_a/src/app_a.erl",
"uri": "file:///[..]/test/test_projects/standard/app_a/src/app_a.erl",
"version": 0
}"#]],
);
@ -334,7 +334,7 @@ fn test_e2e_eqwalizer_module() {
// #[test]
// fn test_e2e_eqwalizer_header() {
// let workspace_root =
// AbsPathBuf::assert(Path::new(env!("CARGO_WORKSPACE_DIR")).join("test_projects/standard"));
// AbsPathBuf::assert(Path::new(env!("CARGO_WORKSPACE_DIR")).join("test/test_projects/standard"));
// // Sanity check
// assert!(std::fs::metadata(&workspace_root).is_ok());

View file

@ -865,7 +865,7 @@ mod tests {
f() ->
As = [1,2,3],
Bs = [4,5,6],
[{X,Y}~ || X <- As && Y <- Bs]
[{X,Y, ~} || X <- As && Y <- Bs]
.
",
&["Y", "X", "As", "Bs"],

View file

@ -448,6 +448,7 @@ foo(#record.field) -> #record.field.
fn record() {
check(
r#"
//- expect_parse_errors
foo1(#record{field = 1}) -> #record{field = A + B}.
foo2(#record{field}) -> #record{field = }.
"#,
@ -473,6 +474,7 @@ foo2(#record{field}) -> #record{field = }.
fn record_update() {
check(
r#"
//- expect_parse_errors
foo1() -> Expr#record{field = undefined}.
foo2() -> Expr#record{field = ok, missing = }.
"#,
@ -557,7 +559,7 @@ fn case() {
r#"
foo() ->
case 1 + 2 of
X when X andalso true; X <= 100, X >= 5 -> ok;
X when X andalso true; X =< 100, X >= 5 -> ok;
_ -> error
end.
"#,
@ -566,7 +568,7 @@ foo() ->
case (1 + 2) of
X when
(X andalso true);
(X < 100),
(X =< 100),
(X >= 5)
->
ok;
@ -836,6 +838,7 @@ foo() ->
fn parens() {
check(
r#"
//- expect_parse_errors
foo((ok), ()) ->
(ok),
().
@ -995,6 +998,7 @@ foo(fun() -> ok end) -> ok.
fn invalid_comprehension() {
check(
r#"
//- expect_parse_errors
foo(<<Byte || Byte <- List>>, [Byte || Byte <- List]]) -> ok.
"#,
expect![[r#"
@ -1392,6 +1396,7 @@ fn call_type_erlang_bif() {
fn record_type() {
check(
r#"
//- expect_parse_errors
-type foo1() :: #record{}.
-type foo2(B) :: #record{a :: integer(), b :: B}.
-type foo3() :: #record{a ::}.
@ -1529,6 +1534,7 @@ fn record_definition() {
fn simple_term() {
check(
r#"
//- expect_parse_errors
-foo(ok).
-missing_value().
"#,
@ -2693,6 +2699,7 @@ fn verbatim_binary_sigil_in_type() {
// Note: \~ gets replaced by ~ in the fixture parsing
check(
r#"
//- expect_parse_errors
-type foo() :: \~B"ab\"c\"\d").
-type bar() :: "hello").
"#,
@ -2723,6 +2730,7 @@ fn verbatim_binary_sigil_in_term() {
fn lowering_with_error_nodes() {
check(
r#"
//- expect_parse_errors
f(1a) -> ok begin 1 end.
"#,
expect![[r#"
@ -2989,8 +2997,10 @@ fn tree_print_record() {
#[test]
fn tree_print_attribute() {
// TODO: fix wild attribute parsing, T246546041, to remove expect_parse_errors
check_ast(
r#"
//- expect_parse_errors
-wild(foo, []).
-compile({inline, [foo/1]}).
-compile({a/a, 1/1}).

View file

@ -2340,7 +2340,7 @@ mod tests {
r#"
foo() ->
case 1 + 2 of
X when X andalso true; X <= 100, X >= 5 -> ok;
X when X andalso true; X =< 100, X >= 5 -> ok;
_ -> error
end.
"#,
@ -2381,7 +2381,7 @@ mod tests {
rhs
Expr<8>:Literal(Integer(100))
op
CompOp(Ord { ordering: Less, strict: true }),
CompOp(Ord { ordering: Less, strict: false }),
},
Expr<12>:Expr::BinaryOp {
lhs
@ -3114,6 +3114,7 @@ mod tests {
fn type_record() {
check(
r#"
//- expect_parse_errors
-type foo1() :: #record{}.
-type foo2(B) :: #record{a :: integer(), b :: B}.
-type foo3() :: #record{a ::}.
@ -3498,6 +3499,7 @@ mod tests {
fn top_level_forms() {
check(
r#"
//- expect_parse_errors
-module(main).
bug
-compile([export_all]).

View file

@ -339,7 +339,7 @@ pub enum ParentId {
#[derive(Debug)]
pub struct AnyCallBackCtx<'a> {
pub in_macro: Option<HirIdx>,
pub in_macro: Option<(HirIdx, Option<InFile<DefineId>>)>,
pub parents: &'a Vec<ParentId>,
pub item_id: AnyExprId,
pub item: AnyExpr,
@ -426,7 +426,7 @@ pub struct FoldCtx<'a, T> {
body_origin: BodyOrigin,
body: &'a FoldBody<'a>,
strategy: Strategy,
macro_stack: Vec<HirIdx>,
macro_stack: Vec<(HirIdx, Option<InFile<DefineId>>)>,
parents: Vec<ParentId>,
callback: AnyCallBack<'a, T>,
}
@ -594,7 +594,7 @@ impl<'a, T> FoldCtx<'a, T> {
.do_fold_pat(pat_id, initial)
}
fn in_macro(&self) -> Option<HirIdx> {
fn in_macro(&self) -> Option<(HirIdx, Option<InFile<DefineId>>)> {
self.macro_stack.first().copied()
}
@ -752,16 +752,19 @@ impl<'a, T> FoldCtx<'a, T> {
crate::Expr::MacroCall {
expansion,
args,
macro_def: _,
macro_def,
macro_name: _,
} => {
if self.strategy.macros == MacroStrategy::DoNotExpand {
self.do_fold_exprs(args, acc)
} else {
self.macro_stack.push(HirIdx {
body_origin: self.body_origin,
idx: AnyExprId::Expr(expr_id),
});
self.macro_stack.push((
HirIdx {
body_origin: self.body_origin,
idx: AnyExprId::Expr(expr_id),
},
*macro_def,
));
let e = self.do_fold_expr(*expansion, acc);
self.macro_stack.pop();
e
@ -950,16 +953,19 @@ impl<'a, T> FoldCtx<'a, T> {
crate::Pat::MacroCall {
expansion,
args,
macro_def: _,
macro_def,
macro_name: _,
} => {
if self.strategy.macros == MacroStrategy::DoNotExpand {
self.do_fold_exprs(args, acc)
} else {
self.macro_stack.push(HirIdx {
body_origin: self.body_origin,
idx: AnyExprId::Pat(pat_id),
});
self.macro_stack.push((
HirIdx {
body_origin: self.body_origin,
idx: AnyExprId::Pat(pat_id),
},
*macro_def,
));
let e = self.do_fold_pat(*expansion, acc);
self.macro_stack.pop();
e
@ -1165,16 +1171,19 @@ impl<'a, T> FoldCtx<'a, T> {
TypeExpr::MacroCall {
expansion,
args,
macro_def: _,
macro_def,
macro_name: _,
} => {
if self.strategy.macros == MacroStrategy::DoNotExpand {
self.do_fold_exprs(args, acc)
} else {
self.macro_stack.push(HirIdx {
body_origin: self.body_origin,
idx: AnyExprId::TypeExpr(type_expr_id),
});
self.macro_stack.push((
HirIdx {
body_origin: self.body_origin,
idx: AnyExprId::TypeExpr(type_expr_id),
},
*macro_def,
));
let e = self.do_fold_type_expr(*expansion, acc);
self.macro_stack.pop();
e
@ -2212,7 +2221,9 @@ bar() ->
#[test]
fn traverse_attribute() {
// TODO: fix wild attribute parsing, T246546041, to remove expect_parse_errors
let fixture_str = r#"
//- expect_parse_errors
-module(foo).
-wild(r1, {f1, f~oo}).
"#;

View file

@ -318,6 +318,7 @@ fn export() {
fn import() {
check(
r#"
//- expect_parse_errors
-import(, []).
-import(foo, []).
-import(foo, [foo/1]).

View file

@ -155,7 +155,7 @@ pub use name::MacroName;
pub use name::Name;
pub use name::NameArity;
pub use name::known;
// @fb-only
// @fb-only: pub use name::meta_only;
pub use sema::AtomDef;
pub use sema::CallDef;
pub use sema::DefinitionOrReference;
@ -232,6 +232,10 @@ impl HirIdx {
}
}
pub fn file_id(&self) -> FileId {
self.body_origin.file_id()
}
/// This function is used to print a representation of the HIR AST
/// corresponding to the given `HirIdx`. It is used for debugging
/// and testing.

View file

@ -10,7 +10,7 @@
//! See [`Name`].
// @fb-only
// @fb-only: pub mod meta_only;
use std::borrow::Cow;
use std::collections::HashSet;

View file

@ -102,7 +102,7 @@ use crate::resolver::Resolution;
use crate::resolver::Resolver;
mod find;
// @fb-only
// @fb-only: pub mod meta_only;
pub mod to_def;
pub struct ModuleIter(Arc<ModuleIndex>);
@ -1006,6 +1006,28 @@ impl Semantic<'_> {
// Folds end
// -----------------------------------------------------------------
pub fn bound_vars_by_function(
&self,
file_id: FileId,
) -> FxHashMap<FunctionClauseId, FxHashSet<PatId>> {
let bound_vars = self.bound_vars_in_pattern_diagnostic(file_id);
let mut bound_vars_by_function: FxHashMap<FunctionClauseId, FxHashSet<PatId>> =
FxHashMap::default();
bound_vars.iter().for_each(|(function_id, pat_id, _var)| {
bound_vars_by_function
.entry(function_id.value)
.and_modify(|vars| {
vars.insert(*pat_id);
})
.or_insert_with(|| {
let mut vars = FxHashSet::default();
vars.insert(*pat_id);
vars
});
});
bound_vars_by_function
}
pub fn bound_vars_in_pattern_diagnostic(
&self,
file_id: FileId,

View file

@ -42,7 +42,7 @@ use crate::macro_exp;
use crate::macro_exp::BuiltInMacro;
use crate::macro_exp::MacroExpCtx;
use crate::resolver::Resolver;
// @fb-only
// @fb-only: use crate::sema::meta_only;
pub trait ToDef: Clone {
type Def;
@ -567,7 +567,7 @@ pub fn resolve_call_target(
let fn_name: Name = sema.db.lookup_atom(body[*name].as_atom()?);
let mo =
None; // @oss-only
// @fb-only
// @fb-only: meta_only::resolve_handle_call_target(sema, arity, file_id, &module_name, &fn_name);
if let Some(r) = mo {
r
} else {
@ -885,12 +885,183 @@ fn add_dynamic_call_patterns(patterns: &mut FxHashMap<PatternKey, DynamicCallPat
);
}
// Lazy static initialization for the patterns map
/// Specifies what forms a module argument can take.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModuleArgType {
/// The argument must be a single module atom (e.g., `apply(Mod, Fun, Args)`)
Atom,
/// The argument must be a list of module atoms (e.g., some batch operations)
List,
/// The argument can be either a single module atom or a list of modules
/// (e.g., `meck:new(Mod | [Mod], Opts)`)
AtomOrList,
}
/// Pattern for matching module argument positions in function calls.
/// Used by rename operations to identify which argument contains a module name.
#[derive(Debug, Clone, Copy)]
pub struct ModuleArgPattern {
/// Index of the argument containing the module name (0-based)
pub index: usize,
/// The type of the module argument (atom, list, or either)
pub arg_type: ModuleArgType,
}
impl ModuleArgPattern {
/// Creates a pattern where the argument is a single module atom.
pub const fn atom(index: usize) -> Self {
Self {
index,
arg_type: ModuleArgType::Atom,
}
}
/// Creates a pattern where the argument is a list of module atoms.
pub const fn list(index: usize) -> Self {
Self {
index,
arg_type: ModuleArgType::List,
}
}
/// Creates a pattern where the argument can be either a single atom or a list.
pub const fn atom_or_list(index: usize) -> Self {
Self {
index,
arg_type: ModuleArgType::AtomOrList,
}
}
/// Returns true if this pattern accepts a single atom.
pub const fn accepts_atom(&self) -> bool {
matches!(
self.arg_type,
ModuleArgType::Atom | ModuleArgType::AtomOrList
)
}
/// Returns true if this pattern accepts a list of atoms.
pub const fn accepts_list(&self) -> bool {
matches!(
self.arg_type,
ModuleArgType::List | ModuleArgType::AtomOrList
)
}
}
fn add_module_argument_patterns(patterns: &mut FxHashMap<PatternKey, ModuleArgPattern>) {
// Each entry follows the format:
// (module, function, arity) -> ModuleArgPattern
//
// Where:
// module: Module name (Some("meck"), Some("application"), etc.)
// function: Function name as string literal (e.g., "new", "get_env")
// arity: Number of arguments this function pattern expects
// ModuleArgPattern: Contains the argument index and the expected type
//
// All indexes are 0-based.
// meck - mocking library
// meck:new/2 accepts either a single module atom or a list of modules
patterns.insert((Some("meck"), "called", 3), ModuleArgPattern::atom(0));
patterns.insert((Some("meck"), "called", 4), ModuleArgPattern::atom(0));
patterns.insert((Some("meck"), "capture", 5), ModuleArgPattern::atom(1));
patterns.insert((Some("meck"), "capture", 6), ModuleArgPattern::atom(1));
patterns.insert(
(Some("meck"), "delete", 3),
ModuleArgPattern::atom_or_list(0),
);
patterns.insert(
(Some("meck"), "delete", 4),
ModuleArgPattern::atom_or_list(0),
);
patterns.insert(
(Some("meck"), "expect", 3),
ModuleArgPattern::atom_or_list(0),
);
patterns.insert(
(Some("meck"), "expect", 4),
ModuleArgPattern::atom_or_list(0),
);
patterns.insert(
(Some("meck"), "expects", 2),
ModuleArgPattern::atom_or_list(0),
);
patterns.insert((Some("meck"), "history", 1), ModuleArgPattern::atom(0));
patterns.insert((Some("meck"), "history", 2), ModuleArgPattern::atom(0));
patterns.insert((Some("meck"), "loop", 4), ModuleArgPattern::atom_or_list(0));
patterns.insert((Some("meck"), "new", 1), ModuleArgPattern::atom_or_list(0));
patterns.insert((Some("meck"), "new", 2), ModuleArgPattern::atom_or_list(0));
patterns.insert((Some("meck"), "num_calls", 3), ModuleArgPattern::atom(0));
patterns.insert((Some("meck"), "num_calls", 4), ModuleArgPattern::atom(0));
patterns.insert(
(Some("meck"), "reset", 1),
ModuleArgPattern::atom_or_list(0),
);
patterns.insert(
(Some("meck"), "sequence", 4),
ModuleArgPattern::atom_or_list(0),
);
patterns.insert(
(Some("meck"), "unload", 1),
ModuleArgPattern::atom_or_list(0),
);
patterns.insert(
(Some("meck"), "validate", 1),
ModuleArgPattern::atom_or_list(0),
);
patterns.insert((Some("meck"), "wait", 4), ModuleArgPattern::atom(0));
patterns.insert((Some("meck"), "wait", 5), ModuleArgPattern::atom(1));
patterns.insert((Some("meck"), "wait", 6), ModuleArgPattern::atom(1));
// code module - module loading and management
// These functions from the Erlang stdlib take module() as their argument
patterns.insert((Some("code"), "load_file", 1), ModuleArgPattern::atom(0));
patterns.insert(
(Some("code"), "ensure_loaded", 1),
ModuleArgPattern::atom(0),
);
patterns.insert((Some("code"), "delete", 1), ModuleArgPattern::atom(0));
patterns.insert((Some("code"), "purge", 1), ModuleArgPattern::atom(0));
patterns.insert((Some("code"), "soft_purge", 1), ModuleArgPattern::atom(0));
patterns.insert((Some("code"), "is_loaded", 1), ModuleArgPattern::atom(0));
patterns.insert(
(Some("code"), "get_object_code", 1),
ModuleArgPattern::atom(0),
);
patterns.insert((Some("code"), "module_md5", 1), ModuleArgPattern::atom(0));
patterns.insert((Some("code"), "is_sticky", 1), ModuleArgPattern::atom(0));
}
// Lazy static initialization for the patterns maps
lazy_static! {
static ref DYNAMIC_CALL_PATTERNS: FxHashMap<PatternKey, DynamicCallPattern> = {
let mut patterns = FxHashMap::default();
add_dynamic_call_patterns(&mut patterns);
// @fb-only
// @fb-only: meta_only::add_dynamic_call_patterns(&mut patterns);
patterns
};
static ref MODULE_ARGUMENT_PATTERNS: FxHashMap<PatternKey, ModuleArgPattern> = {
let mut patterns = FxHashMap::default();
add_module_argument_patterns(&mut patterns);
// @fb-only: meta_only::add_module_argument_patterns(&mut patterns);
patterns
};
/// Combined patterns for module argument positions.
/// Merges dynamic call patterns (that have module_arg_index) with simple module argument patterns.
/// Used by rename operations where we only care about the module argument position.
static ref COMBINED_MODULE_ARG_PATTERNS: FxHashMap<PatternKey, ModuleArgPattern> = {
let mut patterns: FxHashMap<PatternKey, ModuleArgPattern> = FxHashMap::default();
// Add module_arg_index from dynamic call patterns (where present)
for (key, pattern) in DYNAMIC_CALL_PATTERNS.iter() {
if let Some(module_idx) = pattern.module_arg_index {
patterns.insert(*key, ModuleArgPattern::atom(module_idx));
}
}
// Add from simple module argument patterns
for (key, module_arg_pattern) in MODULE_ARGUMENT_PATTERNS.iter() {
patterns.insert(*key, *module_arg_pattern);
}
patterns
};
}
@ -899,6 +1070,10 @@ fn get_dynamic_call_patterns() -> &'static FxHashMap<PatternKey, DynamicCallPatt
&DYNAMIC_CALL_PATTERNS
}
pub fn get_module_arg_patterns() -> &'static FxHashMap<PatternKey, ModuleArgPattern> {
&COMBINED_MODULE_ARG_PATTERNS
}
fn look_for_dynamic_call(
sema: &Semantic,
file_id: FileId,

View file

@ -17,7 +17,7 @@ use elp_syntax::TextRange;
use fxhash::FxHashMap;
use fxhash::FxHashSet;
// @fb-only
// @fb-only: use crate::meta_only;
use crate::runnables::Runnable;
use crate::runnables::runnables;
@ -57,11 +57,11 @@ pub struct Link {
}
#[rustfmt::skip]
// @fb-only
// @fb-only: pub(crate) fn annotations(db: &RootDatabase, file_id: FileId) -> Vec<Annotation> {
pub(crate) fn annotations(_db: &RootDatabase, _file_id: FileId) -> Vec<Annotation> { // @oss-only
// @fb-only
// @fb-only: let mut annotations = Vec::default();
let annotations = Vec::default(); // @oss-only
// @fb-only
// @fb-only: meta_only::annotations(db, file_id, &mut annotations);
annotations
}

View file

@ -573,8 +573,8 @@ pub(crate) fn find_call_in_function<CallCtx, MakeCtx, Res>(
};
if let Some(extra) = check_call(context) {
// Got one.
let call_expr_id = if let Some(expr_id) = ctx.in_macro {
expr_id.idx
let call_expr_id = if let Some((hir_idx, _macro_def)) = ctx.in_macro {
hir_idx.idx
} else {
ctx.item_id
};

View file

@ -50,6 +50,7 @@ use elp_ide_db::text_edit::TextEdit;
use elp_ide_ssr::Match;
use elp_ide_ssr::SsrSearchScope;
use elp_ide_ssr::match_pattern;
use elp_project_model::AppName;
use elp_syntax::NodeOrToken;
use elp_syntax::Parse;
use elp_syntax::SourceFile;
@ -96,13 +97,13 @@ mod application_env;
mod atoms_exhaustion;
mod binary_string_to_sigil;
mod boolean_precedence;
mod bound_variable;
mod could_be_a_string_literal;
mod cross_node_eval;
mod debugging_function;
mod dependent_header;
mod deprecated_function;
mod duplicate_module;
mod edoc;
mod effect_free_statement;
mod equality_check_with_unnecessary_operator;
mod eqwalizer_assists;
@ -117,7 +118,7 @@ mod macro_precedence_suprise;
mod map_find_to_syntax;
mod map_insertion_to_syntax;
mod meck;
// @fb-only
// @fb-only: mod meta_only;
mod missing_compile_warn_missing_spec;
mod missing_module;
mod missing_separator;
@ -131,6 +132,7 @@ mod no_garbage_collect;
mod no_nowarn_suppressions;
mod no_size;
mod nonstandard_integer_formatting;
mod old_edoc_syntax;
mod record_tuple_match;
mod redundant_assignment;
mod replace_call;
@ -549,12 +551,37 @@ pub(crate) trait Linter {
}
}
fn should_process_app(
app_name: &Option<AppName>,
config: &DiagnosticsConfig,
diagnostic_code: &DiagnosticCode,
) -> bool {
let app = match app_name {
Some(app) => app.to_string(),
None => return true,
};
if let Some(lint_config) = config.lint_config.as_ref()
&& let Some(linter_config) = lint_config.linters.get(diagnostic_code)
&& let Some(ref excluded) = linter_config.exclude_apps
&& excluded.contains(&app)
{
return false;
}
true
}
fn should_run(
linter: &dyn Linter,
config: &DiagnosticsConfig,
app_name: &Option<AppName>,
is_generated: bool,
is_test: bool,
) -> bool {
if !should_process_app(app_name, config, &linter.id()) {
return false;
}
let is_enabled = if let Some(lint_config) = config.lint_config.as_ref() {
lint_config
.get_is_enabled_override(&linter.id())
@ -865,6 +892,7 @@ pub(crate) trait GenericLinter: Linter {
fn fixes(
&self,
_context: &Self::Context,
_range: TextRange,
_sema: &Semantic,
_file_id: FileId,
) -> Option<Vec<Assist>> {
@ -898,7 +926,7 @@ impl<T: GenericLinter> GenericDiagnostics for T {
if let Some(matches) = self.matches(sema, file_id) {
for matched in matches {
let message = self.match_description(&matched.context);
let fixes = self.fixes(&matched.context, sema, file_id);
let fixes = self.fixes(&matched.context, matched.range, sema, file_id);
let tag = self.tag(&matched.context);
let mut d = Diagnostic::new(self.id(), message, matched.range)
.with_fixes(fixes)
@ -1216,6 +1244,16 @@ impl LintConfig {
self.linters.get(diagnostic_code)?.experimental
}
/// Get the exclude_apps override for a linter based on its diagnostic code.
pub fn get_exclude_apps_override(
&self,
diagnostic_code: &DiagnosticCode,
) -> Option<Vec<String>> {
self.linters
.get(diagnostic_code)
.and_then(|c| c.exclude_apps.clone())
}
pub fn get_function_call_linter_config(
&self,
diagnostic_code: &DiagnosticCode,
@ -1339,6 +1377,7 @@ pub struct LinterConfig {
pub include_tests: Option<bool>,
pub include_generated: Option<bool>,
pub experimental: Option<bool>,
pub exclude_apps: Option<Vec<String>>,
#[serde(flatten)]
pub config: Option<LinterTraitConfig>,
}
@ -1359,6 +1398,7 @@ impl LinterConfig {
include_tests: other.include_tests.or(self.include_tests),
include_generated: other.include_generated.or(self.include_generated),
experimental: other.experimental.or(self.experimental),
exclude_apps: other.exclude_apps.or(self.exclude_apps),
config: merged_config,
}
}
@ -1524,7 +1564,7 @@ pub fn native_diagnostics(
config
.lints_from_config
.get_diagnostics(&mut res, &sema, file_id);
// @fb-only
// @fb-only: meta_only::diagnostics(&mut res, &sema, file_id, file_kind, config);
syntax_diagnostics(&sema, &parse, &mut res, file_id);
diagnostics_from_descriptors(
&mut res,
@ -1553,6 +1593,7 @@ pub fn native_diagnostics(
} else {
FxHashMap::default()
};
let app_name = db.file_app_name(file_id);
let metadata = db.elp_metadata(file_id);
// TODO: can we ever disable DiagnosticCode::SyntaxError?
// In which case we must check labeled_syntax_errors
@ -1561,6 +1602,7 @@ pub fn native_diagnostics(
&& (config.experimental && d.has_category(Category::Experimental)
|| !d.has_category(Category::Experimental))
&& !d.should_be_suppressed(&metadata, config)
&& should_process_app(&app_name, config, &d.code)
});
LabeledDiagnostics {
@ -1611,20 +1653,20 @@ pub fn diagnostics_from_descriptors(
.db
.is_test_suite_or_test_helper(file_id)
.unwrap_or(false);
let app_name = sema.db.file_app_name(file_id);
descriptors.iter().for_each(|descriptor| {
if descriptor.conditions.enabled(config, is_generated, is_test) {
if descriptor.conditions.default_disabled {
// Filter the returned diagnostics to ensure they are
// enabled
let mut diags: Vec<Diagnostic> = Vec::default();
(descriptor.checker)(&mut diags, sema, file_id, file_kind);
for diag in diags {
if config.enabled.contains(&diag.code) {
res.push(diag);
}
let mut diags: Vec<Diagnostic> = Vec::default();
(descriptor.checker)(&mut diags, sema, file_id, file_kind);
for diag in diags {
// Check if this diagnostic is enabled (for default_disabled descriptors)
// and if the app is not excluded for this diagnostic code
let is_enabled =
!descriptor.conditions.default_disabled || config.enabled.contains(&diag.code);
let app_allowed = should_process_app(&app_name, config, &diag.code);
if is_enabled && app_allowed {
res.push(diag);
}
} else {
(descriptor.checker)(res, sema, file_id, file_kind);
}
}
});
@ -1681,11 +1723,12 @@ const GENERIC_LINTERS: &[&dyn GenericDiagnostics] = &[
&duplicate_module::LINTER,
&no_nowarn_suppressions::LINTER,
&macro_precedence_suprise::LINTER,
&edoc::LINTER,
&old_edoc_syntax::LINTER,
&missing_module::LINTER,
&unused_include::LINTER,
&misspelled_attribute::LINTER,
&boolean_precedence::LINTER,
&bound_variable::LINTER,
];
/// Unified registry for all types of linters
@ -1714,7 +1757,7 @@ pub(crate) fn linters() -> Vec<DiagnosticLinter> {
);
// Add meta-only linters
// @fb-only
// @fb-only: all_linters.extend(meta_only::linters());
all_linters
}
@ -1731,11 +1774,12 @@ fn diagnostics_from_linters(
.db
.is_test_suite_or_test_helper(file_id)
.unwrap_or(false);
let app_name = sema.db.file_app_name(file_id);
for l in linters {
let linter = l.as_linter();
if linter.should_process_file_id(sema, file_id)
&& should_run(linter, config, is_generated, is_test)
&& should_run(linter, config, &app_name, is_generated, is_test)
{
let severity = if let Some(lint_config) = config.lint_config.as_ref() {
lint_config
@ -2297,11 +2341,14 @@ pub fn erlang_service_diagnostics(
diags
};
let app_name = db.file_app_name(file_id);
let metadata = db.elp_metadata(file_id);
let diags = diags
.into_iter()
.filter(|(_file_id, d)| {
!d.should_be_suppressed(&metadata, config) && !config.disabled.contains(&d.code)
!d.should_be_suppressed(&metadata, config)
&& !config.disabled.contains(&d.code)
&& should_process_app(&app_name, config, &d.code)
})
.map(|(file_id, d)| {
(
@ -2592,7 +2639,7 @@ pub fn ct_diagnostics(
CommonTestInfo::Result { all, groups } => {
let testcases = common_test::runnable_names(&sema, file_id, all, groups).ok();
common_test::unreachable_test(&mut res, &sema, file_id, &testcases);
// @fb-only
// @fb-only: meta_only::ct_diagnostics(&mut res, &sema, file_id, testcases);
}
CommonTestInfo::EvalError(_error) => {
// The error currently does not contain anything useful, so we ignore it
@ -3189,6 +3236,7 @@ mod tests {
fn syntax_error() {
check_diagnostics(
r#"
//- expect_parse_errors
-module(main).
foo() -> XX 3.0.
%% ^^ error: P1711: Syntax Error
@ -3366,6 +3414,7 @@ main(X) ->
#[test]
fn label_syntax_error_not_function() {
let fixture_str = r#"
//- expect_parse_errors
-module(main).
-record(person, {(name + XXX)}).
%% ^^^^^^^ error: P1711: Syntax Error
@ -3381,7 +3430,7 @@ main(X) ->
expect![[r#"
Some(
Range(
24..56,
5..45,
),
)
"#]]
@ -3540,6 +3589,7 @@ main(X) ->
config,
&extra_diags,
r#"
//- expect_parse_errors
-module(main).
-export([foo/0,bar/0]).
@ -3560,7 +3610,7 @@ main(X) ->
#[test]
fn group_related_diagnostics_elp_only() {
// Demonstrate that ELP does not pick up a syntax error in the
// spec, same code as in test_projects/diagnostics/app_a/src/syntax.erl
// spec, same code as in test/test_projects/diagnostics/app_a/src/syntax.erl
check_diagnostics(
r#"
-module(main).
@ -3577,6 +3627,7 @@ main(X) ->
check_diagnostics(
r#"
//- erlang_service
//- expect_parse_errors
//- /src/a_mod.erl app:app_a
-module(a_mod).
-export([foo/0]).
@ -3595,6 +3646,7 @@ main(X) ->
check_diagnostics(
r#"
//- erlang_service
//- expect_parse_errors
//- native
//- /src/a_mod.erl app:app_a
-module(a_mod).
@ -3673,6 +3725,7 @@ main(X) ->
fn test_nested_syntax_errors() {
check_diagnostics(
r#"
//- expect_parse_errors
-module(main).
run() ->
ExitCode =
@ -3986,6 +4039,7 @@ main(X) ->
include_tests: None,
include_generated: None,
experimental: None,
exclude_apps: None,
config: None,
},
);
@ -4028,6 +4082,7 @@ main(X) ->
include_tests: Some(true),
include_generated: None,
experimental: None,
exclude_apps: None,
config: None,
},
);
@ -4069,6 +4124,7 @@ main(X) ->
include_tests: None,
include_generated: Some(true),
experimental: None,
exclude_apps: None,
config: None,
},
);
@ -4111,6 +4167,7 @@ main(X) ->
include_tests: None,
include_generated: None,
experimental: Some(true),
exclude_apps: None,
config: None,
},
);
@ -4155,6 +4212,7 @@ main(X) ->
include_tests: None,
include_generated: None,
experimental: None,
exclude_apps: None,
config: None,
},
);
@ -4185,6 +4243,47 @@ main(X) ->
);
}
#[test]
fn test_linter_exclude_apps_override() {
let mut lint_config = LintConfig::default();
lint_config.linters.insert(
DiagnosticCode::NoGarbageCollect,
LinterConfig {
is_enabled: Some(false),
severity: None,
include_tests: None,
include_generated: None,
experimental: None,
exclude_apps: Some(vec!["my_app".to_string()]),
config: None,
},
);
let config = DiagnosticsConfig::default()
.configure_diagnostics(
&lint_config,
&Some("no_garbage_collect".to_string()),
&None,
FallBackToAll::No,
)
.unwrap();
check_diagnostics_with_config(
config,
r#"
//- /src/main.erl app:my_app
-module(main).
-export([warning/0]).
warning() ->
erlang:garbage_collect().
//- /opt/lib/stdlib-3.17/src/erlang.erl otp_app:/opt/lib/stdlib-3.17
-module(erlang).
-export([garbage_collect/0]).
garbage_collect() -> ok.
"#,
);
}
#[test]
fn no_unused_macro_in_macro_rhs_for_function_name() {
let config = DiagnosticsConfig::default()
@ -4232,6 +4331,7 @@ main(X) ->
include_tests: None,
include_generated: None,
experimental: None,
exclude_apps: None,
config: Some(LinterTraitConfig::FunctionCallLinterConfig(
FunctionCallLinterConfig {
include: Some(vec![FunctionMatch::mf("mod_a", "func_a")]),
@ -4264,6 +4364,7 @@ main(X) ->
include_tests: Some(true),
include_generated: None,
experimental: None,
exclude_apps: None,
config: Some(LinterTraitConfig::FunctionCallLinterConfig(
FunctionCallLinterConfig {
include: Some(vec![FunctionMatch::mf("mod_b", "func_b")]),
@ -4281,6 +4382,7 @@ main(X) ->
include_tests: None,
include_generated: Some(true),
experimental: None,
exclude_apps: None,
config: None,
},
);

View file

@ -28,7 +28,7 @@ use crate::codemod_helpers::CheckCallCtx;
use crate::codemod_helpers::FunctionMatch;
use crate::codemod_helpers::MatchCtx;
use crate::codemod_helpers::find_call_in_function;
// @fb-only
// @fb-only: use crate::diagnostics;
use crate::diagnostics::DiagnosticCode;
use crate::diagnostics::Severity;
@ -36,7 +36,7 @@ pub(crate) static DESCRIPTOR: DiagnosticDescriptor = DiagnosticDescriptor {
conditions: DiagnosticConditions {
experimental: false,
include_generated: true,
include_tests: true,
include_tests: false,
default_disabled: false,
},
checker: &|diags, sema, file_id, _ext| {
@ -108,7 +108,7 @@ fn check_function(diags: &mut Vec<Diagnostic>, sema: &Semantic, def: &FunctionDe
vec![2, 3],
BadEnvCallAction::AppArg(0),
),
// @fb-only
// @fb-only: diagnostics::meta_only::application_env_bad_matches(),
]
.into_iter()
.flatten()

View file

@ -13,7 +13,7 @@ use hir::Semantic;
use crate::FunctionMatch;
use crate::codemod_helpers::CheckCallCtx;
// @fb-only
// @fb-only: use crate::diagnostics;
use crate::diagnostics::DiagnosticCode;
use crate::diagnostics::FunctionCallLinter;
use crate::diagnostics::Linter;
@ -35,9 +35,9 @@ impl Linter for AtomsExhaustionLinter {
false
}
#[rustfmt::skip]
// @fb-only
// @fb-only: fn should_process_file_id(&self, sema: &Semantic, file_id: FileId) -> bool {
fn should_process_file_id(&self, _sema: &Semantic, _file_id: FileId) -> bool { // @oss-only
// @fb-only
// @fb-only: diagnostics::meta_only::is_relevant_file(sema.db.upcast(), file_id)
true // @oss-only
}
}
@ -56,16 +56,16 @@ impl FunctionCallLinter for AtomsExhaustionLinter {
// FunctionMatch::mfa("erlang", "binary_to_term", 2),
]
.into_iter()
// @fb-only
// @fb-only: .chain(diagnostics::meta_only::atoms_exhaustion_matches().into_iter())
.collect::<Vec<_>>()
]
}
fn check_match(&self, context: &CheckCallCtx<'_, ()>) -> Option<Self::Context> {
#[rustfmt::skip]
// @fb-only
// @fb-only
// @fb-only
// @fb-only: let sema = context.in_clause.sema;
// @fb-only: let is_safe =
// @fb-only: diagnostics::meta_only::atoms_exhaustion_is_safe(sema, context.in_clause, context.parents);
let is_safe = false; // @oss-only
if !is_safe {
match context.args.as_slice() {

View file

@ -66,7 +66,6 @@ impl Linter for BooleanPrecedenceLinter {
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Context {
range: TextRange,
preceding_ws_range: TextRange,
op: Op,
lhs_complex: bool,
@ -101,6 +100,7 @@ impl GenericLinter for BooleanPrecedenceLinter {
fn fixes(
&self,
context: &Self::Context,
range: TextRange,
_sema: &Semantic,
file_id: FileId,
) -> Option<Vec<Assist>> {
@ -109,36 +109,36 @@ impl GenericLinter for BooleanPrecedenceLinter {
// Add "replace with preferred operator" fix
let assist_message = format!("Replace '{}' with '{}'", context.op, context.op.preferred());
let edit = TextEdit::replace(
context.op.range(context.range, context.preceding_ws_range),
context.op.range(range, context.preceding_ws_range),
context.op.preferred().to_string(),
);
fixes.push(fix(
"replace_boolean_operator",
&assist_message,
SourceChange::from_text_edit(file_id, edit),
context.range,
range,
));
// Add "add parens" fixes if applicable
if context.lhs_complex {
fixes.push(parens_fix("LHS", file_id, context));
fixes.push(parens_fix("LHS", file_id, context, range));
}
if context.rhs_complex {
fixes.push(parens_fix("RHS", file_id, context));
fixes.push(parens_fix("RHS", file_id, context, range));
}
Some(fixes)
}
}
fn parens_fix(side: &str, file_id: FileId, context: &Context) -> Assist {
fn parens_fix(side: &str, file_id: FileId, context: &Context, range: TextRange) -> Assist {
let assist_message = format!("Add parens to {side}");
let edit = add_parens_edit(&context.add_parens_range);
fix(
"replace_boolean_operator_add_parens",
&assist_message,
SourceChange::from_text_edit(file_id, edit),
context.range,
range,
)
}
@ -231,7 +231,6 @@ fn collect_match(
matches.push(GenericLinterMatchContext {
range,
context: Context {
range,
preceding_ws_range,
op: binop,
lhs_complex,

View file

@ -0,0 +1,178 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is dual-licensed under either the MIT license found in the
* LICENSE-MIT file in the root directory of this source tree or the Apache
* License, Version 2.0 found in the LICENSE-APACHE file in the root directory
* of this source tree. You may select, at your option, one of the
* above-listed licenses.
*/
// Diagnostic: bound_variable
//
// Return a warning if the LHS of a match already contains a bound variable.
//
use elp_ide_db::elp_base_db::FileId;
use hir::AnyExpr;
use hir::Expr;
use hir::Semantic;
use hir::Strategy;
use hir::fold::MacroStrategy;
use hir::fold::ParenStrategy;
use crate::diagnostics::DiagnosticCode;
use crate::diagnostics::GenericLinter;
use crate::diagnostics::GenericLinterMatchContext;
use crate::diagnostics::Linter;
pub(crate) struct BoundVariableLinter;
impl Linter for BoundVariableLinter {
fn id(&self) -> DiagnosticCode {
DiagnosticCode::BoundVarInLhs
}
fn description(&self) -> &'static str {
"Match on a bound variable"
}
}
impl GenericLinter for BoundVariableLinter {
type Context = ();
fn matches(
&self,
sema: &Semantic,
file_id: FileId,
) -> Option<Vec<GenericLinterMatchContext<Self::Context>>> {
let bound_vars_by_function = sema.bound_vars_by_function(file_id);
let mut res = Vec::new();
sema.def_map(file_id)
.get_function_clauses()
.for_each(|(_, def)| {
if def.file.file_id == file_id
&& let Some(bound_vars) = bound_vars_by_function.get(&def.function_clause_id)
{
let in_clause = def.in_clause(sema, def);
in_clause.fold_clause(
Strategy {
macros: MacroStrategy::ExpandButIncludeMacroCall,
parens: ParenStrategy::InvisibleParens,
},
(),
&mut |acc, ctx| {
if let AnyExpr::Expr(Expr::Match { lhs, rhs: _ }) = ctx.item
&& bound_vars.contains(&lhs)
&& let Some(range) = in_clause.range_for_pat(lhs)
&& range.file_id == def.file.file_id
&& ctx.in_macro.is_none()
{
res.push(GenericLinterMatchContext {
range: range.range,
context: (),
});
};
acc
},
);
}
});
Some(res)
}
}
pub static LINTER: BoundVariableLinter = BoundVariableLinter;
#[cfg(test)]
mod test {
use elp_ide_db::DiagnosticCode;
use expect_test::Expect;
use crate::diagnostics::DiagnosticsConfig;
use crate::tests::check_diagnostics_with_config;
use crate::tests::check_fix_with_config;
#[track_caller]
pub(crate) fn check_diagnostics(fixture: &str) {
let config = DiagnosticsConfig::default().disable(DiagnosticCode::UndefinedFunction);
check_diagnostics_with_config(config, fixture)
}
#[track_caller]
pub(crate) fn check_fix(fixture_before: &str, fixture_after: Expect) {
let config = DiagnosticsConfig::default().disable(DiagnosticCode::UndefinedFunction);
check_fix_with_config(config, fixture_before, fixture_after)
}
#[test]
fn bound_variable() {
check_diagnostics(
r#"
//- /src/bound.erl
-module(bound).
foo() ->
AA = bar(),
AA = bar().
%% ^^ 💡 warning: W0060: Match on a bound variable
"#,
)
}
#[test]
fn bound_variable_not_reported_in_case() {
check_diagnostics(
r#"
//- /src/bound.erl
-module(bound).
foo(Val) ->
case Val of
undefined -> ok;
Val when is_list(Val) -> ok
end.
"#,
)
}
#[test]
fn bound_variable_not_reported_in_macro() {
check_diagnostics(
r#"
//- /src/bound.erl
-module(bound).
-include("inc.hrl").
foo(Val) ->
?A_MACRO(Val).
//- /src/inc.hrl
-define(A_MACRO(X), X=X).
"#,
)
}
#[test]
fn bound_variable_ignore_fix() {
check_fix(
r#"
//- /src/bound.erl
-module(bound).
foo() ->
AA = bar(),
A~A = bar().
"#,
expect_test::expect![[r#"
-module(bound).
foo() ->
AA = bar(),
% elp:ignore W0060 (bound_var_in_lhs)
AA = bar().
"#]],
)
}
}

View file

@ -22,7 +22,7 @@ use crate::diagnostics::DiagnosticCode;
use crate::diagnostics::FunctionCallLinter;
use crate::diagnostics::Linter;
use crate::diagnostics::Severity;
// @fb-only
// @fb-only: use crate::diagnostics::meta_only;
use crate::lazy_function_matches;
pub(crate) struct NoDebuggingFunctionLinter;
@ -52,7 +52,7 @@ impl FunctionCallLinter for NoDebuggingFunctionLinter {
lazy_function_matches![
vec![FunctionMatch::m("redbug")]
.into_iter()
// @fb-only
// @fb-only: .chain(meta_only::debugging_function_matches().into_iter())
.collect::<Vec<_>>()
]
}

View file

@ -41,7 +41,7 @@ use super::DiagnosticDescriptor;
use super::Severity;
use crate::codemod_helpers::FunctionMatch;
use crate::codemod_helpers::FunctionMatcher;
// @fb-only
// @fb-only: use crate::diagnostics;
use crate::fix;
pub(crate) static DESCRIPTOR: DiagnosticDescriptor = DiagnosticDescriptor {
@ -88,7 +88,7 @@ fn deprecated_function(diagnostics: &mut Vec<Diagnostic>, sema: &Semantic, file_
lazy_static! {
static ref DEPRECATED_FUNCTIONS: Vec<(FunctionMatch, DeprecationDetails)> = {
let matches: Vec<Vec<(FunctionMatch, DeprecationDetails)>> = vec![
// @fb-only
// @fb-only: diagnostics::meta_only::deprecated_function_matches(),
];
matches.into_iter()
.flatten()
@ -134,8 +134,8 @@ fn check_function(
);
let details = match_result.map(|(_match, details)| details.clone());
if target_def.deprecated || match_result.is_some() {
let expr_id = if let Some(expr_id) = ctx.in_macro {
expr_id.idx
let expr_id = if let Some((hir_idx, _macro_def)) = ctx.in_macro {
hir_idx.idx
} else {
ctx.item_id
};

View file

@ -35,9 +35,7 @@ use crate::diagnostics::Linter;
use crate::fix;
#[derive(Debug, Default, Clone, PartialEq)]
pub(crate) struct MacroPrecedenceContext {
range: TextRange,
}
pub(crate) struct MacroPrecedenceContext;
pub(crate) struct MacroPrecedenceSupriseLinter;
@ -96,10 +94,9 @@ impl GenericLinter for MacroPrecedenceSupriseLinter {
{
let range = ast.range();
if range.file_id == file_id {
let context = MacroPrecedenceContext { range: range.range };
res.push(GenericLinterMatchContext {
range: range.range,
context,
context: MacroPrecedenceContext,
});
}
}
@ -113,16 +110,17 @@ impl GenericLinter for MacroPrecedenceSupriseLinter {
fn fixes(
&self,
context: &Self::Context,
_context: &Self::Context,
range: TextRange,
_sema: &Semantic,
file_id: FileId,
) -> Option<Vec<Assist>> {
let edit = add_parens_edit(&context.range);
let edit = add_parens_edit(&range);
let fix = fix(
"macro_precedence_add_parens",
"Add parens to macro call",
SourceChange::from_text_edit(file_id, edit),
context.range,
range,
);
Some(vec![fix])
}

View file

@ -75,7 +75,6 @@ impl Linter for MissingCompileWarnMissingSpec {
pub struct Context {
found: Found,
compile_option_id: Option<CompileOptionId>,
target_range: TextRange,
}
impl GenericLinter for MissingCompileWarnMissingSpec {
@ -94,7 +93,6 @@ impl GenericLinter for MissingCompileWarnMissingSpec {
context: Context {
found: Found::No,
compile_option_id: None,
target_range: DIAGNOSTIC_WHOLE_FILE_RANGE,
},
});
}
@ -149,7 +147,6 @@ impl GenericLinter for MissingCompileWarnMissingSpec {
context: Context {
found: what.0,
compile_option_id: what.1,
target_range: range,
},
});
}
@ -160,6 +157,7 @@ impl GenericLinter for MissingCompileWarnMissingSpec {
fn fixes(
&self,
context: &Self::Context,
range: TextRange,
sema: &Semantic,
file_id: FileId,
) -> Option<Vec<elp_ide_assists::Assist>> {
@ -184,7 +182,7 @@ impl GenericLinter for MissingCompileWarnMissingSpec {
"add_warn_missing_spec_all",
"Add compile option 'warn_missing_spec_all'",
edit,
context.target_range,
range,
)])
}
}

View file

@ -55,7 +55,6 @@ impl Linter for MisspelledAttributeLinter {
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Context {
range: TextRange,
attr_name: String,
suggested_rename: String,
}
@ -88,7 +87,6 @@ impl GenericLinter for MisspelledAttributeLinter {
res.push(GenericLinterMatchContext {
range: attr_name_range,
context: Context {
range: attr_name_range,
attr_name: attr.name.to_string(),
suggested_rename: suggested_rename.to_string(),
},
@ -110,16 +108,17 @@ impl GenericLinter for MisspelledAttributeLinter {
fn fixes(
&self,
context: &Self::Context,
range: TextRange,
_sema: &Semantic,
file_id: FileId,
) -> Option<Vec<Assist>> {
let edit = TextEdit::replace(context.range, context.suggested_rename.clone());
let edit = TextEdit::replace(range, context.suggested_rename.clone());
let msg = format!("Change to '{}'", context.suggested_rename);
Some(vec![fix(
"fix_misspelled_attribute",
&msg,
SourceChange::from_text_edit(file_id, edit),
context.range,
range,
)])
}
}

View file

@ -27,12 +27,8 @@
//
use elp_ide_db::elp_base_db::FileId;
use fxhash::FxHashMap;
use fxhash::FxHashSet;
use hir::AnyExpr;
use hir::Expr;
use hir::FunctionClauseId;
use hir::PatId;
use hir::Semantic;
use hir::Strategy;
use hir::fold::MacroStrategy;
@ -60,21 +56,7 @@ fn mutable_variable_bug(
sema: &Semantic,
file_id: FileId,
) -> Option<()> {
let mut bound_vars_by_function: FxHashMap<FunctionClauseId, FxHashSet<&PatId>> =
FxHashMap::default();
let bound_vars = sema.bound_vars_in_pattern_diagnostic(file_id);
bound_vars.iter().for_each(|(function_id, pat_id, _var)| {
bound_vars_by_function
.entry(function_id.value)
.and_modify(|vars| {
vars.insert(pat_id);
})
.or_insert_with(|| {
let mut vars = FxHashSet::default();
vars.insert(pat_id);
vars
});
});
let bound_vars_by_function = sema.bound_vars_by_function(file_id);
sema.def_map(file_id)
.get_function_clauses()
.for_each(|(_, def)| {

View file

@ -29,6 +29,9 @@ impl Linter for NoErrorLoggerLinter {
fn severity(&self, _sema: &Semantic, _file_id: FileId) -> Severity {
Severity::Error
}
fn should_process_test_files(&self) -> bool {
false
}
}
impl FunctionCallLinter for NoErrorLoggerLinter {

View file

@ -8,7 +8,7 @@
* above-listed licenses.
*/
// Diagnostic: edoc
// Diagnostic: old_edoc_syntax
use elp_ide_assists::Assist;
use elp_ide_assists::helpers;
@ -31,11 +31,10 @@ use super::DiagnosticCode;
use super::GenericLinter;
use super::GenericLinterMatchContext;
use super::Linter;
use super::Severity;
pub(crate) struct EdocLinter;
pub(crate) struct OldEdocSyntaxLinter;
impl Linter for EdocLinter {
impl Linter for OldEdocSyntaxLinter {
fn id(&self) -> DiagnosticCode {
DiagnosticCode::OldEdocSyntax
}
@ -44,11 +43,8 @@ impl Linter for EdocLinter {
"EDoc style comments are deprecated. Please use Markdown instead."
}
fn severity(&self, sema: &Semantic, file_id: FileId) -> Severity {
match sema.db.is_test_suite_or_test_helper(file_id) {
Some(true) => Severity::WeakWarning,
_ => Severity::Warning,
}
fn should_process_test_files(&self) -> bool {
false
}
}
@ -56,10 +52,9 @@ impl Linter for EdocLinter {
pub struct Context {
header_ptr: Option<InFileAstPtr<ast::Form>>,
doc_start: TextSize,
range: TextRange,
}
impl GenericLinter for EdocLinter {
impl GenericLinter for OldEdocSyntaxLinter {
type Context = Context;
fn matches(
@ -77,7 +72,6 @@ impl GenericLinter for EdocLinter {
context: Context {
header_ptr: Some(*header_ptr),
doc_start,
range: doc.range,
},
});
}
@ -88,7 +82,6 @@ impl GenericLinter for EdocLinter {
context: Context {
header_ptr: Some(*header_ptr),
doc_start,
range: equiv.range,
},
});
}
@ -99,7 +92,6 @@ impl GenericLinter for EdocLinter {
context: Context {
header_ptr: Some(*header_ptr),
doc_start,
range: deprecated.range,
},
});
}
@ -111,7 +103,6 @@ impl GenericLinter for EdocLinter {
context: Context {
header_ptr: Some(*header_ptr),
doc_start,
range: hidden.range,
},
});
}
@ -123,6 +114,7 @@ impl GenericLinter for EdocLinter {
fn fixes(
&self,
context: &Self::Context,
range: TextRange,
sema: &Semantic,
file_id: FileId,
) -> Option<Vec<Assist>> {
@ -134,12 +126,12 @@ impl GenericLinter for EdocLinter {
file_id,
header,
context.doc_start,
context.range,
range,
)])
}
}
pub static LINTER: EdocLinter = EdocLinter;
pub static LINTER: OldEdocSyntaxLinter = OldEdocSyntaxLinter;
fn old_edoc_syntax_fix(
sema: &Semantic,
@ -302,22 +294,6 @@ mod tests {
)
}
#[test]
fn test_function_doc_in_test_file() {
check_diagnostics(
r#"
//- /test/main_SUITE.erl extra:test
-module(main_SUITE).
%% @doc This is the main function documentation.
%% ^^^^ 💡 weak: W0038: EDoc style comments are deprecated. Please use Markdown instead.
main() ->
dep().
dep() -> ok.
"#,
)
}
#[test]
fn test_function_doc_different_arities() {
check_diagnostics(

View file

@ -43,6 +43,13 @@ impl Linter for UndefinedFunctionLinter {
fn should_process_generated_files(&self) -> bool {
true
}
// Ideally, we would like to report undefined functions in all files, but
// there are too many false positives in test files to do so.
// This is often due to mocked modules and test suite cleverness.
// We can revisit this decision in the future. See T249044930.
fn should_process_test_files(&self) -> bool {
false
}
}
impl FunctionCallLinter for UndefinedFunctionLinter {

View file

@ -48,9 +48,7 @@ impl Linter for UndocumentedModuleLinter {
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Context {
module_name_range: TextRange,
}
pub struct Context;
impl GenericLinter for UndocumentedModuleLinter {
type Context = Context;
@ -71,16 +69,21 @@ impl GenericLinter for UndocumentedModuleLinter {
if module_has_no_docs {
let module_name = module_attribute.name()?;
let module_name_range = module_name.syntax().text_range();
let context = Context { module_name_range };
res.push(GenericLinterMatchContext {
range: module_name_range,
context,
context: Context,
});
}
Some(res)
}
fn fixes(&self, context: &Context, sema: &Semantic, file_id: FileId) -> Option<Vec<Assist>> {
fn fixes(
&self,
_context: &Context,
range: TextRange,
sema: &Semantic,
file_id: FileId,
) -> Option<Vec<Assist>> {
let insert_offset = helpers::moduledoc_insert_offset(sema, file_id)?;
let mut builder = SourceChangeBuilder::new(file_id);
builder.insert(insert_offset, "-moduledoc false.\n");
@ -89,7 +92,7 @@ impl GenericLinter for UndocumentedModuleLinter {
"add_moduledoc_false",
"Add `-moduledoc false.` attribute",
source_change,
context.module_name_range,
range,
);
Some(vec![fix])
}

View file

@ -27,7 +27,7 @@ use crate::codemod_helpers::CheckCallCtx;
use crate::codemod_helpers::MatchCtx;
use crate::diagnostics::FunctionCallLinter;
use crate::diagnostics::Linter;
// @fb-only
// @fb-only: use crate::diagnostics::meta_only;
use crate::fix;
use crate::lazy_function_matches;
@ -45,9 +45,9 @@ impl Linter for UnexportedFunctionLinter {
}
#[rustfmt::skip]
fn should_process_file_id(&self, _sema: &Semantic, _file_id: FileId) -> bool { // @oss-only
// @fb-only
// @fb-only: fn should_process_file_id(&self, sema: &Semantic, file_id: FileId) -> bool {
true // @oss-only
// @fb-only
// @fb-only: meta_only::should_check_for_unexported(sema, file_id)
}
}

View file

@ -152,7 +152,7 @@ fn replace_include_path(
#[cfg(test)]
mod tests {
use elp_ide_db::DiagnosticCode;
// @fb-only
// @fb-only: use elp_ide_db::meta_only::MetaOnlyDiagnosticCode;
use expect_test::Expect;
use expect_test::expect;
@ -173,7 +173,7 @@ mod tests {
#[track_caller]
fn check_fix(fixture_before: &str, fixture_after: Expect) {
let config = DiagnosticsConfig::default()
// @fb-only
// @fb-only: .disable(DiagnosticCode::MetaOnly(MetaOnlyDiagnosticCode::MalformedInclude))
.disable(DiagnosticCode::UnusedInclude);
tests::check_fix_with_config(config, fixture_before, fixture_after)
}

View file

@ -137,6 +137,7 @@ impl GenericLinter for UnusedIncludeLinter {
fn fixes(
&self,
context: &Self::Context,
_range: TextRange,
_sema: &Semantic,
file_id: FileId,
) -> Option<Vec<Assist>> {

View file

@ -88,7 +88,13 @@ impl GenericLinter for UnusedMacroLinter {
Some(DiagnosticTag::Unused)
}
fn fixes(&self, context: &Context, _sema: &Semantic, file_id: FileId) -> Option<Vec<Assist>> {
fn fixes(
&self,
context: &Context,
_range: TextRange,
_sema: &Semantic,
file_id: FileId,
) -> Option<Vec<Assist>> {
Some(vec![delete_unused_macro(
file_id,
context.delete_range,

View file

@ -415,6 +415,7 @@ mod tests {
config,
&extra_diags,
r#"
//- expect_parse_errors
-module(main).
-export([foo/0,bar/0]).

View file

@ -15,9 +15,9 @@ use elp_syntax::AstNode;
use hir::InFile;
use hir::Semantic;
// @fb-only
// @fb-only: use crate::meta_only::exdoc_links;
// @fb-only
// @fb-only: mod meta_only;
mod otp_links;
#[derive(Debug, Clone, PartialEq, Eq)]
@ -40,10 +40,10 @@ pub(crate) fn external_docs(db: &RootDatabase, position: &FilePosition) -> Optio
if let Some(class) = SymbolClass::classify(&sema, in_file_token.clone()) {
class.iter().for_each(|def| {
otp_links::links(&mut doc_links, &sema, &def);
// @fb-only
// @fb-only: exdoc_links::links(&mut doc_links, &sema, &def);
});
}
// @fb-only
// @fb-only: meta_only::links(&mut doc_links, node, position);
Some(doc_links)
}

View file

@ -83,7 +83,7 @@ mod tests {
check(
r#"
-module(foo).
-bar() -> ?L~INE.
bar() -> ?L~INE.
"#,
expect![[r#"
LINE
@ -97,7 +97,7 @@ mod tests {
check(
r#"
-module(foo).
-bar() -> ?F~ILE.
bar() -> ?F~ILE.
"#,
expect![[r#"
FILE
@ -420,7 +420,7 @@ baz() ->
maps:get(type, ExpectedQr, missing_expected_type);
_ ->
Type
end,
end
).
baz() ->
?asser~tQrs(AliceWID, ?WA_QR_TYPE_MESSAGE, []),

View file

@ -259,6 +259,7 @@ main() ->
fn param_hints_variables_missing_param() {
check_params(
r#"
//- expect_parse_errors
-module(main).~
-compile(export_all).
sum(A, B) -> A + B.

View file

@ -110,7 +110,7 @@ pub mod diagnostics;
pub mod diagnostics_collection;
pub mod diff;
mod highlight_related;
// @fb-only
// @fb-only: pub mod meta_only;
pub use annotations::Annotation;
pub use annotations::AnnotationKind;
@ -251,9 +251,9 @@ impl Analysis {
})
}
pub fn should_eqwalize(&self, file_id: FileId, include_tests: bool) -> Cancellable<bool> {
pub fn should_eqwalize(&self, file_id: FileId) -> Cancellable<bool> {
let is_in_app = self.file_app_type(file_id).ok() == Some(Some(AppType::App));
Ok(is_in_app && self.is_eqwalizer_enabled(file_id, include_tests)?)
Ok(is_in_app && self.is_eqwalizer_enabled(file_id)?)
}
/// Computes the set of eqwalizer diagnostics for the given files,
@ -383,8 +383,8 @@ impl Analysis {
/// - the app (the module belongs to) has `.eqwalizer` marker in the roof
/// - or the module has `-typing([eqwalizer]).` pragma
/// - or the whole project has `enable_all=true` in its `.elp.toml` file
pub fn is_eqwalizer_enabled(&self, file_id: FileId, include_tests: bool) -> Cancellable<bool> {
self.with_db(|db| db.is_eqwalizer_enabled(file_id, include_tests))
pub fn is_eqwalizer_enabled(&self, file_id: FileId) -> Cancellable<bool> {
self.with_db(|db| db.is_eqwalizer_enabled(file_id))
}
/// ETF for the module's abstract forms

View file

@ -194,35 +194,53 @@ pub fn rename_var(
#[cfg(test)]
pub(crate) mod tests {
use elp_ide_db::RootDatabase;
use elp_ide_db::elp_base_db::AnchoredPathBuf;
use elp_ide_db::elp_base_db::FileId;
use elp_ide_db::elp_base_db::VfsPath;
use elp_ide_db::elp_base_db::assert_eq_text;
use elp_ide_db::elp_base_db::fixture::ChangeFixture;
use elp_ide_db::elp_base_db::fixture::WithFixture as _;
use elp_ide_db::source_change::FileSystemEdit;
use elp_ide_db::text_edit::TextEdit;
use elp_project_model::test_fixture::trim_indent;
use elp_syntax::AstNode;
use elp_syntax::algo;
use elp_syntax::ast;
use fxhash::FxHashSet;
use hir::AnyExprId;
use hir::InFile;
use hir::Semantic;
use super::rename_var;
use crate::AnalysisHost;
use crate::fixture;
#[track_caller]
pub(crate) fn check_rename(new_name: &str, fixture_before: &str, fixture_after_str: &str) {
let fixture_after_str = &trim_indent(fixture_after_str);
let analysis_after = fixture::multi_file(fixture_after_str);
let (analysis, position, _) = fixture::position(fixture_before);
let (db_before, fixture) = RootDatabase::with_fixture(fixture_before);
let host_before = AnalysisHost { db: db_before };
let analysis = host_before.analysis();
let position = fixture.position();
let (db_after, fixture_after) = RootDatabase::with_fixture(fixture_after_str);
let host_after = AnalysisHost { db: db_after };
let analysis_after = host_after.analysis();
let rename_result = analysis
.rename(position, new_name)
.unwrap_or_else(|err| panic!("Rename to '{new_name}' was cancelled: {err}"));
match rename_result {
Ok(source_change) => {
let mut file_ids: FxHashSet<FileId> = FxHashSet::default();
for edit in source_change.source_file_edits {
let mut text_edit_builder = TextEdit::builder();
let file_id = edit.0;
// New and old file_id are the same
file_ids.insert(file_id);
for indel in edit.1.into_iter() {
text_edit_builder.replace(indel.delete, indel.insert);
}
@ -232,6 +250,82 @@ pub(crate) mod tests {
let expected = analysis_after.file_text(file_id).unwrap().to_string();
assert_eq_text!(&*expected, &*result);
}
for op in source_change.file_system_edits {
let expected;
let new_file_id;
match op {
FileSystemEdit::CreateFile {
dst,
initial_contents,
} => {
let new_file =
find_new_file_id(&fixture_after, &dst).unwrap_or_else(|| {
panic!(
"Fixture after:could not find file created as '{}'",
&dst.path
)
});
new_file_id = *new_file.1;
expected = initial_contents;
let actual = analysis_after.file_text(new_file_id).unwrap().to_string();
assert_eq_text!(&*expected, &*actual);
}
FileSystemEdit::MoveFile { src: _, dst } => {
let new_file =
find_new_file_id(&fixture_after, &dst).unwrap_or_else(|| {
panic!(
"Fixture after:could not find file renamed to '{}'",
&dst.path
)
});
new_file_id = *new_file.1;
// We simply record the new file id for checking in `fixture_after``.
// The expected value will be updated by the new_file_edits below,
// and the result asserted there
}
}
file_ids.insert(new_file_id);
}
for (dst, op) in source_change.new_file_edits {
// When renaming a module, we move the original file, then apply fixup edits
// to the new file
let anchored_dst = AnchoredPathBuf {
anchor: dst.anchor,
path: dst.path,
};
let new_file =
find_new_file_id(&fixture_after, &anchored_dst).unwrap_or_else(|| {
panic!(
"Fixture after:could not find file created as '{}'",
&anchored_dst.path
)
});
let mut text_edit_builder = TextEdit::builder();
let file_id = *new_file.1;
// New and old file_id are the same
file_ids.insert(file_id);
for indel in op.iter() {
text_edit_builder.replace(indel.delete, indel.insert.to_string());
}
let mut result = analysis.file_text(file_id).unwrap().to_string();
let edit = text_edit_builder.finish();
edit.apply(&mut result);
let expected = analysis_after.file_text(file_id).unwrap().to_string();
assert_eq_text!(&*expected, &*result);
}
// Check the balance of the expectations in the new fixture.
for file_id in &fixture_after.files {
if !file_ids.contains(file_id) {
let actual = analysis_after.file_text(*file_id).unwrap().to_string();
let expected = if fixture.files.contains(file_id) {
analysis.file_text(*file_id).unwrap().to_string()
} else {
format!("File {:?} not present in original fixture", file_id)
};
assert_eq_text!(&*expected, &*actual);
}
}
}
Err(err) => {
if fixture_after_str.starts_with("error:") {
@ -247,6 +341,16 @@ pub(crate) mod tests {
};
}
fn find_new_file_id<'a>(
fixture: &'a ChangeFixture,
dst: &'a AnchoredPathBuf,
) -> Option<(&'a VfsPath, &'a FileId)> {
fixture
.files_by_path
.iter()
.find(|(name, _)| name.as_path().unwrap().to_string().ends_with(&dst.path))
}
#[test]
fn test_rename_var_1() {
check_rename("Y", r#"main() -> I~ = 1."#, r#"main() -> Y = 1."#);
@ -1135,6 +1239,326 @@ pub(crate) mod tests {
);
}
// ---------------------------------
// Renaming modules
#[test]
fn rename_module_fails_name_exists() {
check_rename(
"main_2",
r#"
//- /app_a/src/main.erl
-module(ma~in).
//- /app_a_/src/main_2.erl
-module(main_2).
"#,
r#"error: module 'main_2' already exists"#,
);
}
#[test]
fn rename_module_fails_bad_name_1() {
check_rename(
"Main",
r#"
//- /app_a/src/main.erl
-module(ma~in).
//- /app_a_/src/main_2.erl
-module(main_2).
"#,
r#"error: Invalid new module name: 'Main'"#,
);
}
#[test]
fn rename_module_simple() {
check_rename(
"main_2",
r#"
//- /app_a/src/main.erl
-module(ma~in).
"#,
r#"
//- /app_a/src/main_2.erl
-module(main_2).
"#,
);
}
#[test]
fn rename_module_fails_dup_name() {
check_rename(
"main_2",
r#"
//- /app_a/src/main_2.erl
-module(main_2).
-export([foo/0]).
foo() -> ok.
//- /app_a/src/main.erl
-module(ma~in).
-export([foo/0]).
foo() -> ok.
bar() -> main:foo().
baz() -> main:bar().
//- /app_a/src/other.erl
-module(other).
-export([bar/0]).
bar() -> main:foo().
"#,
r#"error: module 'main_2' already exists"#,
);
}
#[test]
fn rename_module_with_usage_internal() {
check_rename(
"main_2",
r#"
//- /app_a/src/main.erl
-module(ma~in).
-export([foo/0]).
foo() -> ok.
bar() -> main:foo().
baz() -> main:bar().
//- /app_a/src/other.erl
-module(other).
-export([bar/0]).
bar() -> main:foo().
"#,
//------------------
r#"
//- /app_a/src/main_2.erl
-module(main_2).
-export([foo/0]).
foo() -> ok.
bar() -> main_2:foo().
baz() -> main_2:bar().
//- /app_a/src/other.erl
-module(other).
-export([bar/0]).
bar() -> main_2:foo().
"#,
);
}
#[test]
fn rename_module_with_usage_type() {
// TODO: check for compile errors in the fixture
check_rename(
"main_3",
r#"
//- /app_a/src/main.erl
-module(ma~in).
-export_type([foo/0]).
-type foo() :: ok.
//- /app_a/src/other.erl
-module(other).
-export([bar/0]).
-spec bar() -> main:foo().
bar() -> ok.
"#,
r#"
//- /app_a/src/main_3.erl
-module(main_3).
-export_type([foo/0]).
-type foo() :: ok.
//- /app_a/src/other.erl
-module(other).
-export([bar/0]).
-spec bar() -> main_3:foo().
bar() -> ok.
"#,
);
}
#[test]
fn rename_module_with_usage_record() {
check_rename(
"main_3",
r#"
//- /app_a/src/main.erl
-module(ma~in).
-export_type([foo/0]).
-type foo() :: ok.
//- /app_a/src/other.erl
-module(other).
-export([bar/0]).
-spec bar() -> main:foo().
bar() -> ok.
-record(main, {field :: main:foo()}).
"#,
//------------------
r#"
//- /app_a/src/main_3.erl
-module(main_3).
-export_type([foo/0]).
-type foo() :: ok.
//- /app_a/src/other.erl
-module(other).
-export([bar/0]).
-spec bar() -> main_3:foo().
bar() -> ok.
-record(main, {field :: main_3:foo()}).
"#,
);
}
#[test]
fn rename_module_with_usage_fun_arg() {
check_rename(
"main_3",
r#"
//- /app_a/src/main.erl
-module(ma~in).
-export_type([foo/0]).
-type foo() :: ok.
//- /app_a/src/other.erl
-module(other).
-export([bar/0]).
-spec bar() -> main:foo().
bar() ->
meck:new(main, [passthrough]),
meck:new([other, main] , [passthrough]),
meck:unload(main),
apply(main, foo, []),
ok.
-record(main, {field :: main:foo()}).
"#,
//------------------
r#"
//- /app_a/src/main_3.erl
-module(main_3).
-export_type([foo/0]).
-type foo() :: ok.
//- /app_a/src/other.erl
-module(other).
-export([bar/0]).
-spec bar() -> main_3:foo().
bar() ->
meck:new(main_3, [passthrough]),
meck:new([other, main_3] , [passthrough]),
meck:unload(main_3),
apply(main_3, foo, []),
ok.
-record(main, {field :: main_3:foo()}).
"#,
);
}
#[test]
fn rename_module_with_usage_fun() {
check_rename(
"main_3",
r#"
//- /app_a/src/main.erl
-module(ma~in).
-export([foo/1]).
foo(X) -> {X}.
//- /app_a/src/other.erl
-module(other).
-export([bar/1]).
-spec bar(term()) -> ok.
bar(UStrings) ->
Jobs = [{fun main:foo/1, [U], []} || U <- UStrings],
ok.
"#,
r#"
//- /app_a/src/main_3.erl
-module(main_3).
-export([foo/1]).
foo(X) -> {X}.
//- /app_a/src/other.erl
-module(other).
-export([bar/1]).
-spec bar(term()) -> ok.
bar(UStrings) ->
Jobs = [{fun main_3:foo/1, [U], []} || U <- UStrings],
ok.
"#,
);
}
#[test]
fn rename_module_with_usage_fun_as_module() {
check_rename(
"main_3",
r#"
//- /app_a/src/main.erl
-module(ma~in).
-export([main/1]).
main(X) -> {X}.
//- /app_a/src/other.erl
-module(other).
-export([bar/1]).
-spec bar(term()) -> ok.
bar(UStrings) ->
Jobs = [{fun main:main/1, [U], []} || U <- UStrings],
ok.
"#,
r#"
//- /app_a/src/main_3.erl
-module(main_3).
-export([main/1]).
main(X) -> {X}.
//- /app_a/src/other.erl
-module(other).
-export([bar/1]).
-spec bar(term()) -> ok.
bar(UStrings) ->
Jobs = [{fun main_3:main/1, [U], []} || U <- UStrings],
ok.
"#,
);
}
#[test]
fn rename_module_with_usage_define() {
check_rename(
"main_3",
r#"
//- /app_a/src/main.erl
-module(ma~in).
-export([foo/1]).
foo(X) -> {X}.
//- /app_a/src/definer.hrl
-define(FOO(X), main:foo(X)).
//- /app_a/src/other.erl
-module(other).
-include("definer.hrl").
-export([bar/0]).
-spec bar(term()) -> ok.
bar(U) ->
main:foo(U),
?FOO(U),
ok.
"#,
//------------------
r#"
//- /app_a/src/main_3.erl
-module(main_3).
-export([foo/1]).
foo(X) -> {X}.
//- /app_a/src/definer.hrl
-define(FOO(X), main_3:foo(X)).
//- /app_a/src/other.erl
-module(other).
-include("definer.hrl").
-export([bar/0]).
-spec bar(term()) -> ok.
bar(U) ->
main_3:foo(U),
?FOO(U),
ok.
"#,
);
}
// ---------------------------------
#[track_caller]

View file

@ -243,7 +243,7 @@ mod tests {
//- /my_app/src/runnables.erl
~
-module(runnables).
-export([all/]).
-export([all/0]).
main() ->
ok.
"#,

View file

@ -311,6 +311,7 @@ mod tests {
fn test_fn_signature_local_two_args() {
check(
r#"
//- expect_parse_errors
-module(main).
-spec add(integer(), integer()) -> integer().
@ -343,6 +344,7 @@ main() ->
);
check(
r#"
//- expect_parse_errors
-module(main).
-spec add(integer(), integer()) -> integer().
@ -375,6 +377,7 @@ main() ->
);
check(
r#"
//- expect_parse_errors
-module(main).
-spec add(integer(), integer()) -> integer().
@ -411,6 +414,7 @@ main() ->
fn test_fn_signature_remote_two_args() {
check(
r#"
//- expect_parse_errors
//- /one.erl
-module(one).
@ -449,6 +453,7 @@ main() ->
);
check(
r#"
//- expect_parse_errors
//- /one.erl
-module(one).
@ -487,6 +492,7 @@ main() ->
);
check(
r#"
//- expect_parse_errors
//- /one.erl
-module(one).
@ -529,6 +535,7 @@ main() ->
fn test_fn_signature_quoted_remote_two_args() {
check(
r#"
//- expect_parse_errors
//- /Elixir.One.erl
-module('Elixir.One').
@ -576,6 +583,7 @@ main() ->
fn test_fn_signature_unclosed_call() {
check(
r#"
//- expect_parse_errors
-module(main).
-compile(export_all).
@ -626,6 +634,7 @@ main() ->
fn test_fn_signature_doc() {
check(
r#"
//- expect_parse_errors
-module(main).
-compile(export_all).
@ -685,6 +694,7 @@ main() ->
if supports_eep59_doc_attributes() {
check(
r#"
//- expect_parse_errors
-module(main).
-compile(export_all).
@ -753,6 +763,7 @@ main() ->
fn test_fn_signature_local_imported() {
check(
r#"
//- expect_parse_errors
//- /one.erl
-module(one).
-compile(export_all).
@ -794,6 +805,7 @@ main() ->
fn test_fn_signature_spec_arg_names() {
check(
r#"
//- expect_parse_errors
//- /one.erl
-module(one).
-compile(export_all).

View file

@ -378,6 +378,7 @@ pub(crate) fn check_diagnostics(fixture: &str) {
.disable(DiagnosticCode::UnspecificInclude)
.disable(DiagnosticCode::BinaryStringToSigil)
.disable(DiagnosticCode::HirUnresolvedMacro)
.disable(DiagnosticCode::BoundVarInLhs)
.disable(DiagnosticCode::HirUnresolvedInclude);
check_diagnostics_with_config(config, fixture)
}
@ -695,6 +696,7 @@ mod test {
fn filtered_diagnostics_passes_syntax_errors() {
check_filtered_diagnostics(
r#"
//- expect_parse_errors
%%<^^^^^^^^^^^^ 💡 error: L1201: no module definition
foo() ->
bug bug.

View file

@ -166,6 +166,7 @@ mod test {
fn test_error_recovery() {
check(
r#"
//- expect_parse_errors
//- /src/sample.erl
-module(sample1).
% U.S. English
@ -180,6 +181,7 @@ mod test {
check(
r#"
//- expect_parse_errors
//- /src/sample.erl
-module(sample1).
% U.K. English
@ -197,6 +199,7 @@ mod test {
fn test_typing_attribute() {
check(
r#"
//- expect_parse_errors
-module(sample).
-typ~
"#,
@ -211,6 +214,7 @@ mod test {
fn test_module_attribute() {
check(
r#"
//- expect_parse_errors
-mod~
"#,
None,
@ -224,6 +228,7 @@ mod test {
fn test_module_attribute_hyphen() {
check(
r#"
//- expect_parse_errors
//- /src/my-module.erl
-mod~
"#,
@ -238,6 +243,7 @@ mod test {
fn test_module_attribute_at() {
check(
r#"
//- expect_parse_errors
//- /src/my@module.erl
-mod~
"#,
@ -252,6 +258,7 @@ mod test {
fn test_module_attribute_underscore() {
check(
r#"
//- expect_parse_errors
//- /src/my_module.erl
-mod~
"#,
@ -266,6 +273,7 @@ mod test {
fn test_module_attribute_uppercase() {
check(
r#"
//- expect_parse_errors
//- /src/Module.erl
-mod~
"#,
@ -280,6 +288,7 @@ mod test {
fn test_module_attribute_uppercase_middle() {
check(
r#"
//- expect_parse_errors
//- /src/moDule.erl
-mod~
"#,

View file

@ -281,6 +281,7 @@ mod ctx_tests {
fn expr_ctx() {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
test() ->
~X.
@ -290,6 +291,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
test() ->
case 1 of.
@ -301,6 +303,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
test() ->
fun(_) -> ~X end.
@ -310,6 +313,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
test() ->
try 1
@ -353,6 +357,7 @@ mod ctx_tests {
fn ctx_pattern() {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
test(Y, X) ->
~Y = X.
@ -362,6 +367,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
test(X) ->
case rand:uniform(1) of
@ -373,6 +379,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
test(X) ->
fun(X~) -> 1 end.
@ -382,6 +389,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
test() ->
receive
@ -393,6 +401,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
test() ->
try [1]
@ -407,6 +416,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
test(X) ->
if
@ -429,6 +439,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
test(Y, X) ->
try ok of
@ -440,6 +451,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
test(Y, X) ->
try ok of
@ -457,6 +469,7 @@ mod ctx_tests {
fn ctx_pattern_error_recovery_wip() {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
test(Y, X) ->
try ok of
@ -469,6 +482,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
test(Y, X) ->
try ok of
@ -485,6 +499,7 @@ mod ctx_tests {
fn test_type_param_ctx() {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
-type ty(s~) :: ok.
"#),
@ -496,6 +511,7 @@ mod ctx_tests {
fn test_export_ctx() {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
-export([
f~
@ -509,6 +525,7 @@ mod ctx_tests {
fn test_export_type_ctx() {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
-export_type([
t~
@ -522,6 +539,7 @@ mod ctx_tests {
fn test_spec_ctx() {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
-spec t~
table() -> ok.
@ -535,6 +553,7 @@ mod ctx_tests {
fn test_type_ctx() {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
-spec test() -> ~
test() -> ok.
@ -544,6 +563,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
-spec test() -> o~k
test() -> ok.
@ -553,6 +573,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
-spec test(o~) -> ok.
test() -> ok.
@ -562,6 +583,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
-record(foo, {field1, field2 :: X~}).
"#),
@ -570,6 +592,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
-opaque test() :: ~.
"#),
@ -578,6 +601,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
-nominal test() :: ~.
"#),
@ -586,6 +610,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
-type test() :: m~
"#),
@ -594,6 +619,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
-spec test() -> ~ok.
"#),
@ -605,6 +631,7 @@ mod ctx_tests {
fn test_ctx_error_recovery() {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
test() ->
~
@ -614,6 +641,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
test() ->
X + ~
@ -623,6 +651,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
test() ->
X + ~.
@ -632,6 +661,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
test() ->
case rand:uniform(1) of
@ -643,6 +673,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
test() ->
(erlang:term_to_binary(~
@ -653,6 +684,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
test() ->
(erlang:term_to_binary(~.
@ -663,6 +695,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
-type ty() :: ~
"#),
@ -671,6 +704,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
-type ty() :: l~.
"#),
@ -679,6 +713,7 @@ mod ctx_tests {
assert_eq!(
ctx(r#"
//- expect_parse_errors
-module(sample).
-record(rec, {field = lists:map(fun(X) -> X + 1 end, [1, ~])}).
"#),

View file

@ -83,6 +83,7 @@ mod test {
check(
r#"
//- expect_parse_errors
-module(sample).
-export([
foo~
@ -106,6 +107,7 @@ mod test {
check(
r#"
//- expect_parse_errors
-module(sample).
-export([
function_a/0,

View file

@ -79,6 +79,7 @@ mod test {
check(
r#"
//- expect_parse_errors
-module(sample).
-export_type([
foo~

Some files were not shown because too many files have changed in this diff Show more