From 4227771616ac424864cfd6e8fd70016992560b8d Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 5 Nov 2025 02:27:35 -0800 Subject: [PATCH 001/142] CLI ssr: Add `--dump-config` for .elp_lint.toml config snippet Summary: We can use the command line `elp ssr` command to search through files in a project for matches, but it is also convenient to be able to examine them in an IDE context. This diff adds this capability, with two new command line options - `--info` upgrades the reported diagnostic from weak warning to info - `--dump-config` emits a toml snippet suitable for placement at the end of an `.elp_lint.toml` file. ```bash elp ssr --parens --dump-config --info "(3)" "{_A}" # Add this to your .elp_lint.toml [[ad_hoc_lints.lints]] type = "LintMatchSsr" ssr_pattern = "ssr: (3)." severity = "info" macro_strategy = "expand" paren_strategy = "visible" [[ad_hoc_lints.lints]] type = "LintMatchSsr" ssr_pattern = "ssr: {_A}." severity = "info" macro_strategy = "expand" paren_strategy = "visible" ``` The edit to `.elp_lint.toml` will be immediately picked up, and you will see the matches as normal diagnostics with a blue squiggle for info. Reviewed By: TD5 Differential Revision: D85960380 fbshipit-source-id: 803c19f1917d939d13e6406e440057632b489ea6 --- crates/elp/src/bin/args.rs | 7 + crates/elp/src/bin/main.rs | 11 + crates/elp/src/bin/ssr_cli.rs | 16 +- crates/elp/src/lib.rs | 1 + .../linter/ssr_ad_hoc_cli_dump_config.stdout | 12 ++ crates/elp/src/resources/test/ssr_help.stdout | 4 +- crates/ide/src/diagnostics/from_config.rs | 195 +++++++++++++++++- 7 files changed, 241 insertions(+), 5 deletions(-) create mode 100644 crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_dump_config.stdout diff --git a/crates/elp/src/bin/args.rs b/crates/elp/src/bin/args.rs index 343f6bd820..f5f5347951 100644 --- a/crates/elp/src/bin/args.rs +++ b/crates/elp/src/bin/args.rs @@ -372,6 +372,10 @@ pub struct Ssr { )] pub format: Option, + /// Report severity as Info instead of WeakWarning + #[bpaf(long("info"))] + pub info_severity: bool, + /// Macro expansion strategy: expand | no-expand | visible-expand (default expand) #[bpaf( long("macros"), @@ -386,6 +390,9 @@ pub struct Ssr { #[bpaf(long("parens"))] pub paren_strategy: bool, + /// Dump a configuration snippet that can be put in .elp_lint.toml to match the given SSR patterns + pub dump_config: bool, + /// Report system memory usage and other statistics #[bpaf(long("report-system-stats"))] pub report_system_stats: bool, diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index 16b7bbdfbe..5f26f99fb2 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -2114,6 +2114,17 @@ mod tests { ) } + #[test] + fn lint_ssr_as_cli_dump_config() { + simple_snapshot( + args_vec!["ssr", "--dump-config", "--info", "?BAR(_@AA)", "{4}"], + "linter", + expect_file!("../resources/test/linter/ssr_ad_hoc_cli_dump_config.stdout"), + true, + None, + ) + } + #[test_case(false ; "rebar")] #[test_case(true ; "buck")] fn eqwalizer_tests_check(buck: bool) { diff --git a/crates/elp/src/bin/ssr_cli.rs b/crates/elp/src/bin/ssr_cli.rs index 57c4f17928..016d838eb4 100644 --- a/crates/elp/src/bin/ssr_cli.rs +++ b/crates/elp/src/bin/ssr_cli.rs @@ -28,6 +28,7 @@ use elp_ide::diagnostics; use elp_ide::diagnostics::DiagnosticsConfig; use elp_ide::diagnostics::FallBackToAll; use elp_ide::diagnostics::LintConfig; +use elp_ide::diagnostics::LintsFromConfig; use elp_ide::diagnostics::MatchSsr; use elp_ide::elp_ide_db::elp_base_db::AbsPath; use elp_ide::elp_ide_db::elp_base_db::FileId; @@ -85,10 +86,16 @@ pub fn run_ssr_command( let mut lint_config = LintConfig::default(); for pattern in &args.ssr_specs { let normalized_pattern = normalize_ssr_pattern(pattern); + let severity = if args.info_severity { + Some(diagnostics::Severity::Information) + } else { + None + }; let ssr_lint = diagnostics::Lint::LintMatchSsr(MatchSsr { ssr_pattern: normalized_pattern, message: None, strategy: Some(strategy), + severity, }); lint_config.ad_hoc_lints.lints.push(ssr_lint); } @@ -105,8 +112,13 @@ pub fn run_ssr_command( .set_experimental(false) .set_use_cli_severity(false); - if diagnostics_config.enabled.all_enabled() && args.is_format_normal() { - writeln!(cli, "Reporting all diagnostics codes")?; + if args.dump_config { + let result = toml::to_string::(&diagnostics_config.lints_from_config)?; + // This is a subsection of .elp_lint.toml, add subsection prefix + let result = result.replace("[[lints]]", "[[ad_hoc_lints.lints]]"); + writeln!(cli, "\n# Add this to your .elp_lint.toml")?; + writeln!(cli, "{}", result)?; + return Ok(()); } // Load the project diff --git a/crates/elp/src/lib.rs b/crates/elp/src/lib.rs index be14930f12..0e9a2e28e6 100644 --- a/crates/elp/src/lib.rs +++ b/crates/elp/src/lib.rs @@ -193,6 +193,7 @@ mod tests { ssr_pattern: "ssr: _@A = 10.".to_string(), message: None, strategy: None, + severity: None, }), ], }, diff --git a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_dump_config.stdout b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_dump_config.stdout new file mode 100644 index 0000000000..880b8f3473 --- /dev/null +++ b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_dump_config.stdout @@ -0,0 +1,12 @@ + +# Add this to your .elp_lint.toml +[[ad_hoc_lints.lints]] +type = "LintMatchSsr" +ssr_pattern = "ssr: ?BAR(_@AA)." +severity = "info" + +[[ad_hoc_lints.lints]] +type = "LintMatchSsr" +ssr_pattern = "ssr: {4}." +severity = "info" + diff --git a/crates/elp/src/resources/test/ssr_help.stdout b/crates/elp/src/resources/test/ssr_help.stdout index a90220787a..b0e6347314 100644 --- a/crates/elp/src/resources/test/ssr_help.stdout +++ b/crates/elp/src/resources/test/ssr_help.stdout @@ -1,4 +1,4 @@ -Usage: [--project PROJECT] [--module MODULE] [--app APP] [--file FILE] [--rebar] [--as PROFILE] [--include-generated] [--include-tests] [[--format FORMAT]] [[--macros STRATEGY]] [--parens] [--report-system-stats] ... +Usage: [--project PROJECT] [--module MODULE] [--app APP] [--file FILE] [--rebar] [--as PROFILE] [--include-generated] [--include-tests] [[--format FORMAT]] [--info] [[--macros STRATEGY]] [--parens] [--dump-config] [--report-system-stats] ... Available positional items: SSR specs to use @@ -13,7 +13,9 @@ Available options: --include-generated Also generate diagnostics for generated files --include-tests Also generate diagnostics for test files --format Show diagnostics in JSON format + --info Report severity as Info instead of WeakWarning --macros Macro expansion strategy: expand | no-expand | visible-expand (default expand) --parens Explicitly match parentheses. If omitted, they are ignored. + --dump-config Dump a configuration snippet that can be put in .elp_lint.toml to match the given SSR patterns --report-system-stats Report system memory usage and other statistics -h, --help Prints help information diff --git a/crates/ide/src/diagnostics/from_config.rs b/crates/ide/src/diagnostics/from_config.rs index 678ffbd369..250fa4422d 100644 --- a/crates/ide/src/diagnostics/from_config.rs +++ b/crates/ide/src/diagnostics/from_config.rs @@ -131,6 +131,7 @@ pub struct MatchSsr { pub ssr_pattern: String, pub message: Option, pub strategy: Option, + pub severity: Option, } impl Serialize for MatchSsr { @@ -140,11 +141,14 @@ impl Serialize for MatchSsr { { use serde::ser::SerializeStruct; - let mut state = serializer.serialize_struct("MatchSsr", 4)?; + let mut state = serializer.serialize_struct("MatchSsr", 5)?; state.serialize_field("ssr_pattern", &self.ssr_pattern)?; if let Some(ref message) = self.message { state.serialize_field("message", message)?; } + if let Some(ref severity) = self.severity { + state.serialize_field("severity", severity)?; + } if let Some(strategy) = self.strategy { // Default strategy is Expand and InvisibleParens let is_default = strategy.macros == MacroStrategy::Expand @@ -185,6 +189,8 @@ impl<'de> Deserialize<'de> for MatchSsr { #[serde(default)] message: Option, #[serde(default)] + severity: Option, + #[serde(default)] macro_strategy: Option, #[serde(default)] paren_strategy: Option, @@ -236,6 +242,7 @@ impl<'de> Deserialize<'de> for MatchSsr { ssr_pattern: helper.ssr_pattern, message: helper.message, strategy, + severity: helper.severity, }) } } @@ -256,12 +263,13 @@ impl MatchSsr { .clone() .unwrap_or_else(|| format!("SSR pattern matched: {}", self.ssr_pattern)); + let severity = self.severity.unwrap_or(Severity::WeakWarning); let diag = Diagnostic::new( DiagnosticCode::AdHoc("ssr-match".to_string()), message, matched.range.range, ) - .with_severity(Severity::WeakWarning); + .with_severity(severity); acc.push(diag); } } @@ -657,6 +665,7 @@ mod tests { ssr_pattern: "ssr: _@A = 10.".to_string(), message: Some("Found pattern".to_string()), strategy: None, + severity: None, }) .unwrap(); expect![[r#" @@ -683,6 +692,7 @@ mod tests { "Found pattern", ), strategy: None, + severity: None, } "#]] .assert_debug_eq(&match_ssr); @@ -695,6 +705,7 @@ mod tests { ssr_pattern: "ssr: _@A = 10.".to_string(), message: Some("Found pattern".to_string()), strategy: None, + severity: None, })], }) .unwrap(); @@ -717,6 +728,7 @@ mod tests { macros: MacroStrategy::Expand, parens: ParenStrategy::InvisibleParens, }), + severity: None, })], }) .unwrap(); @@ -739,6 +751,7 @@ mod tests { macros: MacroStrategy::DoNotExpand, parens: ParenStrategy::VisibleParens, }), + severity: None, })], }) .unwrap(); @@ -782,6 +795,7 @@ mod tests { parens: VisibleParens, }, ), + severity: None, }, ), ], @@ -789,4 +803,181 @@ mod tests { "#]] .assert_debug_eq(&match_ssr); } + + #[test] + fn serde_serialize_match_ssr_with_severity_error() { + use crate::diagnostics::Severity; + let result = toml::to_string::(&MatchSsr { + ssr_pattern: "ssr: _@A = 10.".to_string(), + message: Some("Found pattern".to_string()), + strategy: None, + severity: Some(Severity::Error), + }) + .unwrap(); + expect![[r#" + ssr_pattern = "ssr: _@A = 10." + message = "Found pattern" + severity = "error" + "#]] + .assert_eq(&result); + } + + #[test] + fn serde_serialize_match_ssr_with_severity_warning() { + use crate::diagnostics::Severity; + let result = toml::to_string::(&MatchSsr { + ssr_pattern: "ssr: _@A = 10.".to_string(), + message: Some("Found pattern".to_string()), + strategy: None, + severity: Some(Severity::Warning), + }) + .unwrap(); + expect![[r#" + ssr_pattern = "ssr: _@A = 10." + message = "Found pattern" + severity = "warning" + "#]] + .assert_eq(&result); + } + + #[test] + fn serde_serialize_match_ssr_with_severity_weak() { + use crate::diagnostics::Severity; + let result = toml::to_string::(&MatchSsr { + ssr_pattern: "ssr: _@A = 10.".to_string(), + message: Some("Found pattern".to_string()), + strategy: None, + severity: Some(Severity::WeakWarning), + }) + .unwrap(); + expect![[r#" + ssr_pattern = "ssr: _@A = 10." + message = "Found pattern" + severity = "weak" + "#]] + .assert_eq(&result); + } + + #[test] + fn serde_serialize_match_ssr_with_severity_info() { + use crate::diagnostics::Severity; + let result = toml::to_string::(&MatchSsr { + ssr_pattern: "ssr: _@A = 10.".to_string(), + message: Some("Found pattern".to_string()), + strategy: None, + severity: Some(Severity::Information), + }) + .unwrap(); + expect![[r#" + ssr_pattern = "ssr: _@A = 10." + message = "Found pattern" + severity = "info" + "#]] + .assert_eq(&result); + } + + #[test] + fn serde_deserialize_match_ssr_with_severity() { + use crate::diagnostics::Severity; + let match_ssr: MatchSsr = toml::from_str( + r#" + ssr_pattern = "ssr: _@A = 10." + message = "Found pattern" + severity = "error" + "#, + ) + .unwrap(); + + assert_eq!(match_ssr.severity, Some(Severity::Error)); + } + + #[test] + fn serde_deserialize_lint_match_ssr_with_severity() { + use crate::diagnostics::Severity; + let lints: LintsFromConfig = toml::from_str( + r#" + [[lints]] + type = "LintMatchSsr" + ssr_pattern = "ssr: _@A = 10." + message = "Found pattern" + severity = "warning" + "#, + ) + .unwrap(); + + match &lints.lints[0] { + Lint::LintMatchSsr(match_ssr) => { + assert_eq!(match_ssr.severity, Some(Severity::Warning)); + } + _ => panic!("Expected LintMatchSsr"), + } + } + + #[test] + fn serde_serialize_lint_match_ssr_with_severity_and_strategy() { + use crate::diagnostics::Severity; + let result = toml::to_string::(&LintsFromConfig { + lints: vec![Lint::LintMatchSsr(MatchSsr { + ssr_pattern: "ssr: _@A = 10.".to_string(), + message: Some("Found pattern".to_string()), + strategy: Some(Strategy { + macros: MacroStrategy::DoNotExpand, + parens: ParenStrategy::VisibleParens, + }), + severity: Some(Severity::Error), + })], + }) + .unwrap(); + expect![[r#" + [[lints]] + type = "LintMatchSsr" + ssr_pattern = "ssr: _@A = 10." + message = "Found pattern" + severity = "error" + macro_strategy = "no-expand" + paren_strategy = "visible" + "#]] + .assert_eq(&result); + } + + #[test] + fn serde_deserialize_lint_match_ssr_with_severity_and_strategy() { + let lints: LintsFromConfig = toml::from_str( + r#" + [[lints]] + type = "LintMatchSsr" + ssr_pattern = "ssr: _@A = 10." + message = "Found pattern" + severity = "error" + macro_strategy = "no-expand" + paren_strategy = "visible" + "#, + ) + .unwrap(); + + expect![[r#" + LintsFromConfig { + lints: [ + LintMatchSsr( + MatchSsr { + ssr_pattern: "ssr: _@A = 10.", + message: Some( + "Found pattern", + ), + strategy: Some( + Strategy { + macros: DoNotExpand, + parens: VisibleParens, + }, + ), + severity: Some( + Error, + ), + }, + ), + ], + } + "#]] + .assert_debug_eq(&lints); + } } From 6f2ee22321ce35a6ed9b1dbac5d3c12992a3e3b2 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Fri, 7 Nov 2025 03:07:46 -0800 Subject: [PATCH 002/142] buck2: Improve pop-up when the buck project is malformed Summary: When ELP is loading a buck project it does queries using buck. Sometimes these fail, because the current state of the buck configuration files is invalid. In that case, we show a pop-up suggesting that the user examine the buck dashboard, and provide a URL for this. It is not always clear to the user that this is related to a malformed buck project, rather than a bug in ELP. This diff tries to make it clearer. Note that we unfortunately do not have the ability to use any kind of formatting in the displayed message. Reviewed By: TD5 Differential Revision: D86510447 fbshipit-source-id: 57a93f829d37a8a4d7c8be88d6cf68adcf41c859 --- crates/elp/src/server.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/elp/src/server.rs b/crates/elp/src/server.rs index 4914cf9f3f..6b596db0dd 100644 --- a/crates/elp/src/server.rs +++ b/crates/elp/src/server.rs @@ -1803,9 +1803,13 @@ impl Server { } for (err, uri) in errors { if let Some(uri) = uri { + // It is not possible to put line breaks or other formatting in the message + let error_message = format!( + "Look at `Details` in the buck UI for more information, there is likely an invalid BUCK file: {err}" + ); let params = lsp_types::ShowMessageRequestParams { typ: lsp_types::MessageType::ERROR, - message: err, + message: error_message, actions: Some(vec![MessageActionItem { title: "Open Buck UI".to_string(), properties: HashMap::from_iter(vec![( From 85c1586d7da44238981040a0c7e3636ef62d32a7 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Mon, 10 Nov 2025 02:04:17 -0800 Subject: [PATCH 003/142] Add initial CLAUDE.md Summary: As title. Also tweak elp_development.md Reviewed By: TheGeorge Differential Revision: D86507504 fbshipit-source-id: 0c3d92728014914c2954e1201d9da8fca56c19c0 --- elp_development.md | 296 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 elp_development.md diff --git a/elp_development.md b/elp_development.md new file mode 100644 index 0000000000..78d0e3086e --- /dev/null +++ b/elp_development.md @@ -0,0 +1,296 @@ +--- +llms-gk: 'devmate_elp_development_md' +apply_to_regex: '^(.*\.rs|.*\.md)$' +oncalls: ['vscode_erlang'] +--- +# ELP Development Rules for LLMs (OSS) + +## Project Overview + +ELP (Erlang Language Platform) is a language server and development tools suite +for Erlang, built in Rust. This project provides IDE features, diagnostics, and +code analysis for Erlang codebases. + +## Diagnostic Code Management + +### Adding New Diagnostic Codes + +When adding new diagnostic codes to `DiagnosticCode` enum: + +1. **Naming Convention**: Use descriptive PascalCase names that clearly indicate + the issue + - Good: `UnusedFunctionArg`, `MissingCompileWarnMissingSpec` + - Bad: `Error1`, `BadCode` + +2. **Code Assignment**: Follow the established numbering scheme + - `W0000-W9999`: Native ELP diagnostics, visible in the OSS version + - Use the next available number in the appropriate range + - Never change the number of an existing diagnostic code + - Never change the label of an existing diagnostic code + - Always add the new diagnostic constructor to the end of the list + +3. **Required Methods**: When adding a new variant, update ALL match statements: + - `as_code()`: Return the diagnostic code (e.g., "W0053") + - `as_label()`: Return snake_case label (e.g., "unused_function_arg") + - `allows_fixme_comment()`: Determine if FIXME comments are allowed + - `is_syntax_error()`: Mark if this is a syntax error + +4. **Documentation**: Add comments explaining complex diagnostic codes + +5. **Documentation File**: Create a corresponding documentation file in the + website + - Location: `website/docs/erlang-error-index/{namespace}/{code}.md` + - Example: `W0051` → `website/docs/erlang-error-index/w/W0051.md` + - Include frontmatter with `sidebar_position` matching the code number + - Structure should include: + - Title with code and brief description + - Severity level (Error, Warning, WeakWarning, Information) + - Code example showing the diagnostic in action + - Explanation section describing the issue and why it matters + - Optional: Fix suggestions or alternatives + - The `as_uri()` method automatically generates URLs pointing to these docs + +### Creating DiagnosticDescriptor + +Every diagnostic must have a corresponding `DiagnosticDescriptor` that defines +when and how the diagnostic runs: + +1. **Static Descriptor Declaration**: Create a public static descriptor in your + diagnostic module + - Use `pub(crate) static DESCRIPTOR: DiagnosticDescriptor` pattern + - Define `DiagnosticConditions` with appropriate flags + - Provide a checker function that implements the diagnostic logic + +2. **Diagnostic Conditions**: Configure when the diagnostic should run + - `experimental`: Mark as true for experimental/unstable diagnostics + - `include_generated`: Set to false if diagnostic shouldn't run on generated + code + - `include_tests`: Set to false if diagnostic shouldn't run on test files + - `default_disabled`: Set to true if diagnostic requires explicit enabling + +3. **Checker Function**: Implement the diagnostic logic + - Must match signature: `&dyn AdhocSemanticDiagnostics` + - Push diagnostics to the `diags` vector using `Diagnostic::new()` + - Use helper functions to keep the checker clean and focused + +4. **Registration**: Add the descriptor to `diagnostics_descriptors()` function + in `diagnostics.rs` + - Include your module's `DESCRIPTOR` in the returned vector + +5. **Module Structure**: Follow the established pattern + - Create separate module files for each diagnostic type + - Export the `DESCRIPTOR` as `pub(crate) static` + - Include comprehensive tests with `#[cfg(test)]` + - Use SSR patterns when appropriate for complex matching + +## Rust Code Style + +### Error Handling + +- Use `Result` for fallible operations +- Prefer `?` operator over explicit match for error propagation +- Use descriptive error messages with context + +### Pattern Matching + +- Use exhaustive matches for enums to catch new variants at compile time +- Add explicit comments when intentionally using catch-all patterns +- Prefer early returns to reduce nesting + +### String Handling + +- Use `&str` for borrowed strings, `String` for owned +- Use `format!()` for complex string formatting +- Use `to_string()` for simple conversions + +### Collections + +- Use `FxHashMap` instead of `std::HashMap` for better performance +- Use `lazy_static!` for expensive static computations +- Prefer iterators over manual loops where possible + +## Testing Guidelines + +### Test Structure + +- Use `expect_test` for snapshot testing of complex outputs +- Group related tests in the same module +- Use descriptive test names that explain the scenario + +### Declarative Test Fixtures + +ELP uses a declarative test fixture system that allows you to write tests with +inline annotations and markers directly in test strings. This system is defined +in `crates/project_model/src/test_fixture.rs`. + +#### Key Features + +1. **File Organization**: Use `//- /path/to/file.erl` to define multiple files + in a single test +2. **Metadata Markers**: Specify app names, include paths, OTP apps, etc. using + metadata after the path +3. **Annotations**: Mark expected diagnostics or ranges using `%% ^^^` syntax +4. **Cursors and Ranges**: Use `~` markers to indicate positions or ranges in + test code + +#### Annotation Syntax + +Annotations allow you to mark expected diagnostics, types, or other information +directly in test code: + +- **Basic annotation**: `%% ^^^ some text` - Points to the range above matching + the caret length +- **Top-of-file marker**: `%% <<< text` (at file start) - Creates annotation at + position 0..0 +- **File-wide annotation**: `%% ^^^file text` - Annotation spans the entire file + contents +- **Left-margin annotation**: `%%<^^^ text` - Annotation starts at `%%` position + instead of first `^` +- **Multiline annotations**: Use continuation lines with `%% | next line` + +#### Example Test Fixture + +```rust +let fixture = r#" +//- /src/main.erl +-module(main). + +foo(X) -> + X + undefined. + %% ^^^^^^^^^ error: type mismatch +"#; +``` + +### Test Data + +- Create minimal test cases that focus on specific functionality +- Use realistic Erlang code examples in tests +- Test both positive and negative cases + +### Running Tests for Specific Crates + +When running tests for a specific crate, you need to specify the crate name, not +the directory name. The mapping is: + +| Crate Name | Directory Name | +| -------------------- | ----------------------- | +| `elp` | `crates/elp` | +| `elp_base_db` | `crates/base_db` | +| `elp_eqwalizer` | `crates/eqwalizer` | +| `elp_erlang_service` | `crates/erlang_service` | +| `elp_ide` | `crates/ide` | +| `elp_ide_assists` | `crates/ide_assists` | +| `elp_ide_completion` | `crates/ide_completion` | +| `elp_ide_db` | `crates/ide_db` | +| `elp_ide_ssr` | `crates/ide_ssr` | +| `elp_log` | `crates/elp_log` | +| `elp_project_model` | `crates/project_model` | +| `elp_syntax` | `crates/syntax` | +| `elp_text_edit` | `crates/text_edit` | +| `elp_types_db` | `crates/types_db` | +| `hir` | `crates/hir` | + +Example: To run tests for the `elp_ide` crate: + +```bash +cargo test -p elp_ide +``` + +Or to run tests in a specific directory: + +```bash +cargo test --manifest-path crates/ide/Cargo.toml +``` + +### Existing tests + +- Do not change existing tests without asking + +## Documentation + +### Code Comments + +- Document complex algorithms and business logic +- Explain WHY, not just WHAT the code does +- Use `///` for public API documentation +- Use `//` for internal implementation notes + +### Error Messages + +- Make error messages actionable and user-friendly +- Include context about what was expected vs. what was found +- Provide suggestions for fixing the issue when possible + +## Performance Considerations + +### Memory Usage + +- Use `Box` for large enum variants to keep enum size small +- Consider using `Cow` for strings that might be borrowed or owned +- Use `Arc` for shared immutable data + +### Computation + +- Cache expensive computations using `lazy_static!` or `once_cell` +- Use appropriate data structures (HashMap for lookups, Vec for sequences) +- Profile code paths that handle large Erlang codebases + +## Integration Guidelines + +### Erlang Service Integration + +- Handle Erlang service errors gracefully +- Use appropriate namespaces for different error sources +- Maintain backward compatibility with existing error codes + +### IDE Integration + +- Provide rich diagnostic information (ranges, severity, fixes) +- Support quick fixes and code actions where appropriate +- Ensure diagnostics are fast enough for real-time feedback + +## Maintenance + +### Backward Compatibility + +- Don't change existing diagnostic codes or their meanings +- Deprecate old codes before removing them +- Maintain serialization compatibility for configuration files + +### Code Organization + +- Keep related functionality together in modules +- Use clear module boundaries and public APIs +- Minimize dependencies between modules + +### Version Management + +- Follow semantic versioning for public APIs +- Document breaking changes in release notes +- Provide migration guides for major changes + +## Common Patterns + +### Regex Usage + +- Use `lazy_static!` for compiled regexes +- Prefer specific patterns over overly broad ones +- Test regex patterns thoroughly with edge cases + +### Configuration + +- Support both code-based and label-based diagnostic references +- Use serde for serialization/deserialization +- Provide sensible defaults for all configuration options + +### Error Recovery + +- Continue processing after encountering errors when possible +- Collect multiple errors rather than failing on the first one +- Provide partial results when full analysis isn't possible + +### Process + +- Always run tests before finishing +- Always run `cargo clippy --tests` before submitting PRs +- Use `cargo fmt` for code formatting From 412ccca85ea720cd8cac4e930600f2812e6426d6 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Mon, 10 Nov 2025 03:01:11 -0800 Subject: [PATCH 004/142] Update elp_development.md to improve declarative test example Summary: We now include the diagnostic code in the expected diagnostic. Reviewed By: RobinMorisset Differential Revision: D86658752 fbshipit-source-id: 43f835633a0c81c15d7f43cfcdfde967db9a3178 --- elp_development.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/elp_development.md b/elp_development.md index 78d0e3086e..713caf349a 100644 --- a/elp_development.md +++ b/elp_development.md @@ -155,9 +155,8 @@ let fixture = r#" //- /src/main.erl -module(main). -foo(X) -> - X + undefined. - %% ^^^^^^^^^ error: type mismatch +foo( -> ok. %% +%% ^ error: W0004: Missing ')'~ "#; ``` From 49fb231fe9c2ebc009f1b0621a2edfc45510225d Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Mon, 10 Nov 2025 03:36:04 -0800 Subject: [PATCH 005/142] BE: remove workspace reference to non-existent erl_ast crate Summary: As title. Reviewed By: jcpetruzza Differential Revision: D86513055 fbshipit-source-id: 068357b9d6976bca2838036c5dbe308afbb52fe7 --- Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5814517745..b3b8ae074c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,9 +34,6 @@ elp_text_edit = { path = "./crates/text_edit" } elp_types_db = { path = "./crates/types_db" } hir = { path = "./crates/hir" } -# Forks -erl_ast = { path = "./crates/erl_ast" } - # External crates trie-rs = "0.4.2" always-assert = "0.1.3" From b01a09f2ffe2258dcfbfb78e9f7d37501f5ccbd7 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 12 Nov 2025 01:56:31 -0800 Subject: [PATCH 006/142] elp ssr: set info priority when `--dump-config` is set Summary: As title. It does not make sense to have at least info severity in the IDE, otherwise the squiggles do not show up. Reviewed By: jcpetruzza Differential Revision: D86774246 fbshipit-source-id: 0db22ecf1b90c7e7a2d2037c0c45cc8619928c44 --- crates/elp/src/bin/args.rs | 4 ---- crates/elp/src/bin/main.rs | 13 ++++++++++++- crates/elp/src/bin/ssr_cli.rs | 3 ++- crates/elp/src/resources/test/ssr_help.stdout | 3 +-- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/crates/elp/src/bin/args.rs b/crates/elp/src/bin/args.rs index f5f5347951..b80bae0ff6 100644 --- a/crates/elp/src/bin/args.rs +++ b/crates/elp/src/bin/args.rs @@ -372,10 +372,6 @@ pub struct Ssr { )] pub format: Option, - /// Report severity as Info instead of WeakWarning - #[bpaf(long("info"))] - pub info_severity: bool, - /// Macro expansion strategy: expand | no-expand | visible-expand (default expand) #[bpaf( long("macros"), diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index 5f26f99fb2..a34cd76100 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -2117,7 +2117,18 @@ mod tests { #[test] fn lint_ssr_as_cli_dump_config() { simple_snapshot( - args_vec!["ssr", "--dump-config", "--info", "?BAR(_@AA)", "{4}"], + args_vec!["ssr", "--dump-config", "?BAR(_@AA)", "{4}"], + "linter", + expect_file!("../resources/test/linter/ssr_ad_hoc_cli_dump_config.stdout"), + true, + None, + ) + } + + #[test] + fn lint_ssr_as_cli_dump_config_without_info() { + simple_snapshot( + args_vec!["ssr", "--dump-config", "?BAR(_@AA)", "{4}"], "linter", expect_file!("../resources/test/linter/ssr_ad_hoc_cli_dump_config.stdout"), true, diff --git a/crates/elp/src/bin/ssr_cli.rs b/crates/elp/src/bin/ssr_cli.rs index 016d838eb4..f3b4c4339f 100644 --- a/crates/elp/src/bin/ssr_cli.rs +++ b/crates/elp/src/bin/ssr_cli.rs @@ -86,7 +86,8 @@ pub fn run_ssr_command( let mut lint_config = LintConfig::default(); for pattern in &args.ssr_specs { let normalized_pattern = normalize_ssr_pattern(pattern); - let severity = if args.info_severity { + let severity = if args.dump_config { + // Set the severity so that squiggles are shown in the VS Code UI Some(diagnostics::Severity::Information) } else { None diff --git a/crates/elp/src/resources/test/ssr_help.stdout b/crates/elp/src/resources/test/ssr_help.stdout index b0e6347314..7e5d3f4559 100644 --- a/crates/elp/src/resources/test/ssr_help.stdout +++ b/crates/elp/src/resources/test/ssr_help.stdout @@ -1,4 +1,4 @@ -Usage: [--project PROJECT] [--module MODULE] [--app APP] [--file FILE] [--rebar] [--as PROFILE] [--include-generated] [--include-tests] [[--format FORMAT]] [--info] [[--macros STRATEGY]] [--parens] [--dump-config] [--report-system-stats] ... +Usage: [--project PROJECT] [--module MODULE] [--app APP] [--file FILE] [--rebar] [--as PROFILE] [--include-generated] [--include-tests] [[--format FORMAT]] [[--macros STRATEGY]] [--parens] [--dump-config] [--report-system-stats] ... Available positional items: SSR specs to use @@ -13,7 +13,6 @@ Available options: --include-generated Also generate diagnostics for generated files --include-tests Also generate diagnostics for test files --format Show diagnostics in JSON format - --info Report severity as Info instead of WeakWarning --macros Macro expansion strategy: expand | no-expand | visible-expand (default expand) --parens Explicitly match parentheses. If omitted, they are ignored. --dump-config Dump a configuration snippet that can be put in .elp_lint.toml to match the given SSR patterns From e0c5e94ac0543f27f0a51e5576a832937eabdb4e Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 12 Nov 2025 03:31:52 -0800 Subject: [PATCH 007/142] Add file watch logging for project switch decisions Summary: We already have local logging of changes we receive related to file watching, both from VFS and the LSP client. To contextualise these, include logging of the decisions made at key points in the project loading lifecyle when ELP is running as an LSP server. Reviewed By: jcpetruzza Differential Revision: D86679575 fbshipit-source-id: dc4fb2bdab5b489757a618aefee33f544bce4767 --- crates/elp/src/server.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/elp/src/server.rs b/crates/elp/src/server.rs index 6b596db0dd..3d2cf02252 100644 --- a/crates/elp/src/server.rs +++ b/crates/elp/src/server.rs @@ -507,6 +507,7 @@ impl Server { a_file_per_project = FxHashSet::default(); } spinner.end(); + log::info!(target: FILE_WATCH_LOGGER_NAME, "Project reloading complete"); self.reload_manager .lock() .set_reload_done(a_file_per_project); @@ -558,6 +559,7 @@ impl Server { let to_reload = self.reload_manager.lock().query_changed_files(); if let Some(to_reload) = to_reload { + log::info!(target: FILE_WATCH_LOGGER_NAME, "Asking for project reload"); let query_config = self.reload_manager.lock().set_reload_active(); self.reload_project(to_reload, query_config); } @@ -1022,8 +1024,10 @@ impl Server { log::info!(target: FILE_WATCH_LOGGER_NAME, "VFS change:{}:{}", &opened, &path); } if !opened { - // This call will add the file to the changed_files, picked - // up in `process_changes`, if it has changed. + // This call will add the file to the changed_files, + // picked up in `process_changes`, only if the new + // content is different from the current, checked via + // a hash. vfs.set_file_contents(path, contents); } } @@ -1396,9 +1400,11 @@ impl Server { fn switch_workspaces(&mut self, spinner: &Spinner, new_projects: Vec) -> Result<()> { if new_projects.is_empty() { log::info!("nothing new, not switching workspaces"); + log::info!(target: FILE_WATCH_LOGGER_NAME, "nothing new, not switching workspaces"); return Ok(()); } log::info!("will switch workspaces"); + log::info!(target: FILE_WATCH_LOGGER_NAME, "will switch workspaces"); let mut projects: Vec = self.projects.iter().cloned().collect(); for project in new_projects { @@ -1878,6 +1884,7 @@ impl Server { if !self.reload_manager.lock().ok_to_switch_workspace() { // There are other changed files, abort this reload, to // allow the next one. + log::info!(target: FILE_WATCH_LOGGER_NAME, "Not switching workspaces, more changed config files"); return Ok(false); } spinner.report("Switching to loaded projects".to_string()); From c5263ed926f0492eef11c6aa188f7107b6da6e83 Mon Sep 17 00:00:00 2001 From: Facebook Community Bot Date: Wed, 12 Nov 2025 06:03:18 -0800 Subject: [PATCH 008/142] Re-sync with internal repository (#131) 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. --- .llms/rules/elp_development.md | 117 ++++++------- elp_development.md | 295 --------------------------------- 2 files changed, 61 insertions(+), 351 deletions(-) delete mode 100644 elp_development.md diff --git a/.llms/rules/elp_development.md b/.llms/rules/elp_development.md index 579764599a..713caf349a 100644 --- a/.llms/rules/elp_development.md +++ b/.llms/rules/elp_development.md @@ -3,13 +3,13 @@ llms-gk: 'devmate_elp_development_md' apply_to_regex: '^(.*\.rs|.*\.md)$' oncalls: ['vscode_erlang'] --- - -# ELP Development Rules for LLMs - +# ELP Development Rules for LLMs (OSS) ## Project Overview -ELP (Erlang Language Platform) is a language server and development tools suite for Erlang, built in Rust. This project provides IDE features, diagnostics, and code analysis for Erlang codebases. +ELP (Erlang Language Platform) is a language server and development tools suite +for Erlang, built in Rust. This project provides IDE features, diagnostics, and +code analysis for Erlang codebases. ## Diagnostic Code Management @@ -17,13 +17,13 @@ ELP (Erlang Language Platform) is a language server and development tools suite When adding new diagnostic codes to `DiagnosticCode` enum: -1. **Naming Convention**: Use descriptive PascalCase names that clearly indicate the issue +1. **Naming Convention**: Use descriptive PascalCase names that clearly indicate + the issue - Good: `UnusedFunctionArg`, `MissingCompileWarnMissingSpec` - Bad: `Error1`, `BadCode` 2. **Code Assignment**: Follow the established numbering scheme - `W0000-W9999`: Native ELP diagnostics, visible in the OSS version - - `WA000-WA999`: WhatsApp-specific warnings, only visible in Meta builds - Use the next available number in the appropriate range - Never change the number of an existing diagnostic code - Never change the label of an existing diagnostic code @@ -37,7 +37,8 @@ When adding new diagnostic codes to `DiagnosticCode` enum: 4. **Documentation**: Add comments explaining complex diagnostic codes -5. **Documentation File**: Create a corresponding documentation file in the website +5. **Documentation File**: Create a corresponding documentation file in the + website - Location: `website/docs/erlang-error-index/{namespace}/{code}.md` - Example: `W0051` → `website/docs/erlang-error-index/w/W0051.md` - Include frontmatter with `sidebar_position` matching the code number @@ -51,16 +52,19 @@ When adding new diagnostic codes to `DiagnosticCode` enum: ### Creating DiagnosticDescriptor -Every diagnostic must have a corresponding `DiagnosticDescriptor` that defines when and how the diagnostic runs: +Every diagnostic must have a corresponding `DiagnosticDescriptor` that defines +when and how the diagnostic runs: -1. **Static Descriptor Declaration**: Create a public static descriptor in your diagnostic module +1. **Static Descriptor Declaration**: Create a public static descriptor in your + diagnostic module - Use `pub(crate) static DESCRIPTOR: DiagnosticDescriptor` pattern - Define `DiagnosticConditions` with appropriate flags - Provide a checker function that implements the diagnostic logic 2. **Diagnostic Conditions**: Configure when the diagnostic should run - `experimental`: Mark as true for experimental/unstable diagnostics - - `include_generated`: Set to false if diagnostic shouldn't run on generated code + - `include_generated`: Set to false if diagnostic shouldn't run on generated + code - `include_tests`: Set to false if diagnostic shouldn't run on test files - `default_disabled`: Set to true if diagnostic requires explicit enabling @@ -69,7 +73,8 @@ Every diagnostic must have a corresponding `DiagnosticDescriptor` that defines w - Push diagnostics to the `diags` vector using `Diagnostic::new()` - Use helper functions to keep the checker clean and focused -4. **Registration**: Add the descriptor to `diagnostics_descriptors()` function in `diagnostics.rs` +4. **Registration**: Add the descriptor to `diagnostics_descriptors()` function + in `diagnostics.rs` - Include your module's `DESCRIPTOR` in the returned vector 5. **Module Structure**: Follow the established pattern @@ -78,12 +83,6 @@ Every diagnostic must have a corresponding `DiagnosticDescriptor` that defines w - Include comprehensive tests with `#[cfg(test)]` - Use SSR patterns when appropriate for complex matching -### Meta-Only vs OSS Code - -- Use `@fb-only` and `@oss-only` comments to mark platform-specific code -- Meta-only diagnostics should use `MetaOnlyDiagnosticCode` wrapper -- Ensure OSS builds work by providing fallbacks for Meta-only features - ## Rust Code Style ### Error Handling @@ -120,23 +119,33 @@ Every diagnostic must have a corresponding `DiagnosticDescriptor` that defines w ### Declarative Test Fixtures -ELP uses a declarative test fixture system that allows you to write tests with inline annotations and markers directly in test strings. This system is defined in `/data/sandcastle/boxes/fbsource/fbcode/whatsapp/elp/crates/project_model/src/test_fixture.rs`. +ELP uses a declarative test fixture system that allows you to write tests with +inline annotations and markers directly in test strings. This system is defined +in `crates/project_model/src/test_fixture.rs`. #### Key Features -1. **File Organization**: Use `//- /path/to/file.erl` to define multiple files in a single test -2. **Metadata Markers**: Specify app names, include paths, OTP apps, etc. using metadata after the path +1. **File Organization**: Use `//- /path/to/file.erl` to define multiple files + in a single test +2. **Metadata Markers**: Specify app names, include paths, OTP apps, etc. using + metadata after the path 3. **Annotations**: Mark expected diagnostics or ranges using `%% ^^^` syntax -4. **Cursors and Ranges**: Use `~` markers to indicate positions or ranges in test code +4. **Cursors and Ranges**: Use `~` markers to indicate positions or ranges in + test code #### Annotation Syntax -Annotations allow you to mark expected diagnostics, types, or other information directly in test code: +Annotations allow you to mark expected diagnostics, types, or other information +directly in test code: -- **Basic annotation**: `%% ^^^ some text` - Points to the range above matching the caret length -- **Top-of-file marker**: `%% <<< text` (at file start) - Creates annotation at position 0..0 -- **File-wide annotation**: `%% ^^^file text` - Annotation spans the entire file contents -- **Left-margin annotation**: `%%<^^^ text` - Annotation starts at `%%` position instead of first `^` +- **Basic annotation**: `%% ^^^ some text` - Points to the range above matching + the caret length +- **Top-of-file marker**: `%% <<< text` (at file start) - Creates annotation at + position 0..0 +- **File-wide annotation**: `%% ^^^file text` - Annotation spans the entire file + contents +- **Left-margin annotation**: `%%<^^^ text` - Annotation starts at `%%` position + instead of first `^` - **Multiline annotations**: Use continuation lines with `%% | next line` #### Example Test Fixture @@ -146,9 +155,8 @@ let fixture = r#" //- /src/main.erl -module(main). -foo(X) -> - X + undefined. - %% ^^^^^^^^^ error: type mismatch +foo( -> ok. %% +%% ^ error: W0004: Missing ')'~ "#; ``` @@ -160,34 +168,37 @@ foo(X) -> ### Running Tests for Specific Crates -When running tests for a specific crate, you need to specify the crate name, not the directory name. The mapping is: +When running tests for a specific crate, you need to specify the crate name, not +the directory name. The mapping is: -| Crate Name | Directory Name | -|------------|----------------| -| `elp_base_db` | `crates/base_db` | -| `elp_eqwalizer` | `crates/eqwalizer` | +| Crate Name | Directory Name | +| -------------------- | ----------------------- | +| `elp` | `crates/elp` | +| `elp_base_db` | `crates/base_db` | +| `elp_eqwalizer` | `crates/eqwalizer` | | `elp_erlang_service` | `crates/erlang_service` | -| `elp_ide` | `crates/ide` | -| `elp_ide_assists` | `crates/ide_assists` | +| `elp_ide` | `crates/ide` | +| `elp_ide_assists` | `crates/ide_assists` | | `elp_ide_completion` | `crates/ide_completion` | -| `elp_ide_db` | `crates/ide_db` | -| `elp_ide_ssr` | `crates/ide_ssr` | -| `elp_log` | `crates/elp_log` | -| `elp_project_model` | `crates/project_model` | -| `elp_syntax` | `crates/syntax` | -| `elp_text_edit` | `crates/text_edit` | -| `elp_types_db` | `crates/types_db` | -| `hir` | `crates/hir` | -| `erl_ast` | `crates/erl_ast` | +| `elp_ide_db` | `crates/ide_db` | +| `elp_ide_ssr` | `crates/ide_ssr` | +| `elp_log` | `crates/elp_log` | +| `elp_project_model` | `crates/project_model` | +| `elp_syntax` | `crates/syntax` | +| `elp_text_edit` | `crates/text_edit` | +| `elp_types_db` | `crates/types_db` | +| `hir` | `crates/hir` | Example: To run tests for the `elp_ide` crate: + ```bash -./meta/cargo.sh test -p elp_ide +cargo test -p elp_ide ``` Or to run tests in a specific directory: + ```bash -./meta/cargo.sh test --manifest-path crates/ide/Cargo.toml +cargo test --manifest-path crates/ide/Cargo.toml ``` ### Existing tests @@ -277,14 +288,8 @@ Or to run tests in a specific directory: - Collect multiple errors rather than failing on the first one - Provide partial results when full analysis isn't possible -### Tools - -- ELP uses a cargo workspace. -- Inside Meta, use `./meta/cargo.sh` instead of `cargo` -- Inside Meta, use `./meta/clippy.sh` to run clippy -- Use `arc lint --apply-patches` for formatting. - ### Process -- Always run tests before finishing. -- Always run `./meta/cargo.sh clippy --tests` before submitting a diff +- Always run tests before finishing +- Always run `cargo clippy --tests` before submitting PRs +- Use `cargo fmt` for code formatting diff --git a/elp_development.md b/elp_development.md deleted file mode 100644 index 713caf349a..0000000000 --- a/elp_development.md +++ /dev/null @@ -1,295 +0,0 @@ ---- -llms-gk: 'devmate_elp_development_md' -apply_to_regex: '^(.*\.rs|.*\.md)$' -oncalls: ['vscode_erlang'] ---- -# ELP Development Rules for LLMs (OSS) - -## Project Overview - -ELP (Erlang Language Platform) is a language server and development tools suite -for Erlang, built in Rust. This project provides IDE features, diagnostics, and -code analysis for Erlang codebases. - -## Diagnostic Code Management - -### Adding New Diagnostic Codes - -When adding new diagnostic codes to `DiagnosticCode` enum: - -1. **Naming Convention**: Use descriptive PascalCase names that clearly indicate - the issue - - Good: `UnusedFunctionArg`, `MissingCompileWarnMissingSpec` - - Bad: `Error1`, `BadCode` - -2. **Code Assignment**: Follow the established numbering scheme - - `W0000-W9999`: Native ELP diagnostics, visible in the OSS version - - Use the next available number in the appropriate range - - Never change the number of an existing diagnostic code - - Never change the label of an existing diagnostic code - - Always add the new diagnostic constructor to the end of the list - -3. **Required Methods**: When adding a new variant, update ALL match statements: - - `as_code()`: Return the diagnostic code (e.g., "W0053") - - `as_label()`: Return snake_case label (e.g., "unused_function_arg") - - `allows_fixme_comment()`: Determine if FIXME comments are allowed - - `is_syntax_error()`: Mark if this is a syntax error - -4. **Documentation**: Add comments explaining complex diagnostic codes - -5. **Documentation File**: Create a corresponding documentation file in the - website - - Location: `website/docs/erlang-error-index/{namespace}/{code}.md` - - Example: `W0051` → `website/docs/erlang-error-index/w/W0051.md` - - Include frontmatter with `sidebar_position` matching the code number - - Structure should include: - - Title with code and brief description - - Severity level (Error, Warning, WeakWarning, Information) - - Code example showing the diagnostic in action - - Explanation section describing the issue and why it matters - - Optional: Fix suggestions or alternatives - - The `as_uri()` method automatically generates URLs pointing to these docs - -### Creating DiagnosticDescriptor - -Every diagnostic must have a corresponding `DiagnosticDescriptor` that defines -when and how the diagnostic runs: - -1. **Static Descriptor Declaration**: Create a public static descriptor in your - diagnostic module - - Use `pub(crate) static DESCRIPTOR: DiagnosticDescriptor` pattern - - Define `DiagnosticConditions` with appropriate flags - - Provide a checker function that implements the diagnostic logic - -2. **Diagnostic Conditions**: Configure when the diagnostic should run - - `experimental`: Mark as true for experimental/unstable diagnostics - - `include_generated`: Set to false if diagnostic shouldn't run on generated - code - - `include_tests`: Set to false if diagnostic shouldn't run on test files - - `default_disabled`: Set to true if diagnostic requires explicit enabling - -3. **Checker Function**: Implement the diagnostic logic - - Must match signature: `&dyn AdhocSemanticDiagnostics` - - Push diagnostics to the `diags` vector using `Diagnostic::new()` - - Use helper functions to keep the checker clean and focused - -4. **Registration**: Add the descriptor to `diagnostics_descriptors()` function - in `diagnostics.rs` - - Include your module's `DESCRIPTOR` in the returned vector - -5. **Module Structure**: Follow the established pattern - - Create separate module files for each diagnostic type - - Export the `DESCRIPTOR` as `pub(crate) static` - - Include comprehensive tests with `#[cfg(test)]` - - Use SSR patterns when appropriate for complex matching - -## Rust Code Style - -### Error Handling - -- Use `Result` for fallible operations -- Prefer `?` operator over explicit match for error propagation -- Use descriptive error messages with context - -### Pattern Matching - -- Use exhaustive matches for enums to catch new variants at compile time -- Add explicit comments when intentionally using catch-all patterns -- Prefer early returns to reduce nesting - -### String Handling - -- Use `&str` for borrowed strings, `String` for owned -- Use `format!()` for complex string formatting -- Use `to_string()` for simple conversions - -### Collections - -- Use `FxHashMap` instead of `std::HashMap` for better performance -- Use `lazy_static!` for expensive static computations -- Prefer iterators over manual loops where possible - -## Testing Guidelines - -### Test Structure - -- Use `expect_test` for snapshot testing of complex outputs -- Group related tests in the same module -- Use descriptive test names that explain the scenario - -### Declarative Test Fixtures - -ELP uses a declarative test fixture system that allows you to write tests with -inline annotations and markers directly in test strings. This system is defined -in `crates/project_model/src/test_fixture.rs`. - -#### Key Features - -1. **File Organization**: Use `//- /path/to/file.erl` to define multiple files - in a single test -2. **Metadata Markers**: Specify app names, include paths, OTP apps, etc. using - metadata after the path -3. **Annotations**: Mark expected diagnostics or ranges using `%% ^^^` syntax -4. **Cursors and Ranges**: Use `~` markers to indicate positions or ranges in - test code - -#### Annotation Syntax - -Annotations allow you to mark expected diagnostics, types, or other information -directly in test code: - -- **Basic annotation**: `%% ^^^ some text` - Points to the range above matching - the caret length -- **Top-of-file marker**: `%% <<< text` (at file start) - Creates annotation at - position 0..0 -- **File-wide annotation**: `%% ^^^file text` - Annotation spans the entire file - contents -- **Left-margin annotation**: `%%<^^^ text` - Annotation starts at `%%` position - instead of first `^` -- **Multiline annotations**: Use continuation lines with `%% | next line` - -#### Example Test Fixture - -```rust -let fixture = r#" -//- /src/main.erl --module(main). - -foo( -> ok. %% -%% ^ error: W0004: Missing ')'~ -"#; -``` - -### Test Data - -- Create minimal test cases that focus on specific functionality -- Use realistic Erlang code examples in tests -- Test both positive and negative cases - -### Running Tests for Specific Crates - -When running tests for a specific crate, you need to specify the crate name, not -the directory name. The mapping is: - -| Crate Name | Directory Name | -| -------------------- | ----------------------- | -| `elp` | `crates/elp` | -| `elp_base_db` | `crates/base_db` | -| `elp_eqwalizer` | `crates/eqwalizer` | -| `elp_erlang_service` | `crates/erlang_service` | -| `elp_ide` | `crates/ide` | -| `elp_ide_assists` | `crates/ide_assists` | -| `elp_ide_completion` | `crates/ide_completion` | -| `elp_ide_db` | `crates/ide_db` | -| `elp_ide_ssr` | `crates/ide_ssr` | -| `elp_log` | `crates/elp_log` | -| `elp_project_model` | `crates/project_model` | -| `elp_syntax` | `crates/syntax` | -| `elp_text_edit` | `crates/text_edit` | -| `elp_types_db` | `crates/types_db` | -| `hir` | `crates/hir` | - -Example: To run tests for the `elp_ide` crate: - -```bash -cargo test -p elp_ide -``` - -Or to run tests in a specific directory: - -```bash -cargo test --manifest-path crates/ide/Cargo.toml -``` - -### Existing tests - -- Do not change existing tests without asking - -## Documentation - -### Code Comments - -- Document complex algorithms and business logic -- Explain WHY, not just WHAT the code does -- Use `///` for public API documentation -- Use `//` for internal implementation notes - -### Error Messages - -- Make error messages actionable and user-friendly -- Include context about what was expected vs. what was found -- Provide suggestions for fixing the issue when possible - -## Performance Considerations - -### Memory Usage - -- Use `Box` for large enum variants to keep enum size small -- Consider using `Cow` for strings that might be borrowed or owned -- Use `Arc` for shared immutable data - -### Computation - -- Cache expensive computations using `lazy_static!` or `once_cell` -- Use appropriate data structures (HashMap for lookups, Vec for sequences) -- Profile code paths that handle large Erlang codebases - -## Integration Guidelines - -### Erlang Service Integration - -- Handle Erlang service errors gracefully -- Use appropriate namespaces for different error sources -- Maintain backward compatibility with existing error codes - -### IDE Integration - -- Provide rich diagnostic information (ranges, severity, fixes) -- Support quick fixes and code actions where appropriate -- Ensure diagnostics are fast enough for real-time feedback - -## Maintenance - -### Backward Compatibility - -- Don't change existing diagnostic codes or their meanings -- Deprecate old codes before removing them -- Maintain serialization compatibility for configuration files - -### Code Organization - -- Keep related functionality together in modules -- Use clear module boundaries and public APIs -- Minimize dependencies between modules - -### Version Management - -- Follow semantic versioning for public APIs -- Document breaking changes in release notes -- Provide migration guides for major changes - -## Common Patterns - -### Regex Usage - -- Use `lazy_static!` for compiled regexes -- Prefer specific patterns over overly broad ones -- Test regex patterns thoroughly with edge cases - -### Configuration - -- Support both code-based and label-based diagnostic references -- Use serde for serialization/deserialization -- Provide sensible defaults for all configuration options - -### Error Recovery - -- Continue processing after encountering errors when possible -- Collect multiple errors rather than failing on the first one -- Provide partial results when full analysis isn't possible - -### Process - -- Always run tests before finishing -- Always run `cargo clippy --tests` before submitting PRs -- Use `cargo fmt` for code formatting From f4b93ec388531f8d068c5398080c04379295d10c Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 12 Nov 2025 06:30:58 -0800 Subject: [PATCH 009/142] Improve file watch logging destination Summary: The temp directory location is not the same on all platforms. Reviewed By: jcpetruzza Differential Revision: D86679320 fbshipit-source-id: 39db19df4377798348ae81c87ffb9599e6f87334 --- crates/elp/src/server/setup.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/elp/src/server/setup.rs b/crates/elp/src/server/setup.rs index 6dade5c677..8dba975e73 100644 --- a/crates/elp/src/server/setup.rs +++ b/crates/elp/src/server/setup.rs @@ -33,6 +33,7 @@ use super::FILE_WATCH_LOGGER_NAME; use super::logger::LspLogger; use crate::config::Config; use crate::from_json; +// @fb-only use crate::server::Handle; use crate::server::LOGGER_NAME; use crate::server::Server; @@ -125,8 +126,9 @@ impl ServerSetup { // Set up a logger for tracking down why we are seeing stale // results when branches are switched, as per T218973130 - let log_dir = "/tmp/elp"; - let _ = fs::create_dir_all(log_dir); + // @fb-only + let log_dir = format!("{}/elp", std::env::temp_dir().display()); // @oss-only + let _ = fs::create_dir_all(&log_dir); let log_file = format!( "{}/elp_file_watch_reports-{}.log", log_dir, From 4486d66840a5645853ad553ca7a6cf9b2a879526 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Thu, 13 Nov 2025 03:14:38 -0800 Subject: [PATCH 010/142] Clarify tool usage for building and testing the project in elp_development.md Summary: Clarify tool usage for building and testing the project in elp_development.md Reviewed By: TD5 Differential Revision: D86951935 fbshipit-source-id: 972aea59d3050c258270bbb1694b968d83655858 --- .llms/rules/elp_development.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.llms/rules/elp_development.md b/.llms/rules/elp_development.md index 713caf349a..0a024728a1 100644 --- a/.llms/rules/elp_development.md +++ b/.llms/rules/elp_development.md @@ -11,6 +11,27 @@ ELP (Erlang Language Platform) is a language server and development tools suite for Erlang, built in Rust. This project provides IDE features, diagnostics, and code analysis for Erlang codebases. +## Build System + +Use standard Cargo commands: + +```bash +# Build +cargo build --release + +# Run tests +cargo test --workspace + +# Run clippy +cargo clippy --tests + +# Format code +cargo fmt + +# Code generation +cargo xtask codegen +``` + ## Diagnostic Code Management ### Adding New Diagnostic Codes From 395db3dda795f948082b88dc65e44fc11e752636 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Thu, 13 Nov 2025 06:33:43 -0800 Subject: [PATCH 011/142] Add `lists_reverse_append` linter Summary: Introduce a new linter which identifies usages of the following pattern: ``` lists:reverse(List) ++ Tail ``` Suggesting to replace them with the more performing: ``` lists:reverse(List, Tail) ``` Reviewed By: michalmuskala, alanz Differential Revision: D86967410 fbshipit-source-id: cc5142aa9548a69792bb5ba9ff6698afbd38227c --- crates/ide/src/diagnostics.rs | 2 + .../src/diagnostics/lists_reverse_append.rs | 187 ++++++++++++++++++ crates/ide_db/src/diagnostic_code.rs | 4 + website/docs/erlang-error-index/w/W0056.md | 45 +++++ 4 files changed, 238 insertions(+) create mode 100644 crates/ide/src/diagnostics/lists_reverse_append.rs create mode 100644 website/docs/erlang-error-index/w/W0056.md diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 2078335dc3..e82fe79468 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -108,6 +108,7 @@ mod head_mismatch; mod inefficient_enumerate; mod inefficient_flatlength; mod inefficient_last; +mod lists_reverse_append; mod macro_precedence_suprise; mod map_find_to_syntax; mod map_insertion_to_syntax; @@ -1558,6 +1559,7 @@ const SSR_PATTERN_LINTERS: &[&dyn SsrPatternsDiagnostics] = &[ &binary_string_to_sigil::LINTER, &unnecessary_map_to_list_in_comprehension::LINTER, &could_be_a_string_literal::LINTER, + &lists_reverse_append::LINTER, ]; /// Generic linters diff --git a/crates/ide/src/diagnostics/lists_reverse_append.rs b/crates/ide/src/diagnostics/lists_reverse_append.rs new file mode 100644 index 0000000000..96743b6f28 --- /dev/null +++ b/crates/ide/src/diagnostics/lists_reverse_append.rs @@ -0,0 +1,187 @@ +/* + * 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. + */ + +//! Lint: lists_reverse_append +//! +//! Detect patterns of the form `lists:reverse(_@L) ++ _@T` and suggest `lists:reverse(_@L, _@T)` + +use elp_ide_assists::Assist; +use elp_ide_db::DiagnosticCode; +use elp_ide_db::elp_base_db::FileId; +use elp_ide_db::source_change::SourceChangeBuilder; +use hir::Semantic; + +use crate::diagnostics::Linter; +use crate::diagnostics::SsrPatternsLinter; +use crate::fix; + +pub(crate) struct ListsReverseAppendLinter; + +impl Linter for ListsReverseAppendLinter { + fn id(&self) -> DiagnosticCode { + DiagnosticCode::ListsReverseAppend + } + + fn description(&self) -> &'static str { + "Use `lists:reverse/2` instead of `lists:reverse/1 ++ Tail` for better performance." + } +} + +impl SsrPatternsLinter for ListsReverseAppendLinter { + type Context = (); + + fn patterns(&self) -> Vec<(String, Self::Context)> { + vec![(format!("ssr: lists:reverse({LIST_VAR}) ++ {TAIL_VAR}."), ())] + } + + fn fixes( + &self, + _context: &Self::Context, + matched: &elp_ide_ssr::Match, + sema: &Semantic, + _file_id: FileId, + ) -> Option> { + let list_match = matched.placeholder_text(sema, LIST_VAR)?; + let tail_match = matched.placeholder_text(sema, TAIL_VAR)?; + let mut builder = SourceChangeBuilder::new(matched.range.file_id); + let replacement = format!("lists:reverse({list_match}, {tail_match})"); + let range = matched.range.range; + builder.replace(range, replacement); + let fixes = vec![fix( + "lists_reverse_append", + "Use lists:reverse/2", + builder.finish(), + range, + )]; + Some(fixes) + } +} + +pub(crate) static LINTER: ListsReverseAppendLinter = ListsReverseAppendLinter; + +static LIST_VAR: &str = "_@L"; +static TAIL_VAR: &str = "_@T"; + +#[cfg(test)] +mod tests { + + use expect_test::Expect; + use expect_test::expect; + + use crate::diagnostics::Diagnostic; + use crate::diagnostics::DiagnosticCode; + use crate::tests; + + fn filter(d: &Diagnostic) -> bool { + d.code == DiagnosticCode::ListsReverseAppend + } + + #[track_caller] + fn check_diagnostics(fixture: &str) { + tests::check_filtered_diagnostics(fixture, &filter) + } + + #[track_caller] + fn check_fix(fixture_before: &str, fixture_after: Expect) { + tests::check_fix(fixture_before, fixture_after) + } + + #[test] + fn detects_lists_reverse_append() { + check_diagnostics( + r#" + //- /src/lists_reverse_append.erl + -module(lists_reverse_append). + + reverse_and_append(List, Tail) -> + lists:reverse(List) ++ Tail. + %% ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 warning: W0056: Use `lists:reverse/2` instead of `lists:reverse/1 ++ Tail` for better performance. + "#, + ) + } + + #[test] + fn detects_lists_reverse_append_with_variables() { + check_diagnostics( + r#" + //- /src/lists_reverse_append.erl + -module(lists_reverse_append). + + process(Items, Acc) -> + lists:reverse(Items) ++ Acc. + %% ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 warning: W0056: Use `lists:reverse/2` instead of `lists:reverse/1 ++ Tail` for better performance. + "#, + ) + } + + #[test] + fn ignores_regular_reverse() { + check_diagnostics( + r#" + //- /src/lists_reverse_append.erl + -module(lists_reverse_append). + + reverse_only(List) -> + lists:reverse(List). + "#, + ) + } + + #[test] + fn ignores_regular_append() { + check_diagnostics( + r#" + //- /src/lists_reverse_append.erl + -module(lists_reverse_append). + + append_only(A, B) -> + A ++ B. + "#, + ) + } + + #[test] + fn fixes_lists_reverse_append() { + check_fix( + r#" + //- /src/lists_reverse_append.erl + -module(lists_reverse_append). + + reverse_and_append(List, Tail) -> + lists:re~verse(List) ++ Tail. + "#, + expect![[r#" + -module(lists_reverse_append). + + reverse_and_append(List, Tail) -> + lists:reverse(List, Tail). + "#]], + ) + } + + #[test] + fn fixes_lists_reverse_append_with_complex_expressions() { + check_fix( + r#" + //- /src/lists_reverse_append.erl + -module(lists_reverse_append). + + process([H|T], Acc) -> + lists:rev~erse([H|T]) ++ [1, 2, 3]. + "#, + expect![[r#" + -module(lists_reverse_append). + + process([H|T], Acc) -> + lists:reverse([H|T], [1, 2, 3]). + "#]], + ) + } +} diff --git a/crates/ide_db/src/diagnostic_code.rs b/crates/ide_db/src/diagnostic_code.rs index d3e73636bd..860d61befa 100644 --- a/crates/ide_db/src/diagnostic_code.rs +++ b/crates/ide_db/src/diagnostic_code.rs @@ -89,6 +89,7 @@ pub enum DiagnosticCode { NoErrorLogger, NoNoWarnSuppressions, CouldBeAStringLiteral, + ListsReverseAppend, // Wrapper for erlang service diagnostic codes ErlangService(String), @@ -248,6 +249,7 @@ impl DiagnosticCode { DiagnosticCode::NoErrorLogger => "W0053".to_string(), DiagnosticCode::NoNoWarnSuppressions => "W0054".to_string(), DiagnosticCode::CouldBeAStringLiteral => "W0055".to_string(), + DiagnosticCode::ListsReverseAppend => "W0056".to_string(), DiagnosticCode::ErlangService(c) => c.to_string(), DiagnosticCode::Eqwalizer(c) => format!("eqwalizer: {c}"), DiagnosticCode::AdHoc(c) => format!("ad-hoc: {c}"), @@ -344,6 +346,7 @@ impl DiagnosticCode { DiagnosticCode::NoErrorLogger => "no_error_logger".to_string(), DiagnosticCode::NoNoWarnSuppressions => "no_nowarn_suppressions".to_string(), DiagnosticCode::CouldBeAStringLiteral => "could_be_a_binary_string_literal".to_string(), + DiagnosticCode::ListsReverseAppend => "lists_reverse_append".to_string(), DiagnosticCode::ErlangService(c) => c.to_string(), DiagnosticCode::Eqwalizer(c) => c.to_string(), @@ -520,6 +523,7 @@ impl DiagnosticCode { DiagnosticCode::NoCatch => false, DiagnosticCode::NoErrorLogger => false, DiagnosticCode::NoNoWarnSuppressions => false, + DiagnosticCode::ListsReverseAppend => false, DiagnosticCode::BinaryStringToSigil => false, DiagnosticCode::ErlangService(_) => false, diff --git a/website/docs/erlang-error-index/w/W0056.md b/website/docs/erlang-error-index/w/W0056.md new file mode 100644 index 0000000000..14848f5898 --- /dev/null +++ b/website/docs/erlang-error-index/w/W0056.md @@ -0,0 +1,45 @@ +--- +sidebar_position: 56 +--- + +# W0056 - Use `lists:reverse/2` instead of `lists:reverse/1 ++ Tail` + +## Warning + +```erlang +reverse_and_append(List, Tail) -> + lists:reverse(List) ++ Tail. +%% ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 warning: W0056: Use `lists:reverse/2` instead of `lists:reverse/1 ++ Tail` for better performance. +``` + +## Explanation + +The warning indicates that you are using the pattern `lists:reverse(List) ++ Tail`, which is inefficient. This pattern first reverses the list, creating an intermediate reversed list, and then appends the tail to it using the `++` operator. + +The Erlang standard library provides a more efficient function `lists:reverse/2` that performs both operations in a single pass without creating the intermediate reversed list. + +### Why This Matters + +- **Performance**: Using `lists:reverse/1` followed by `++` creates an unnecessary intermediate data structure +- **Memory**: The intermediate reversed list consumes additional memory that is immediately discarded +- **Idiomatic Erlang**: `lists:reverse/2` is the idiomatic way to reverse and prepend in Erlang + +### Fix + +Replace the pattern with `lists:reverse/2`: + +```erlang +reverse_and_append(List, Tail) -> + lists:reverse(List, Tail). +``` + +### How lists:reverse/2 Works + +The second argument to `lists:reverse/2` acts as an accumulator that gets prepended to the reversed list: + +```erlang +lists:reverse([1, 2, 3], [4, 5, 6]). +% Returns: [3, 2, 1, 4, 5, 6] +``` + +This is equivalent to `lists:reverse([1, 2, 3]) ++ [4, 5, 6]` but significantly more efficient. From f050c6c671603d80144f26f1babc8cdfdfd27eb0 Mon Sep 17 00:00:00 2001 From: Tom Davies Date: Thu, 13 Nov 2025 07:23:15 -0800 Subject: [PATCH 012/142] Support calls to lists:nth/2 Summary: Fixes a query as to why we report on `hd(lists:reverse(L))`, but not `lists:nth(1, lists:reverse(L))`. For the purposes of this diagnostic, `lists:nth(1, lists:reverse(L))` is equivalent to `hd(lists:reverse(L))`, so we trigger on that too. Reviewed By: alanz Differential Revision: D86969766 fbshipit-source-id: e6a022a10267d04c6700b3ee5fc9fa31a02b75ac --- .../ide/src/diagnostics/inefficient_last.rs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/crates/ide/src/diagnostics/inefficient_last.rs b/crates/ide/src/diagnostics/inefficient_last.rs index 5c1f6af896..c120d7ca7f 100644 --- a/crates/ide/src/diagnostics/inefficient_last.rs +++ b/crates/ide/src/diagnostics/inefficient_last.rs @@ -38,6 +38,7 @@ pub(crate) static DESCRIPTOR: DiagnosticDescriptor = DiagnosticDescriptor { }, checker: &|acc, sema, file_id, _ext| { inefficient_last_hd_ssr(acc, sema, file_id); + inefficient_last_nth_ssr(acc, sema, file_id); inefficient_last_pat_ssr(acc, sema, file_id); }, }; @@ -62,6 +63,24 @@ fn inefficient_last_hd_ssr(diags: &mut Vec, sema: &Semantic, file_id }); } +// lists:nth(1, L) is functionally equivalent to hd(L) +fn inefficient_last_nth_ssr(diags: &mut Vec, sema: &Semantic, file_id: FileId) { + let matches = match_pattern_in_file_functions( + sema, + Strategy { + macros: MacroStrategy::Expand, + parens: ParenStrategy::InvisibleParens, + }, + file_id, + format!("ssr: lists:nth(1, lists:reverse({LIST_VAR})).").as_str(), + ); + matches.matches.iter().for_each(|m| { + if let Some(diagnostic) = make_diagnostic_hd(sema, file_id, m) { + diags.push(diagnostic) + } + }); +} + fn inefficient_last_pat_ssr(diags: &mut Vec, sema: &Semantic, file_id: FileId) { let matches = match_pattern_in_file_functions( sema, @@ -248,4 +267,50 @@ mod tests { "#]], ) } + + #[test] + fn ignores_inefficient_last_via_nth_when_index_is_not_one() { + check_diagnostics( + r#" + //- /src/inefficient_last.erl + -module(inefficient_last). + + % elp:ignore W0017 (undefined_function) + fn(List) -> lists:nth(3, lists:reverse(List)). + "#, + ) + } + + #[test] + fn detects_inefficient_last_via_nth() { + check_diagnostics( + r#" + //- /src/inefficient_last.erl + -module(inefficient_last). + + % elp:ignore W0017 (undefined_function) + fn(List) -> lists:nth(1, lists:reverse(List)). + %% ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 warning: W0029: Unnecessary intermediate reverse list allocated. + "#, + ) + } + + #[test] + fn fixes_inefficient_last_via_nth() { + check_fix( + r#" + //- /src/inefficient_last.erl + -module(inefficient_last). + + % elp:ignore W0017 (undefined_function) + fn(List) -> lists:nth(1, lists:re~verse(List)). + "#, + expect![[r#" + -module(inefficient_last). + + % elp:ignore W0017 (undefined_function) + fn(List) -> lists:last(List). + "#]], + ) + } } From 59ec8ab04251314e1bf8e06a95c2c58dd3e60064 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Fri, 14 Nov 2025 04:01:31 -0800 Subject: [PATCH 013/142] 1/n: includes: add not-yet-failing test case for nested include buck targets Summary: This diff sets up a test case to demonstrate a problem which will be fixed by this stack, as it is currently silently ignored. As such, there is no failure reported in the test. That will change later in the stack. It is possible to configure a buck2 project with nested include files so that it compiles with buck2, and also with our erlang_service, but that we are unable to properly lower it into HIR and so do not resolve macros in ELP. This occurs when we have three buck2 targets, A,B,C each in a separate directory. - A has both B and C as dependencies, but - B does not have C as one. Then - target A contains a .erl file that includes a .hrl from target B - the .hrl in target B includes a .hrl from target C So from A we can resolve the entire chain of includes via dependencies, but from B we cannot. And ELP lowers the file into HIR for B in the context of B, so does not resolve the include at C, and silently fails to do so, entering a `Expr::Missing` value instead. Reviewed By: TD5 Differential Revision: D86963334 fbshipit-source-id: 57b51714513fc78cd21083adea6abd12a9350571 --- .../buck_tests_2/resolves_generated_includes.stdout | 4 +++- test_projects/buck_tests_2/.elp.toml | 5 ++++- .../buck_tests_2/check_include/src/top_includer.erl | 12 ++++++++++++ .../include/top_includer.hrl | 4 ++++ .../include/separate_include.hrl | 2 ++ 5 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 test_projects/buck_tests_2/check_include/src/top_includer.erl create mode 100644 test_projects/buck_tests_2/check_include_separate_1/include/top_includer.hrl create mode 100644 test_projects/buck_tests_2/check_include_separate_2/include/separate_include.hrl diff --git a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout index 7787c17575..295a51e424 100644 --- a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout +++ b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout @@ -1,5 +1,5 @@ Reporting all diagnostics codes -Diagnostics reported in 4 modules: +Diagnostics reported in 5 modules: app_a: 3 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. 5:9-5:31::[WeakWarning] [W0037] Unspecific include. @@ -9,6 +9,8 @@ Diagnostics reported in 4 modules: 0:8-0:18::[WeakWarning] [W0046] The module is not documented. eqwalizer: 1 1:8-1:17::[WeakWarning] [W0046] The module is not documented. + top_includer: 1 + 0:8-0:20::[WeakWarning] [W0046] The module is not documented. wa_buck2_module_search: 6 19:0-19:18::[WeakWarning] [W0040] The function is non-trivial, exported, but not documented. 2:8-2:30::[WeakWarning] [W0046] The module is not documented. diff --git a/test_projects/buck_tests_2/.elp.toml b/test_projects/buck_tests_2/.elp.toml index 4ea681809f..35f02a72f3 100644 --- a/test_projects/buck_tests_2/.elp.toml +++ b/test_projects/buck_tests_2/.elp.toml @@ -1,7 +1,10 @@ [buck] enabled = true build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test_projects/buck_tests_2/util/app_a/..." ] +included_targets = [ + "fbcode//whatsapp/elp/test_projects/buck_tests_2/util/app_a/...", + "fbcode//whatsapp/elp/test_projects/buck_tests_2:check_include" + ] excluded_targets = [ "fbcode//whatsapp/elp/test_projects/buck_tests_2:test_elp_ignored" ] source_root = "whatsapp/elp/test_projects/buck_tests_2" diff --git a/test_projects/buck_tests_2/check_include/src/top_includer.erl b/test_projects/buck_tests_2/check_include/src/top_includer.erl new file mode 100644 index 0000000000..4893314930 --- /dev/null +++ b/test_projects/buck_tests_2/check_include/src/top_includer.erl @@ -0,0 +1,12 @@ +-module(top_includer). + +-compile(warn_missing_spec_all). + +-export([foo/0]). + +-include_lib("check_include_separate_1/include/top_includer.hrl"). + +-spec foo() -> ok. +foo() -> + ?FIRST, + ?SECOND. diff --git a/test_projects/buck_tests_2/check_include_separate_1/include/top_includer.hrl b/test_projects/buck_tests_2/check_include_separate_1/include/top_includer.hrl new file mode 100644 index 0000000000..c117baed75 --- /dev/null +++ b/test_projects/buck_tests_2/check_include_separate_1/include/top_includer.hrl @@ -0,0 +1,4 @@ + +-include_lib("check_include_separate_2/include/separate_include.hrl"). + +-define(FIRST, 1). diff --git a/test_projects/buck_tests_2/check_include_separate_2/include/separate_include.hrl b/test_projects/buck_tests_2/check_include_separate_2/include/separate_include.hrl new file mode 100644 index 0000000000..1e0c48bf56 --- /dev/null +++ b/test_projects/buck_tests_2/check_include_separate_2/include/separate_include.hrl @@ -0,0 +1,2 @@ + +-define(SECOND, ok). From 6fe7f108a168ad5c9093368a889c01aa80a06131 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Fri, 14 Nov 2025 04:01:31 -0800 Subject: [PATCH 014/142] 2/n: includes: record macro expansion failures when lowering to HIR Summary: Record failed macro expansions when lowering to HIR. We track them in the `BodySourceMap`, as we need to report based on `ast` locations. Later diffs in this stack will turn them into ide Diagnostics, but that type is not available here because HIR is a leaf in the crate graph. Reviewed By: TD5 Differential Revision: D86963333 fbshipit-source-id: 6f9afecbb10f6a2c999689e007f735f0bafbdf43 --- .../resolves_generated_includes.stdout | 6 ++- crates/hir/src/body.rs | 20 ++++++++++ crates/hir/src/body/lower.rs | 39 +++++++++++++++++-- crates/hir/src/lib.rs | 2 + .../check_include/src/top_includer.erl | 4 +- 5 files changed, 66 insertions(+), 5 deletions(-) diff --git a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout index 295a51e424..5f1a5b09f4 100644 --- a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout +++ b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout @@ -9,8 +9,12 @@ Diagnostics reported in 5 modules: 0:8-0:18::[WeakWarning] [W0046] The module is not documented. eqwalizer: 1 1:8-1:17::[WeakWarning] [W0046] The module is not documented. - top_includer: 1 + top_includer: 5 + 9:0-9:3::[WeakWarning] [W0040] The function is non-trivial, exported, but not documented. 0:8-0:20::[WeakWarning] [W0046] The module is not documented. + 12:4-12:10::[Error] [E1508] undefined macro 'THIRD/2' + 4:9-4:14::[Error] [L1227] function foo/0 undefined + 8:6-8:9::[Error] [L1308] spec for undefined function foo/0 wa_buck2_module_search: 6 19:0-19:18::[WeakWarning] [W0040] The function is non-trivial, exported, but not documented. 2:8-2:30::[WeakWarning] [W0046] The module is not documented. diff --git a/crates/hir/src/body.rs b/crates/hir/src/body.rs index 2f4968b7ba..0252a44ee1 100644 --- a/crates/hir/src/body.rs +++ b/crates/hir/src/body.rs @@ -1155,6 +1155,21 @@ pub type ExprSource = InFileAstPtr; pub type MacroSource = InFileAstPtr; +/// Lightweight diagnostic for body lowering +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BodyDiagnostic { + /// Location of the problematic construct + pub source: MacroSource, + /// Type of diagnostic + pub kind: BodyDiagnosticKind, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum BodyDiagnosticKind { + /// Macro failed to expand/resolve + UnresolvedMacro, +} + #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub struct InFileAstPtr(InFile>) where @@ -1231,6 +1246,7 @@ pub struct BodySourceMap { term_map: FxHashMap, term_map_back: ArenaMap, macro_map: FxHashMap, + diagnostics: Vec, } impl BodySourceMap { @@ -1292,6 +1308,10 @@ impl BodySourceMap { .get(&InFileAstPtr::from_infile(call)) .copied() } + + pub fn diagnostics(&self) -> &[BodyDiagnostic] { + &self.diagnostics + } } #[cfg(test)] diff --git a/crates/hir/src/body/lower.rs b/crates/hir/src/body/lower.rs index 85c3378930..7408ece032 100644 --- a/crates/hir/src/body/lower.rs +++ b/crates/hir/src/body/lower.rs @@ -38,6 +38,8 @@ use crate::AttributeBody; use crate::BasedInteger; use crate::BinarySeg; use crate::Body; +use crate::BodyDiagnostic; +use crate::BodyDiagnosticKind; use crate::BodySourceMap; use crate::CRClause; use crate::CallTarget; @@ -116,6 +118,8 @@ pub struct Ctx<'a> { // This means we need to lower a Var specially when processing a SSR // template, where if it has a prefix of `_@` it is a placeholder. in_ssr: bool, + // Diagnostics collected during body lowering + diagnostics: Vec, } #[derive(Debug)] @@ -152,6 +156,7 @@ impl<'a> Ctx<'a> { starting_stack_size: 1, macro_source_map: FxHashMap::default(), in_ssr: false, + diagnostics: Vec::new(), } } @@ -216,6 +221,7 @@ impl<'a> Ctx<'a> { assert!(self.body.origin.is_valid()); self.body.shrink_to_fit(); + self.source_map.diagnostics = self.diagnostics; (Arc::new(self.body), self.source_map) } @@ -812,6 +818,9 @@ impl<'a> Ctx<'a> { ) }) .unwrap_or_else(|| { + // Record diagnostic for unresolved macro + self.record_unresolved_macro(call); + let expansion = self.alloc_pat(Pat::Missing, Some(expr)); let args = call .args() @@ -1169,6 +1178,9 @@ impl<'a> Ctx<'a> { }) .flatten() .unwrap_or_else(|| { + // Record diagnostic for unresolved macro + self.record_unresolved_macro(call); + call.args() .iter() .flat_map(|args| args.args()) @@ -1400,8 +1412,8 @@ impl<'a> Ctx<'a> { let exprs = self.lower_lc_exprs(lc.lc_exprs()); self.alloc_expr(Expr::Comprehension { builder, exprs }, Some(expr)) } - ast::ExprMax::MacroCallExpr(call) => self - .resolve_macro(call, |this, source, replacement| match replacement { + ast::ExprMax::MacroCallExpr(call) => { + self.resolve_macro(call, |this, source, replacement| match replacement { MacroReplacement::BuiltIn(built_in) => { this.lower_built_in_macro(built_in).map(|literal| { let expr_id = this.alloc_expr(Expr::Literal(literal), None); @@ -1464,6 +1476,9 @@ impl<'a> Ctx<'a> { ) }) .unwrap_or_else(|| { + // Record diagnostic for unresolved macro + self.record_unresolved_macro(call); + let expansion = self.alloc_expr(Expr::Missing, Some(expr)); let args = call .args() @@ -1480,7 +1495,8 @@ impl<'a> Ctx<'a> { }, Some(expr), ) - }), + }) + } ast::ExprMax::MacroString(_) => self.alloc_expr(Expr::Missing, Some(expr)), ast::ExprMax::ParenExpr(paren_expr) => { if let Some(inner_expr) = paren_expr.expr() { @@ -1990,6 +2006,9 @@ impl<'a> Ctx<'a> { }) .flatten() .unwrap_or_else(|| { + // Record diagnostic for unresolved macro + self.record_unresolved_macro(call); + call.args() .iter() .flat_map(|args| args.args()) @@ -2170,6 +2189,9 @@ impl<'a> Ctx<'a> { ) }) .unwrap_or_else(|| { + // Record diagnostic for unresolved macro + self.record_unresolved_macro(call); + let expansion = self.alloc_type_expr(TypeExpr::Missing, Some(expr)); let args = call .args() @@ -2536,6 +2558,9 @@ impl<'a> Ctx<'a> { ) }) .unwrap_or_else(|| { + // Record diagnostic for unresolved macro + self.record_unresolved_macro(call); + let expansion = self.alloc_term(Term::Missing, Some(expr)); let args = call .args() @@ -2997,6 +3022,14 @@ impl<'a> Ctx<'a> { self.macro_source_map.insert(name, source); } + fn record_unresolved_macro(&mut self, call: &ast::MacroCallExpr) { + let source = InFileAstPtr::new(self.curr_file_id(), AstPtr::new(call).cast().unwrap()); + self.diagnostics.push(BodyDiagnostic { + source, + kind: BodyDiagnosticKind::UnresolvedMacro, + }); + } + fn curr_file_id(&self) -> FileId { self.macro_stack[self.macro_stack_id].file_id } diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 11d4b40971..9f51b0d70f 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -35,6 +35,8 @@ mod test_db; pub use body::AnyAttribute; pub use body::AttributeBody; pub use body::Body; +pub use body::BodyDiagnostic; +pub use body::BodyDiagnosticKind; pub use body::BodyOrigin; pub use body::BodySourceMap; pub use body::DefineBody; diff --git a/test_projects/buck_tests_2/check_include/src/top_includer.erl b/test_projects/buck_tests_2/check_include/src/top_includer.erl index 4893314930..ebe984a0df 100644 --- a/test_projects/buck_tests_2/check_include/src/top_includer.erl +++ b/test_projects/buck_tests_2/check_include/src/top_includer.erl @@ -9,4 +9,6 @@ -spec foo() -> ok. foo() -> ?FIRST, - ?SECOND. + ?SECOND, + ?THIRD(41,34), + ?FUNCTION_NAME. From 42ef1d19242cb553a3b634aa4750ad3f73a33851 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Fri, 14 Nov 2025 04:01:31 -0800 Subject: [PATCH 015/142] 3/n: includes: convert HIR diagnostics into ide Diagnostic W0057 Summary: Create a new diagnostic W0057 (hir_unresolved_macro) to report macros not resolved during HIR lowering. For a properly configured buck2 project this simply shadows the existing Erlang service `E1507` / `E1508` diagnostics, but for an incorrectly configured one they show up alone. The next diff filters them out if they are already reported. It also ensures it does not report on usages of `?FUNCTION_NAME` or ?FUNCTION_ARITY` in the replacement part of a `-define()` attribute. Reviewed By: TD5 Differential Revision: D86963336 fbshipit-source-id: 08f8d1b6c58dde6ee0a106f9151b237709dc56e0 --- .../resolves_generated_includes.stdout | 6 +- .../include_lib_non_dependency_fails.stdout | 3 +- .../include_lib_non_dependency_rebar.stdout | 3 +- crates/hir/src/body/lower.rs | 16 ++ crates/ide/src/diagnostics.rs | 158 ++++++++++++++++++ crates/ide/src/diagnostics/unused_macro.rs | 4 +- crates/ide/src/tests.rs | 4 +- crates/ide_db/src/diagnostic_code.rs | 4 + .../check_include/src/top_includer.erl | 6 +- website/docs/erlang-error-index/w/W0057.md | 106 ++++++++++++ 10 files changed, 299 insertions(+), 11 deletions(-) create mode 100644 website/docs/erlang-error-index/w/W0057.md diff --git a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout index 5f1a5b09f4..8ca4ec00a2 100644 --- a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout +++ b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout @@ -9,12 +9,10 @@ Diagnostics reported in 5 modules: 0:8-0:18::[WeakWarning] [W0046] The module is not documented. eqwalizer: 1 1:8-1:17::[WeakWarning] [W0046] The module is not documented. - top_includer: 5 - 9:0-9:3::[WeakWarning] [W0040] The function is non-trivial, exported, but not documented. + top_includer: 3 + 12:4-12:10::[Warning] [W0057] undefined macro 'THIRD/2' 0:8-0:20::[WeakWarning] [W0046] The module is not documented. 12:4-12:10::[Error] [E1508] undefined macro 'THIRD/2' - 4:9-4:14::[Error] [L1227] function foo/0 undefined - 8:6-8:9::[Error] [L1308] spec for undefined function foo/0 wa_buck2_module_search: 6 19:0-19:18::[WeakWarning] [W0040] The function is non-trivial, exported, but not documented. 2:8-2:30::[WeakWarning] [W0046] The module is not documented. diff --git a/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_fails.stdout b/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_fails.stdout index c0fe03edea..33dc4b534a 100644 --- a/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_fails.stdout +++ b/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_fails.stdout @@ -1,8 +1,9 @@ module specified: main_app Diagnostics reported in 1 modules: - main_app: 5 + main_app: 6 4:13-4:55::[Error] [E1516] can't find include lib "external_app/include/external_header.hrl" 11:0-11:42::[Warning] [W0020] Unused file: stdlib/include/assert.hrl 14:9-14:21::[Hint] [W0037] Unspecific include. 16:9-16:24::[Error] [L1227] function test_function/0 undefined + 23:4-23:19::[Warning] [W0057] undefined macro 'EXTERNAL_MACRO' 23:4-23:19::[Error] [E1507] undefined macro 'EXTERNAL_MACRO' diff --git a/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_rebar.stdout b/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_rebar.stdout index 513197d889..dceea92cf3 100644 --- a/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_rebar.stdout +++ b/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_rebar.stdout @@ -1,7 +1,8 @@ module specified: main_app Diagnostics reported in 1 modules: - main_app: 4 + main_app: 5 11:0-11:42::[Warning] [W0020] Unused file: stdlib/include/assert.hrl 14:9-14:21::[Error] [E1516] can't find include file "assert.hrl" 16:9-16:24::[Error] [L1227] function test_function/0 undefined + 20:4-20:25::[Warning] [W0057] undefined macro 'normalDepAssertEqual/2' 20:4-20:25::[Error] [E1508] undefined macro 'normalDepAssertEqual/2' diff --git a/crates/hir/src/body/lower.rs b/crates/hir/src/body/lower.rs index 7408ece032..ae7236754b 100644 --- a/crates/hir/src/body/lower.rs +++ b/crates/hir/src/body/lower.rs @@ -118,6 +118,8 @@ pub struct Ctx<'a> { // This means we need to lower a Var specially when processing a SSR // template, where if it has a prefix of `_@` it is a placeholder. in_ssr: bool, + // Track when we're lowering the RHS of a define preprocessor directive + in_macro_rhs: bool, // Diagnostics collected during body lowering diagnostics: Vec, } @@ -156,6 +158,7 @@ impl<'a> Ctx<'a> { starting_stack_size: 1, macro_source_map: FxHashMap::default(), in_ssr: false, + in_macro_rhs: false, diagnostics: Vec::new(), } } @@ -407,7 +410,9 @@ impl<'a> Ctx<'a> { let replacement = define.replacement(); match replacement { Some(MacroDefReplacement::Expr(expr)) => { + self.in_macro_rhs = true; let expr = self.lower_expr(&expr); + self.in_macro_rhs = false; let (body, source_map) = self.finish(); (DefineBody { body, expr }, source_map) } @@ -3023,6 +3028,17 @@ impl<'a> Ctx<'a> { } fn record_unresolved_macro(&mut self, call: &ast::MacroCallExpr) { + // If we're in a macro RHS and this is ?FUNCTION_NAME or ?FUNCTION_ARITY, skip the diagnostic + if self.in_macro_rhs { + let macro_name = self.macro_call_name(call.name()); + if let MacroCallName::Var(var) = macro_name { + let name_str = var.as_string(self.db.upcast()); + if name_str == "FUNCTION_NAME" || name_str == "FUNCTION_ARITY" { + return; + } + } + } + let source = InFileAstPtr::new(self.curr_file_id(), AstPtr::new(call).cast().unwrap()); self.diagnostics.push(BodyDiagnostic { source, diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index e82fe79468..9621ab85d6 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -67,7 +67,9 @@ use elp_text_edit::TextEdit; use elp_types_db::TypedSemantic; use fxhash::FxHashMap; use fxhash::FxHashSet; +use hir::FormIdx; use hir::InFile; +use hir::PPDirective; use hir::Semantic; use hir::db::DefDatabase; use hir::fold::MacroStrategy; @@ -1400,6 +1402,7 @@ pub fn native_diagnostics( } res.append(&mut form_missing_separator_diagnostics(&parse)); + res.extend(get_hir_diagnostics(db, file_id)); adhoc_semantic_diagnostics .iter() @@ -1743,6 +1746,143 @@ pub fn filter_diagnostics(diagnostics: Vec, code: DiagnosticCode) -> diagnostics.into_iter().filter(|d| d.code == code).collect() } +/// Retrieve all BodyDiagnostic values from the BodySourceMaps from lowering +/// all the bodies for a given FileId. +/// +/// This function iterates through all forms in the file and collects diagnostics +/// from the BodySourceMaps associated with each form's body. +pub fn collect_body_diagnostics(db: &RootDatabase, file_id: FileId) -> Vec { + let sema = Semantic::new(db); + let form_list = sema.form_list(file_id); + let mut diagnostics = Vec::new(); + + for form_idx in form_list.forms().iter() { + let body_map = match form_idx { + FormIdx::FunctionClause(function_clause_id) => { + let (_, body_map) = sema + .db + .function_clause_body_with_source(InFile::new(file_id, *function_clause_id)); + Some(body_map) + } + FormIdx::TypeAlias(type_alias_id) => { + let (_, body_map) = sema + .db + .type_body_with_source(InFile::new(file_id, *type_alias_id)); + Some(body_map) + } + FormIdx::Spec(spec_id) => { + let (_, body_map) = sema + .db + .spec_body_with_source(InFile::new(file_id, *spec_id)); + Some(body_map) + } + FormIdx::Callback(callback_id) => { + let (_, body_map) = sema + .db + .callback_body_with_source(InFile::new(file_id, *callback_id)); + Some(body_map) + } + FormIdx::Record(record_id) => { + let (_, body_map) = sema + .db + .record_body_with_source(InFile::new(file_id, *record_id)); + Some(body_map) + } + FormIdx::Attribute(attribute_id) => { + let (_, body_map) = sema + .db + .attribute_body_with_source(InFile::new(file_id, *attribute_id)); + Some(body_map) + } + FormIdx::CompileOption(compile_option_id) => { + let (_, body_map) = sema + .db + .compile_body_with_source(InFile::new(file_id, *compile_option_id)); + Some(body_map) + } + FormIdx::PPDirective(idx) => { + if let PPDirective::Define(define_id) = &form_list[*idx] { + let (_, body_map) = sema + .db + .define_body_with_source(InFile::new(file_id, *define_id)); + Some(body_map) + } else { + None + } + } + _ => None, + }; + + if let Some(body_map) = body_map { + diagnostics.extend_from_slice(body_map.diagnostics()); + } + } + + diagnostics +} + +/// Convert HIR body diagnostics to IDE Diagnostics. +/// This function takes the diagnostics collected during HIR lowering and converts +/// them to the IDE Diagnostic format for display to users. +pub fn get_hir_diagnostics(db: &RootDatabase, file_id: FileId) -> Vec { + let body_diagnostics = collect_body_diagnostics(db, file_id); + + body_diagnostics + .into_iter() + .filter_map(|body_diag| { + let full_range = body_diag.source.range(); + + // Only include diagnostics for the requested file + if full_range.file_id != file_id { + return None; + } + + let (code, message, range) = match body_diag.kind { + hir::BodyDiagnosticKind::UnresolvedMacro => { + // Get the macro call AST node to extract name and arity + let macro_call = body_diag.source.to_ast(db); + let macro_name = macro_call + .name() + .map(|name| name.to_string()) + .unwrap_or_else(|| "?".to_string()); + + let message = match macro_call.arity() { + Some(arity) => format!("undefined macro '{}/{}'", macro_name, arity), + None => format!("undefined macro '{}'", macro_name), + }; + + // For macros with arguments, only highlight the name part, not the full call + let range = macro_call + .name() + .map(|name| { + // Get the syntax range of just the macro name + let name_range = name.syntax().text_range(); + // Include the '?' prefix by extending one character to the left + if name_range.start() > 0.into() { + TextRange::new( + name_range.start() - TextSize::from(1), + name_range.end(), + ) + } else { + name_range + } + }) + .unwrap_or(full_range.range); + + (DiagnosticCode::HirUnresolvedMacro, message, range) + } + }; + + Some( + Diagnostic::new(code, message, range) + // We set the severity to Warning for now, until we have cleaned + // up the code base from this diagnostic + .with_severity(Severity::Warning), + ) + }) + .collect() +} + fn no_module_definition_diagnostic( diagnostics: &mut Vec, parse: &Parse, @@ -3750,4 +3890,22 @@ baz(1)->4. "#, ); } + + #[test] + fn no_unused_macro_in_macro_rhs_for_function_name() { + let config = DiagnosticsConfig::default() + .set_experimental(true) + .disable(DiagnosticCode::UnspecificInclude) + .disable(DiagnosticCode::BinaryStringToSigil); + check_diagnostics_with_config( + config, + r#" + //- /my_app/src/a_file.erl + -module(a_file). + -define(A_MACRO, ?FUNCTION_NAME). + foo() -> ?A_MACRO. + + "#, + ); + } } diff --git a/crates/ide/src/diagnostics/unused_macro.rs b/crates/ide/src/diagnostics/unused_macro.rs index 89f00e671b..c25f5990a4 100644 --- a/crates/ide/src/diagnostics/unused_macro.rs +++ b/crates/ide/src/diagnostics/unused_macro.rs @@ -123,7 +123,9 @@ mod tests { #[track_caller] pub(crate) fn check_diagnostics(fixture: &str) { - let config = DiagnosticsConfig::default().disable(DiagnosticCode::UndefinedFunction); + let config = DiagnosticsConfig::default() + .disable(DiagnosticCode::UndefinedFunction) + .disable(DiagnosticCode::HirUnresolvedMacro); check_diagnostics_with_config(config, fixture) } diff --git a/crates/ide/src/tests.rs b/crates/ide/src/tests.rs index a32b7e3044..9b0cf18341 100644 --- a/crates/ide/src/tests.rs +++ b/crates/ide/src/tests.rs @@ -376,7 +376,8 @@ pub(crate) fn check_diagnostics(fixture: &str) { let config = DiagnosticsConfig::default() .set_experimental(true) .disable(DiagnosticCode::UnspecificInclude) - .disable(DiagnosticCode::BinaryStringToSigil); + .disable(DiagnosticCode::BinaryStringToSigil) + .disable(DiagnosticCode::HirUnresolvedMacro); check_diagnostics_with_config(config, fixture) } @@ -524,6 +525,7 @@ pub fn check_no_parse_errors(analysis: &Analysis, file_id: FileId) { let config = DiagnosticsConfig::default() .disable(DiagnosticCode::UnspecificInclude) .disable(DiagnosticCode::UndefinedFunction) + .disable(DiagnosticCode::HirUnresolvedMacro) .disable(DiagnosticCode::NoCatch); check_no_parse_errors_with_config(analysis, file_id, &config, &vec![]); } diff --git a/crates/ide_db/src/diagnostic_code.rs b/crates/ide_db/src/diagnostic_code.rs index 860d61befa..ecde7d1bfb 100644 --- a/crates/ide_db/src/diagnostic_code.rs +++ b/crates/ide_db/src/diagnostic_code.rs @@ -90,6 +90,7 @@ pub enum DiagnosticCode { NoNoWarnSuppressions, CouldBeAStringLiteral, ListsReverseAppend, + HirUnresolvedMacro, // Wrapper for erlang service diagnostic codes ErlangService(String), @@ -250,6 +251,7 @@ impl DiagnosticCode { DiagnosticCode::NoNoWarnSuppressions => "W0054".to_string(), DiagnosticCode::CouldBeAStringLiteral => "W0055".to_string(), DiagnosticCode::ListsReverseAppend => "W0056".to_string(), + DiagnosticCode::HirUnresolvedMacro => "W0057".to_string(), DiagnosticCode::ErlangService(c) => c.to_string(), DiagnosticCode::Eqwalizer(c) => format!("eqwalizer: {c}"), DiagnosticCode::AdHoc(c) => format!("ad-hoc: {c}"), @@ -347,6 +349,7 @@ impl DiagnosticCode { DiagnosticCode::NoNoWarnSuppressions => "no_nowarn_suppressions".to_string(), DiagnosticCode::CouldBeAStringLiteral => "could_be_a_binary_string_literal".to_string(), DiagnosticCode::ListsReverseAppend => "lists_reverse_append".to_string(), + DiagnosticCode::HirUnresolvedMacro => "hir_unresolved_macro".to_string(), DiagnosticCode::ErlangService(c) => c.to_string(), DiagnosticCode::Eqwalizer(c) => c.to_string(), @@ -524,6 +527,7 @@ impl DiagnosticCode { DiagnosticCode::NoErrorLogger => false, DiagnosticCode::NoNoWarnSuppressions => false, DiagnosticCode::ListsReverseAppend => false, + DiagnosticCode::HirUnresolvedMacro => false, DiagnosticCode::BinaryStringToSigil => false, DiagnosticCode::ErlangService(_) => false, diff --git a/test_projects/buck_tests_2/check_include/src/top_includer.erl b/test_projects/buck_tests_2/check_include/src/top_includer.erl index ebe984a0df..79f50ee108 100644 --- a/test_projects/buck_tests_2/check_include/src/top_includer.erl +++ b/test_projects/buck_tests_2/check_include/src/top_includer.erl @@ -2,13 +2,13 @@ -compile(warn_missing_spec_all). --export([foo/0]). - -include_lib("check_include_separate_1/include/top_includer.hrl"). --spec foo() -> ok. +-define(A_MACRO, ?FUNCTION_NAME). + foo() -> ?FIRST, + ?A_MACRO ?SECOND, ?THIRD(41,34), ?FUNCTION_NAME. diff --git a/website/docs/erlang-error-index/w/W0057.md b/website/docs/erlang-error-index/w/W0057.md new file mode 100644 index 0000000000..990c4648b6 --- /dev/null +++ b/website/docs/erlang-error-index/w/W0057.md @@ -0,0 +1,106 @@ +--- +sidebar_position: 57 +--- + +# W0057 - Undefined macro + +## Error + +```erlang +-module(example). + +foo() -> + ?UNDEFINED_MACRO. +%% ^^^^^^^^^^^^^^^^ error: W0057: undefined macro 'UNDEFINED_MACRO' + +bar(X) -> + ?UNDEFINED_WITH_ARGS(X, 42). +%% ^^^^^^^^^^^^^^^^^^^^ error: W0057: undefined macro 'UNDEFINED_WITH_ARGS/2' +``` + +This diagnostic is triggered when a macro usage cannot be resolved in ELP native +diagnostic processing. The macro is referenced in the code but has not been defined +in the current file or any included header files. + +**Important:** This diagnostic corresponds to Erlang service diagnostics +**E1507** (for macros without arguments) and **E1508** (for macros with +arguments). W0057 only appears when the corresponding Erlang service diagnostic +does not appear. If you see W0057 in a Buck project, it is likely a sign that +the project is not defined properly in your TARGETS/BUCK file. + +## Common Causes + +1. **Missing macro definition**: The macro has not been defined with + `-define()`. +2. **Missing include file**: The macro is defined in a header file that has not + been included. +3. **Typo in macro name**: The macro name is misspelled. +4. **Conditional compilation**: The macro is defined within a conditional block + (e.g., `-ifdef()`) that is not active. + +## Fix + +### Define the macro + +If the macro is not defined anywhere, add a `-define()` directive: + +```erlang +-module(example). + +-define(UNDEFINED_MACRO, 42). +-define(UNDEFINED_WITH_ARGS(X, Y), X + Y). + +foo() -> + ?UNDEFINED_MACRO. + +bar(X) -> + ?UNDEFINED_WITH_ARGS(X, 42). +``` + +### Include the header file + +If the macro is defined in a header file, add the appropriate `-include()` or +`-include_lib()` directive: + +```erlang +-module(example). + +-include("macros.hrl"). + +foo() -> + ?UNDEFINED_MACRO. +``` + +### Fix typos + +If the macro name is misspelled, correct it to match the definition: + +```erlang +-module(example). + +-define(DEFINED_MACRO, 42). + +foo() -> + ?DEFINED_MACRO. % Fixed typo +``` + +## Relationship to Erlang Service Diagnostics + +W0057 is filtered out when the Erlang service reports the corresponding +diagnostics: + +- **E1507**: undefined macro (without arguments) +- **E1508**: undefined macro (with arguments) + +When both diagnostics detect the same issue, only the Erlang service diagnostic +will be shown to avoid duplication. + +## Buck Project Configuration + +If you see W0057 in a Buck project, this typically indicates that your TARGETS +file is not properly configured. The Erlang service relies on Buck to understand +the project structure, dependencies, and include paths. Check that: + +- All application dependencies are declared in the `deps` attribute +- Include paths are properly configured +- The macro definition is in an accessible dependency From ed673119e4bd5c154311cb51bb9d484dbd2c8c5d Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Fri, 14 Nov 2025 04:01:31 -0800 Subject: [PATCH 016/142] 4/n: include: do not report W0057 if it is already reported by the erlang service Summary: As title. Reviewed By: TD5 Differential Revision: D86963332 fbshipit-source-id: ffb3020fbfd6b09a273d4e34cb28f353fa12deab --- .../resolves_generated_includes.stdout | 3 +- .../include_lib_non_dependency_fails.stdout | 3 +- .../include_lib_non_dependency_rebar.stdout | 3 +- crates/ide/src/diagnostics.rs | 57 +++++++++++++++++-- 4 files changed, 56 insertions(+), 10 deletions(-) diff --git a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout index 8ca4ec00a2..d38837fe1d 100644 --- a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout +++ b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout @@ -9,8 +9,7 @@ Diagnostics reported in 5 modules: 0:8-0:18::[WeakWarning] [W0046] The module is not documented. eqwalizer: 1 1:8-1:17::[WeakWarning] [W0046] The module is not documented. - top_includer: 3 - 12:4-12:10::[Warning] [W0057] undefined macro 'THIRD/2' + top_includer: 2 0:8-0:20::[WeakWarning] [W0046] The module is not documented. 12:4-12:10::[Error] [E1508] undefined macro 'THIRD/2' wa_buck2_module_search: 6 diff --git a/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_fails.stdout b/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_fails.stdout index 33dc4b534a..c0fe03edea 100644 --- a/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_fails.stdout +++ b/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_fails.stdout @@ -1,9 +1,8 @@ module specified: main_app Diagnostics reported in 1 modules: - main_app: 6 + main_app: 5 4:13-4:55::[Error] [E1516] can't find include lib "external_app/include/external_header.hrl" 11:0-11:42::[Warning] [W0020] Unused file: stdlib/include/assert.hrl 14:9-14:21::[Hint] [W0037] Unspecific include. 16:9-16:24::[Error] [L1227] function test_function/0 undefined - 23:4-23:19::[Warning] [W0057] undefined macro 'EXTERNAL_MACRO' 23:4-23:19::[Error] [E1507] undefined macro 'EXTERNAL_MACRO' diff --git a/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_rebar.stdout b/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_rebar.stdout index dceea92cf3..513197d889 100644 --- a/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_rebar.stdout +++ b/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_rebar.stdout @@ -1,8 +1,7 @@ module specified: main_app Diagnostics reported in 1 modules: - main_app: 5 + main_app: 4 11:0-11:42::[Warning] [W0020] Unused file: stdlib/include/assert.hrl 14:9-14:21::[Error] [E1516] can't find include file "assert.hrl" 16:9-16:24::[Error] [L1227] function test_function/0 undefined - 20:4-20:25::[Warning] [W0057] undefined macro 'normalDepAssertEqual/2' 20:4-20:25::[Error] [E1508] undefined macro 'normalDepAssertEqual/2' diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 9621ab85d6..960c8762a2 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -2817,10 +2817,59 @@ pub fn attach_related_diagnostics( .filter(|(k, _)| !undefineds_to_remove.contains(k)) .flat_map(|(_, v)| v); - native - .normal - .into_iter() - .chain(erlang_service.normal) + // Step 4. + // Split erlang service normal diagnostics into undefined macro diagnostics (E1507/E1508), + // and other diagnostics + let mut erlang_service_undefined_macros = Vec::new(); + let mut erlang_service_other = Vec::new(); + + for d in erlang_service.normal { + match &d.code { + DiagnosticCode::ErlangService(code) if code == "E1507" || code == "E1508" => { + erlang_service_undefined_macros.push(d); + } + _ => { + erlang_service_other.push(d); + } + } + } + + // Collect E1507/E1508 from labeled_undefined_errors for filtering + let undefined_macros_from_labeled: Vec<_> = erlang_service_undefined_not_related + .clone() + .filter(|d| { + matches!(&d.code, DiagnosticCode::ErlangService(code) if code == "E1507" || code == "E1508") + }) + .cloned() + .collect(); + + // Combine all E1507/E1508 diagnostics for filtering (clone to avoid borrow issues) + let all_undefined_macros: Vec<_> = erlang_service_undefined_macros + .iter() + .cloned() + .chain(undefined_macros_from_labeled) + .collect(); + + // Step 5. + // Filter out W0056 diagnostics if there's a matching E1507/E1508 for the same macro + let filtered_native_normal = native.normal.into_iter().filter(|d| { + if d.code != DiagnosticCode::HirUnresolvedMacro { + return true; // Keep non-W0056 diagnostics + } + + // Check if there's a matching E1507/E1508 diagnostic + let has_matching_erlang_service = all_undefined_macros.iter().any(|es_diag| { + // Check if ranges overlap + d.range.intersect(es_diag.range).is_some() + }); + + // Keep W0056 only if there's no matching E1507/E1508 + !has_matching_erlang_service + }); + + filtered_native_normal + .chain(erlang_service_other) + .chain(erlang_service_undefined_macros) .chain(syntax_errors_with_related) .chain(erlang_service_undefined_not_related.cloned()) // TODO:AZ: consider returning an iterator From a8bb2de049ce5933b7d0910eaf88d9c61bd2305a Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Fri, 14 Nov 2025 04:35:24 -0800 Subject: [PATCH 017/142] 5/n: include: report HIR unresolved include files W0058 Summary: For a buck project, if the dependencies are not set up properly for ELP use, it may compile but ELP is unable to resolve a nested include file when processing the middle ones in a chain. This diff adds a check for the HIR include file resolution so this can be detected. For a properly configured buck project this is redundant, because the Erlang service already reports `E1516` for this. The next diff filters out occurrences generated here in that case. Reviewed By: TD5 Differential Revision: D86963331 fbshipit-source-id: 356cefeba61e306fb3541a21ba16c189aa8c8faa --- .../resolves_generated_includes.stdout | 3 +- .../parse_elp_custom_build_tool.jsonl | 3 +- .../include_lib_non_dependency_fails.stdout | 3 +- .../include_lib_non_dependency_rebar.stdout | 3 +- crates/hir/src/body.rs | 22 ++-- crates/hir/src/body/lower.rs | 7 +- crates/hir/src/form_list.rs | 17 +++ crates/hir/src/lib.rs | 1 - crates/ide/src/diagnostics.rs | 65 ++++++++--- crates/ide/src/tests.rs | 3 +- crates/ide_db/src/diagnostic_code.rs | 4 + .../include/top_includer.hrl | 1 + website/docs/erlang-error-index/w/W0058.md | 108 ++++++++++++++++++ 13 files changed, 207 insertions(+), 33 deletions(-) create mode 100644 website/docs/erlang-error-index/w/W0058.md diff --git a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout index d38837fe1d..59714ddd01 100644 --- a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout +++ b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout @@ -9,8 +9,9 @@ Diagnostics reported in 5 modules: 0:8-0:18::[WeakWarning] [W0046] The module is not documented. eqwalizer: 1 1:8-1:17::[WeakWarning] [W0046] The module is not documented. - top_includer: 2 + top_includer: 3 0:8-0:20::[WeakWarning] [W0046] The module is not documented. + 0:1-0:1::[Error] [L0000] Issue in included file 12:4-12:10::[Error] [E1508] undefined macro 'THIRD/2' wa_buck2_module_search: 6 19:0-19:18::[WeakWarning] [W0040] The function is non-trivial, exported, but not documented. diff --git a/crates/elp/src/resources/test/custom_build_tool/parse_elp_custom_build_tool.jsonl b/crates/elp/src/resources/test/custom_build_tool/parse_elp_custom_build_tool.jsonl index ac87aa6ec4..4acde049ae 100644 --- a/crates/elp/src/resources/test/custom_build_tool/parse_elp_custom_build_tool.jsonl +++ b/crates/elp/src/resources/test/custom_build_tool/parse_elp_custom_build_tool.jsonl @@ -1,4 +1,5 @@ module specified: app_b Diagnostics reported in 1 modules: - app_b: 1 + app_b: 2 + 2:13-2:38::[Warning] [W0058] can't find include lib "app_a/include/app_b.hrl" 2:13-2:38::[Error] [E1516] can't find include lib "app_a/include/app_b.hrl" diff --git a/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_fails.stdout b/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_fails.stdout index c0fe03edea..87b946c385 100644 --- a/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_fails.stdout +++ b/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_fails.stdout @@ -1,6 +1,7 @@ module specified: main_app Diagnostics reported in 1 modules: - main_app: 5 + main_app: 6 + 4:13-4:55::[Warning] [W0058] can't find include lib "external_app/include/external_header.hrl" 4:13-4:55::[Error] [E1516] can't find include lib "external_app/include/external_header.hrl" 11:0-11:42::[Warning] [W0020] Unused file: stdlib/include/assert.hrl 14:9-14:21::[Hint] [W0037] Unspecific include. diff --git a/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_rebar.stdout b/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_rebar.stdout index 513197d889..198330cb2f 100644 --- a/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_rebar.stdout +++ b/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_rebar.stdout @@ -1,7 +1,8 @@ module specified: main_app Diagnostics reported in 1 modules: - main_app: 4 + main_app: 5 11:0-11:42::[Warning] [W0020] Unused file: stdlib/include/assert.hrl + 14:9-14:21::[Warning] [W0058] can't find include file "assert.hrl" 14:9-14:21::[Error] [E1516] can't find include file "assert.hrl" 16:9-16:24::[Error] [L1227] function test_function/0 undefined 20:4-20:25::[Error] [E1508] undefined macro 'normalDepAssertEqual/2' diff --git a/crates/hir/src/body.rs b/crates/hir/src/body.rs index 0252a44ee1..9ebac17c66 100644 --- a/crates/hir/src/body.rs +++ b/crates/hir/src/body.rs @@ -43,6 +43,7 @@ use crate::FormList; use crate::FunctionClause; use crate::FunctionClauseId; use crate::InFile; +use crate::IncludeAttributeId; use crate::Literal; use crate::Name; use crate::NameArity; @@ -1157,17 +1158,20 @@ pub type MacroSource = InFileAstPtr; /// Lightweight diagnostic for body lowering #[derive(Debug, Clone, PartialEq, Eq)] -pub struct BodyDiagnostic { - /// Location of the problematic construct - pub source: MacroSource, - /// Type of diagnostic - pub kind: BodyDiagnosticKind, +pub enum BodyDiagnostic { + /// Macro failed to expand/resolve + UnresolvedMacro(MacroSource), + /// Include failed to resolve + UnresolvedInclude(InFile), } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum BodyDiagnosticKind { - /// Macro failed to expand/resolve - UnresolvedMacro, +impl BodyDiagnostic { + pub fn file_id(&self) -> FileId { + match self { + BodyDiagnostic::UnresolvedMacro(src) => src.file_id(), + BodyDiagnostic::UnresolvedInclude(include) => include.file_id, + } + } } #[derive(Clone, PartialEq, Eq, Debug, Hash)] diff --git a/crates/hir/src/body/lower.rs b/crates/hir/src/body/lower.rs index ae7236754b..4abf0737b2 100644 --- a/crates/hir/src/body/lower.rs +++ b/crates/hir/src/body/lower.rs @@ -39,7 +39,6 @@ use crate::BasedInteger; use crate::BinarySeg; use crate::Body; use crate::BodyDiagnostic; -use crate::BodyDiagnosticKind; use crate::BodySourceMap; use crate::CRClause; use crate::CallTarget; @@ -3040,10 +3039,8 @@ impl<'a> Ctx<'a> { } let source = InFileAstPtr::new(self.curr_file_id(), AstPtr::new(call).cast().unwrap()); - self.diagnostics.push(BodyDiagnostic { - source, - kind: BodyDiagnosticKind::UnresolvedMacro, - }); + self.diagnostics + .push(BodyDiagnostic::UnresolvedMacro(source)); } fn curr_file_id(&self) -> FileId { diff --git a/crates/hir/src/form_list.rs b/crates/hir/src/form_list.rs index 560befbb4b..c33af25622 100644 --- a/crates/hir/src/form_list.rs +++ b/crates/hir/src/form_list.rs @@ -650,6 +650,23 @@ impl IncludeAttribute { pub fn range(&self, db: &dyn DefDatabase, file_id: FileId) -> TextRange { self.form_id().get_ast(db, file_id).syntax().text_range() } + + pub fn file_range(&self, db: &dyn DefDatabase, file_id: FileId) -> TextRange { + let form = self.form_id().get_ast(db, file_id); + match form { + ast::Form::PreprocessorDirective(ast::PreprocessorDirective::PpInclude(pp_include)) => { + pp_include + .include_range() + .unwrap_or_else(|| pp_include.text_range()) + } + ast::Form::PreprocessorDirective(ast::PreprocessorDirective::PpIncludeLib( + pp_include_lib, + )) => pp_include_lib + .include_range() + .unwrap_or_else(|| pp_include_lib.text_range()), + _ => form.syntax().text_range(), + } + } } /// -deprecated diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 9f51b0d70f..1ec75fd25e 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -36,7 +36,6 @@ pub use body::AnyAttribute; pub use body::AttributeBody; pub use body::Body; pub use body::BodyDiagnostic; -pub use body::BodyDiagnosticKind; pub use body::BodyOrigin; pub use body::BodySourceMap; pub use body::DefineBody; diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 960c8762a2..fd0062122c 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -1801,13 +1801,29 @@ pub fn collect_body_diagnostics(db: &RootDatabase, file_id: FileId) -> Vec { - if let PPDirective::Define(define_id) = &form_list[*idx] { - let (_, body_map) = sema - .db - .define_body_with_source(InFile::new(file_id, *define_id)); - Some(body_map) - } else { - None + match &form_list[*idx] { + PPDirective::Define(define_id) => { + let (_, body_map) = sema + .db + .define_body_with_source(InFile::new(file_id, *define_id)); + Some(body_map) + } + PPDirective::Include(include_id) => { + // Try to resolve the include + if sema + .db + .resolve_include(InFile::new(file_id, *include_id)) + .is_none() + { + // Include resolution failed, create a diagnostic + diagnostics.push(hir::BodyDiagnostic::UnresolvedInclude(InFile::new( + file_id, + *include_id, + ))); + } + None + } + _ => None, } } _ => None, @@ -1830,17 +1846,18 @@ pub fn get_hir_diagnostics(db: &RootDatabase, file_id: FileId) -> Vec { + let (code, message, range) = match &body_diag { + hir::BodyDiagnostic::UnresolvedMacro(macro_source) => { + // Determine range for UnresolvedMacro + let full_range = macro_source.range(); + // Get the macro call AST node to extract name and arity - let macro_call = body_diag.source.to_ast(db); + let macro_call = macro_source.to_ast(db); let macro_name = macro_call .name() .map(|name| name.to_string()) @@ -1871,6 +1888,28 @@ pub fn get_hir_diagnostics(db: &RootDatabase, file_id: FileId) -> Vec { + // Get the include attribute from the form_list + let sema = Semantic::new(db); + let form_list = sema.form_list(file_id); + let include_attr = &form_list[include.value]; + + // Extract path and range from IncludeAttribute + let path = include_attr.path().to_string(); + let range = include_attr.file_range(db, file_id); + + // Use appropriate message based on include type + let message = match include_attr { + hir::IncludeAttribute::Include { .. } => { + format!("can't find include file \"{}\"", path) + } + hir::IncludeAttribute::IncludeLib { .. } => { + format!("can't find include lib \"{}\"", path) + } + }; + + (DiagnosticCode::HirUnresolvedInclude, message, range) + } }; Some( diff --git a/crates/ide/src/tests.rs b/crates/ide/src/tests.rs index 9b0cf18341..bd607b5d30 100644 --- a/crates/ide/src/tests.rs +++ b/crates/ide/src/tests.rs @@ -377,7 +377,8 @@ pub(crate) fn check_diagnostics(fixture: &str) { .set_experimental(true) .disable(DiagnosticCode::UnspecificInclude) .disable(DiagnosticCode::BinaryStringToSigil) - .disable(DiagnosticCode::HirUnresolvedMacro); + .disable(DiagnosticCode::HirUnresolvedMacro) + .disable(DiagnosticCode::HirUnresolvedInclude); check_diagnostics_with_config(config, fixture) } diff --git a/crates/ide_db/src/diagnostic_code.rs b/crates/ide_db/src/diagnostic_code.rs index ecde7d1bfb..cee17251e8 100644 --- a/crates/ide_db/src/diagnostic_code.rs +++ b/crates/ide_db/src/diagnostic_code.rs @@ -91,6 +91,7 @@ pub enum DiagnosticCode { CouldBeAStringLiteral, ListsReverseAppend, HirUnresolvedMacro, + HirUnresolvedInclude, // Wrapper for erlang service diagnostic codes ErlangService(String), @@ -252,6 +253,7 @@ impl DiagnosticCode { DiagnosticCode::CouldBeAStringLiteral => "W0055".to_string(), DiagnosticCode::ListsReverseAppend => "W0056".to_string(), DiagnosticCode::HirUnresolvedMacro => "W0057".to_string(), + DiagnosticCode::HirUnresolvedInclude => "W0058".to_string(), DiagnosticCode::ErlangService(c) => c.to_string(), DiagnosticCode::Eqwalizer(c) => format!("eqwalizer: {c}"), DiagnosticCode::AdHoc(c) => format!("ad-hoc: {c}"), @@ -350,6 +352,7 @@ impl DiagnosticCode { DiagnosticCode::CouldBeAStringLiteral => "could_be_a_binary_string_literal".to_string(), DiagnosticCode::ListsReverseAppend => "lists_reverse_append".to_string(), DiagnosticCode::HirUnresolvedMacro => "hir_unresolved_macro".to_string(), + DiagnosticCode::HirUnresolvedInclude => "hir_unresolved_include".to_string(), DiagnosticCode::ErlangService(c) => c.to_string(), DiagnosticCode::Eqwalizer(c) => c.to_string(), @@ -528,6 +531,7 @@ impl DiagnosticCode { DiagnosticCode::NoNoWarnSuppressions => false, DiagnosticCode::ListsReverseAppend => false, DiagnosticCode::HirUnresolvedMacro => false, + DiagnosticCode::HirUnresolvedInclude => false, DiagnosticCode::BinaryStringToSigil => false, DiagnosticCode::ErlangService(_) => false, diff --git a/test_projects/buck_tests_2/check_include_separate_1/include/top_includer.hrl b/test_projects/buck_tests_2/check_include_separate_1/include/top_includer.hrl index c117baed75..5c88a7806e 100644 --- a/test_projects/buck_tests_2/check_include_separate_1/include/top_includer.hrl +++ b/test_projects/buck_tests_2/check_include_separate_1/include/top_includer.hrl @@ -1,4 +1,5 @@ -include_lib("check_include_separate_2/include/separate_include.hrl"). +-include("does_not_exist.hrl"). -define(FIRST, 1). diff --git a/website/docs/erlang-error-index/w/W0058.md b/website/docs/erlang-error-index/w/W0058.md new file mode 100644 index 0000000000..acad4c2d79 --- /dev/null +++ b/website/docs/erlang-error-index/w/W0058.md @@ -0,0 +1,108 @@ +--- +sidebar_position: 58 +--- + +# W0058 - Can't find include file + +## Error + +```erlang +-module(example). + +-include("missing_header.hrl"). +%% error: W0058: can't find include file "missing_header.hrl" + +-include_lib("missing_app/include/header.hrl"). +%% error: W0058: can't find include lib "missing_app/include/header.hrl" +``` + +This diagnostic is triggered when an include file referenced by `-include()` or +`-include_lib()` cannot be resolved by the ELP native diagnostic processing. +The file path cannot be found in the search paths available to ELP. + +**Important:** This diagnostic corresponds to Erlang service diagnostic +**E1516** (can't find include file/lib). W0058 only appears when the +corresponding Erlang service diagnostic does not appear. If you see W0058 in a +Buck project, it is likely a sign that the project is not defined properly in +your TARGETS file. + +## Common Causes + +1. **Missing file**: The include file does not exist at the specified path. +2. **Incorrect path**: The path to the include file is incorrect or misspelled. +3. **Missing application dependency**: For `-include_lib()`, the referenced + application is not listed as a dependency. +4. **Incorrect include path configuration**: The directory containing the header + file is not in the configured include paths. + +## Fix + +### Verify the file exists + +Make sure the include file exists at the expected location. + +### Fix the path + +Correct the path to match the actual file location: + +```erlang +-module(example). + +% Correct path to the include file +-include("correct_header.hrl"). + +% Correct application name and path +-include_lib("correct_app/include/header.hrl"). +``` + +### Add missing dependency + +For `-include_lib()`, ensure the application is listed as a dependency in your +build configuration: + +**rebar.config:** + +```erlang +{deps, [ + missing_app +]}. +``` + +**Buck TARGETS file:** + +```python +erlang_app( + name = "my_app", + deps = [ + "//path/to:missing_app", + ], +) +``` + +### Configure include paths + +Ensure the directory containing the header file is in your project's include +paths configuration. + +## Relationship to Erlang Service Diagnostics + +W0058 is filtered out when the Erlang service reports the corresponding +diagnostic: + +- **E1516**: can't find include file/lib + +When both diagnostics detect the same issue, only the Erlang service diagnostic +will be shown to avoid duplication. + +## Buck Project Configuration + +If you see W0058 in a Buck project, this typically indicates that your TARGETS +file is not properly configured. The Erlang service relies on Buck to understand +the project structure, dependencies, and include paths. Check that: + +- All application dependencies are declared in the `deps` attribute +- For `-include_lib()` directives, the referenced application is included as a + dependency +- Include paths are properly configured with the `include` or `include_lib` + attributes +- The header file is defined in a dependency of the buck target holding the current file From 276297e366734a1fffee06f8cd0af492d8690536 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Fri, 14 Nov 2025 04:35:24 -0800 Subject: [PATCH 018/142] 6/n: include: do not report W0058 if is is already reported by the erlang service Summary: In a properly configured buck2 project, W0058 and E1516 will always be reported at the same time. So do not report W0058 if there is already a E1516. Reviewed By: TD5 Differential Revision: D86963330 fbshipit-source-id: fe1aaaa71330da059ed697d187542e3cb470e965 --- .../parse_elp_custom_build_tool.jsonl | 3 +- .../include_lib_non_dependency_fails.stdout | 3 +- .../include_lib_non_dependency_rebar.stdout | 3 +- crates/ide/src/diagnostics.rs | 51 +++++++++++++++---- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/crates/elp/src/resources/test/custom_build_tool/parse_elp_custom_build_tool.jsonl b/crates/elp/src/resources/test/custom_build_tool/parse_elp_custom_build_tool.jsonl index 4acde049ae..ac87aa6ec4 100644 --- a/crates/elp/src/resources/test/custom_build_tool/parse_elp_custom_build_tool.jsonl +++ b/crates/elp/src/resources/test/custom_build_tool/parse_elp_custom_build_tool.jsonl @@ -1,5 +1,4 @@ module specified: app_b Diagnostics reported in 1 modules: - app_b: 2 - 2:13-2:38::[Warning] [W0058] can't find include lib "app_a/include/app_b.hrl" + app_b: 1 2:13-2:38::[Error] [E1516] can't find include lib "app_a/include/app_b.hrl" diff --git a/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_fails.stdout b/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_fails.stdout index 87b946c385..c0fe03edea 100644 --- a/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_fails.stdout +++ b/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_fails.stdout @@ -1,7 +1,6 @@ module specified: main_app Diagnostics reported in 1 modules: - main_app: 6 - 4:13-4:55::[Warning] [W0058] can't find include lib "external_app/include/external_header.hrl" + main_app: 5 4:13-4:55::[Error] [E1516] can't find include lib "external_app/include/external_header.hrl" 11:0-11:42::[Warning] [W0020] Unused file: stdlib/include/assert.hrl 14:9-14:21::[Hint] [W0037] Unspecific include. diff --git a/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_rebar.stdout b/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_rebar.stdout index 198330cb2f..513197d889 100644 --- a/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_rebar.stdout +++ b/crates/elp/src/resources/test/include_lib_dependency_test/include_lib_non_dependency_rebar.stdout @@ -1,8 +1,7 @@ module specified: main_app Diagnostics reported in 1 modules: - main_app: 5 + main_app: 4 11:0-11:42::[Warning] [W0020] Unused file: stdlib/include/assert.hrl - 14:9-14:21::[Warning] [W0058] can't find include file "assert.hrl" 14:9-14:21::[Error] [E1516] can't find include file "assert.hrl" 16:9-16:24::[Error] [L1227] function test_function/0 undefined 20:4-20:25::[Error] [E1508] undefined macro 'normalDepAssertEqual/2' diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index fd0062122c..1afe56f0d4 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -2858,8 +2858,9 @@ pub fn attach_related_diagnostics( // Step 4. // Split erlang service normal diagnostics into undefined macro diagnostics (E1507/E1508), - // and other diagnostics + // unresolved include diagnostics (E1516), and other diagnostics in a single pass let mut erlang_service_undefined_macros = Vec::new(); + let mut erlang_service_unresolved_includes = Vec::new(); let mut erlang_service_other = Vec::new(); for d in erlang_service.normal { @@ -2867,6 +2868,9 @@ pub fn attach_related_diagnostics( DiagnosticCode::ErlangService(code) if code == "E1507" || code == "E1508" => { erlang_service_undefined_macros.push(d); } + DiagnosticCode::ErlangService(code) if code == "E1516" => { + erlang_service_unresolved_includes.push(d); + } _ => { erlang_service_other.push(d); } @@ -2882,6 +2886,13 @@ pub fn attach_related_diagnostics( .cloned() .collect(); + // Collect E1516 from labeled_undefined_errors for filtering + let unresolved_includes_from_labeled: Vec<_> = erlang_service_undefined_not_related + .clone() + .filter(|d| matches!(&d.code, DiagnosticCode::ErlangService(code) if code == "E1516")) + .cloned() + .collect(); + // Combine all E1507/E1508 diagnostics for filtering (clone to avoid borrow issues) let all_undefined_macros: Vec<_> = erlang_service_undefined_macros .iter() @@ -2889,26 +2900,46 @@ pub fn attach_related_diagnostics( .chain(undefined_macros_from_labeled) .collect(); + // Combine all E1516 diagnostics for filtering + let all_unresolved_includes: Vec<_> = erlang_service_unresolved_includes + .iter() + .cloned() + .chain(unresolved_includes_from_labeled) + .collect(); + // Step 5. // Filter out W0056 diagnostics if there's a matching E1507/E1508 for the same macro + // Filter out W0057 diagnostics if there's a matching E1516 for the same include let filtered_native_normal = native.normal.into_iter().filter(|d| { - if d.code != DiagnosticCode::HirUnresolvedMacro { - return true; // Keep non-W0056 diagnostics + if d.code == DiagnosticCode::HirUnresolvedMacro { + // Check if there's a matching E1507/E1508 diagnostic + let has_matching_erlang_service = all_undefined_macros.iter().any(|es_diag| { + // Check if ranges overlap + d.range.intersect(es_diag.range).is_some() + }); + + // Keep W0056 only if there's no matching E1507/E1508 + return !has_matching_erlang_service; } - // Check if there's a matching E1507/E1508 diagnostic - let has_matching_erlang_service = all_undefined_macros.iter().any(|es_diag| { - // Check if ranges overlap - d.range.intersect(es_diag.range).is_some() - }); + if d.code == DiagnosticCode::HirUnresolvedInclude { + // Check if there's a matching E1516 diagnostic + let has_matching_erlang_service = all_unresolved_includes.iter().any(|es_diag| { + // Check if ranges overlap + d.range.intersect(es_diag.range).is_some() + }); - // Keep W0056 only if there's no matching E1507/E1508 - !has_matching_erlang_service + // Keep W0057 only if there's no matching E1516 + return !has_matching_erlang_service; + } + + true // Keep all other diagnostics }); filtered_native_normal .chain(erlang_service_other) .chain(erlang_service_undefined_macros) + .chain(erlang_service_unresolved_includes) .chain(syntax_errors_with_related) .chain(erlang_service_undefined_not_related.cloned()) // TODO:AZ: consider returning an iterator From aca24c8fc39e9dd0bc538c27d289a2120fe92ddd Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Fri, 14 Nov 2025 10:05:06 -0800 Subject: [PATCH 019/142] Convert missing_compile_warn_missing_spec linter to use trait Summary: Even if the `missing_compile_warn_missing_spec` is supposed to be disabled by default, it still runs. This is due to a flawed logic in `diagnostics.rs`, originally introduced in D59272525. This is problematic, since open-source projects such as OTP see a huge number of errors when running the `elp lint` command without specifying a custom configuration. By converting the linter to use the new trait, we incidentally fix the bug. Two more linters use a `default_disabled: true` property in their descriptors: `undocumented_module` and `undocumented_function`. They will be converted next, and the logic to handle the `default_disabled` will eventually be removed. Reviewed By: alanz Differential Revision: D86978483 fbshipit-source-id: 5a89ff0fd1297a1e75db9d4b60d75b70f98d1e0c --- .../resolves_generated_includes.stdout | 6 +- ...e_elp_no_lint_specified_json_output.stdout | 7 - .../parse_elp_no_lint_specified_output.stdout | 21 +- .../test/linter/warnings_as_errors.stdout | 21 +- crates/ide/src/diagnostics.rs | 26 +- .../missing_compile_warn_missing_spec.rs | 289 ++++++++++-------- test_projects/linter/elp_lint_test2.toml | 3 + 7 files changed, 203 insertions(+), 170 deletions(-) diff --git a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout index 59714ddd01..b5ed320571 100644 --- a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout +++ b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout @@ -1,11 +1,9 @@ Reporting all diagnostics codes Diagnostics reported in 5 modules: - app_a: 3 - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. + app_a: 2 5:9-5:31::[WeakWarning] [W0037] Unspecific include. 0:8-0:13::[WeakWarning] [W0046] The module is not documented. - auto_gen_a: 2 - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. + auto_gen_a: 1 0:8-0:18::[WeakWarning] [W0046] The module is not documented. eqwalizer: 1 1:8-1:17::[WeakWarning] [W0046] The module is not documented. diff --git a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_json_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_json_output.stdout index 8b664bb080..b8924b40ed 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_json_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_json_output.stdout @@ -1,22 +1,16 @@ {"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":1,"char":1,"code":"ELP","severity":"error","name":"W0012 (compile-warn-missing-spec)","original":null,"replacement":null,"description":"Please add \"-compile(warn_missing_spec_all).\" to the module. If exported functions are not all specced, they need to be specced.\n\nFor more information see: /erlang-error-index/w/W0012","docPath":"website/docs/erlang-error-index/w/W0012.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.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":1,"char":9,"code":"ELP","severity":"disabled","name":"W0046 (undocumented_module)","original":null,"replacement":null,"description":"The module is not documented.\n\nFor more information see: /erlang-error-index/w/W0046","docPath":"website/docs/erlang-error-index/w/W0046.md"} {"path":"app_a/src/app_a.erl","line":13,"char":5,"code":"ELP","severity":"warning","name":"W0026 (unexported_function)","original":null,"replacement":null,"description":"Function 'app_a:baz/2' is not exported.\n\nFor more information see: /erlang-error-index/w/W0026","docPath":"website/docs/erlang-error-index/w/W0026.md"} {"path":"app_a/src/app_a.erl","line":12,"char":1,"code":"ELP","severity":"warning","name":"L1230 (L1230)","original":null,"replacement":null,"description":"function bar/0 is unused\n\nFor more information see: /erlang-error-index/l/L1230","docPath":null} {"path":"app_a/src/app_a.erl","line":16,"char":1,"code":"ELP","severity":"warning","name":"L1230 (L1230)","original":null,"replacement":null,"description":"function baz/2 is unused\n\nFor more information see: /erlang-error-index/l/L1230","docPath":null} -{"path":"app_a/src/app_a_edoc.erl","line":1,"char":1,"code":"ELP","severity":"error","name":"W0012 (compile-warn-missing-spec)","original":null,"replacement":null,"description":"Please add \"-compile(warn_missing_spec_all).\" to the module. If exported functions are not all specced, they need to be specced.\n\nFor more information see: /erlang-error-index/w/W0012","docPath":"website/docs/erlang-error-index/w/W0012.md"} {"path":"app_a/src/app_a_edoc.erl","line":1,"char":9,"code":"ELP","severity":"disabled","name":"W0046 (undocumented_module)","original":null,"replacement":null,"description":"The module is not documented.\n\nFor more information see: /erlang-error-index/w/W0046","docPath":"website/docs/erlang-error-index/w/W0046.md"} -{"path":"app_a/src/app_a_ssr.erl","line":1,"char":1,"code":"ELP","severity":"error","name":"W0012 (compile-warn-missing-spec)","original":null,"replacement":null,"description":"Please add \"-compile(warn_missing_spec_all).\" to the module. If exported functions are not all specced, they need to be specced.\n\nFor more information see: /erlang-error-index/w/W0012","docPath":"website/docs/erlang-error-index/w/W0012.md"} {"path":"app_a/src/app_a_ssr.erl","line":1,"char":9,"code":"ELP","severity":"disabled","name":"W0046 (undocumented_module)","original":null,"replacement":null,"description":"The module is not documented.\n\nFor more information see: /erlang-error-index/w/W0046","docPath":"website/docs/erlang-error-index/w/W0046.md"} -{"path":"app_a/src/app_a_unused_param.erl","line":1,"char":1,"code":"ELP","severity":"error","name":"W0012 (compile-warn-missing-spec)","original":null,"replacement":null,"description":"Please add \"-compile(warn_missing_spec_all).\" to the module. If exported functions are not all specced, they need to be specced.\n\nFor more information see: /erlang-error-index/w/W0012","docPath":"website/docs/erlang-error-index/w/W0012.md"} {"path":"app_a/src/app_a_unused_param.erl","line":1,"char":9,"code":"ELP","severity":"disabled","name":"W0046 (undocumented_module)","original":null,"replacement":null,"description":"The module is not documented.\n\nFor more information see: /erlang-error-index/w/W0046","docPath":"website/docs/erlang-error-index/w/W0046.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_b/src/app_b.erl","line":5,"char":5,"code":"ELP","severity":"warning","name":"W0011 (application_get_env)","original":null,"replacement":null,"description":"module `app_b` belongs to app `app_b`, 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_b/src/app_b.erl","line":1,"char":1,"code":"ELP","severity":"error","name":"W0012 (compile-warn-missing-spec)","original":null,"replacement":null,"description":"Please add \"-compile(warn_missing_spec_all).\" to the module. If exported functions are not all specced, they need to be specced.\n\nFor more information see: /erlang-error-index/w/W0012","docPath":"website/docs/erlang-error-index/w/W0012.md"} {"path":"app_b/src/app_b.erl","line":1,"char":9,"code":"ELP","severity":"disabled","name":"W0046 (undocumented_module)","original":null,"replacement":null,"description":"The module is not documented.\n\nFor more information see: /erlang-error-index/w/W0046","docPath":"website/docs/erlang-error-index/w/W0046.md"} -{"path":"app_b/src/app_b_unused_param.erl","line":1,"char":1,"code":"ELP","severity":"error","name":"W0012 (compile-warn-missing-spec)","original":null,"replacement":null,"description":"Please add \"-compile(warn_missing_spec_all).\" to the module. If exported functions are not all specced, they need to be specced.\n\nFor more information see: /erlang-error-index/w/W0012","docPath":"website/docs/erlang-error-index/w/W0012.md"} {"path":"app_b/src/app_b_unused_param.erl","line":1,"char":9,"code":"ELP","severity":"disabled","name":"W0046 (undocumented_module)","original":null,"replacement":null,"description":"The module is not documented.\n\nFor more information see: /erlang-error-index/w/W0046","docPath":"website/docs/erlang-error-index/w/W0046.md"} {"path":"app_b/src/app_b_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"} @@ -26,5 +20,4 @@ {"path":"app_a/src/expression_updates_literal.erl","line":7,"char":1,"code":"ELP","severity":"warning","name":"L1309 (L1309)","original":null,"replacement":null,"description":"missing specification for function a_fun/0\n\nFor more information see: /erlang-error-index/l/L1309","docPath":null} {"path":"app_a/src/expression_updates_literal.erl","line":8,"char":7,"code":"ELP","severity":"warning","name":"L1318 (L1318)","original":null,"replacement":null,"description":"expression updates a literal\n\nFor more information see: /erlang-error-index/l/L1318","docPath":null} {"path":"app_a/src/spelling.erl","line":2,"char":2,"code":"ELP","severity":"error","name":"W0013 (misspelled_attribute)","original":null,"replacement":null,"description":"misspelled attribute, saw 'dyalizer' but expected 'dialyzer'\n\nFor more information see: /erlang-error-index/w/W0013","docPath":"website/docs/erlang-error-index/w/W0013.md"} -{"path":"app_a/src/spelling.erl","line":1,"char":1,"code":"ELP","severity":"error","name":"W0012 (compile-warn-missing-spec)","original":null,"replacement":null,"description":"Please add \"-compile(warn_missing_spec_all).\" to the module. If exported functions are not all specced, they need to be specced.\n\nFor more information see: /erlang-error-index/w/W0012","docPath":"website/docs/erlang-error-index/w/W0012.md"} {"path":"app_a/src/spelling.erl","line":1,"char":9,"code":"ELP","severity":"disabled","name":"W0046 (undocumented_module)","original":null,"replacement":null,"description":"The module is not documented.\n\nFor more information see: /erlang-error-index/w/W0046","docPath":"website/docs/erlang-error-index/w/W0046.md"} diff --git a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout index 954278d91b..6e3e3d56ea 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout @@ -1,30 +1,24 @@ Reporting all diagnostics codes Diagnostics reported in 9 modules: - app_a: 8 + app_a: 7 4:4-4:34::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. 8:0-8:4::[Error] [P1700] head mismatch 'fooX' vs 'food' 7:6-7:7::[Warning] [W0018] Unexpected ';' 0:8-0:13::[WeakWarning] [W0046] The module is not documented. 12:4-12:13::[Warning] [W0026] Function 'app_a:baz/2' is not exported. 11:0-11:3::[Warning] [L1230] function bar/0 is unused 15:0-15:3::[Warning] [L1230] function baz/2 is unused - app_a_edoc: 2 - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. + app_a_edoc: 1 0:8-0:18::[WeakWarning] [W0046] The module is not documented. - app_a_ssr: 2 - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. + app_a_ssr: 1 0:8-0:17::[WeakWarning] [W0046] The module is not documented. - app_a_unused_param: 3 - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. + app_a_unused_param: 2 0:8-0:26::[WeakWarning] [W0046] The module is not documented. 4:4-4:5::[Warning] [L1268] variable 'X' is unused - app_b: 3 + app_b: 2 4:4-4:34::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. 0:8-0:13::[WeakWarning] [W0046] The module is not documented. - app_b_unused_param: 3 - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. + app_b_unused_param: 2 0:8-0:26::[WeakWarning] [W0046] The module is not documented. 4:4-4:5::[Warning] [L1268] variable 'X' is unused custom_function_matches: 3 @@ -35,7 +29,6 @@ Diagnostics reported in 9 modules: 1:8-1:34::[WeakWarning] [W0046] The module is not documented. 6:0-6:5::[Warning] [L1309] missing specification for function a_fun/0 7:6-8:15::[Warning] [L1318] expression updates a literal - spelling: 3 + spelling: 2 1:1-1:9::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. 0:8-0:16::[WeakWarning] [W0046] The module is not documented. diff --git a/crates/elp/src/resources/test/linter/warnings_as_errors.stdout b/crates/elp/src/resources/test/linter/warnings_as_errors.stdout index 493afa95c7..60edb2b6d4 100644 --- a/crates/elp/src/resources/test/linter/warnings_as_errors.stdout +++ b/crates/elp/src/resources/test/linter/warnings_as_errors.stdout @@ -1,30 +1,24 @@ Reporting all diagnostics codes Diagnostics reported in 9 modules: - app_a: 8 + app_a: 7 4:4-4:34::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. 8:0-8:4::[Error] [P1700] head mismatch 'fooX' vs 'food' 7:6-7:7::[Warning] [W0018] Unexpected ';' 0:8-0:13::[WeakWarning] [W0046] The module is not documented. 12:4-12:13::[Warning] [W0026] Function 'app_a:baz/2' is not exported. 11:0-11:3::[Error] [L1230] function bar/0 is unused 15:0-15:3::[Error] [L1230] function baz/2 is unused - app_a_edoc: 2 - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. + app_a_edoc: 1 0:8-0:18::[WeakWarning] [W0046] The module is not documented. - app_a_ssr: 2 - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. + app_a_ssr: 1 0:8-0:17::[WeakWarning] [W0046] The module is not documented. - app_a_unused_param: 3 - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. + app_a_unused_param: 2 0:8-0:26::[WeakWarning] [W0046] The module is not documented. 4:4-4:5::[Error] [L1268] variable 'X' is unused - app_b: 3 + app_b: 2 4:4-4:34::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. 0:8-0:13::[WeakWarning] [W0046] The module is not documented. - app_b_unused_param: 3 - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. + app_b_unused_param: 2 0:8-0:26::[WeakWarning] [W0046] The module is not documented. 4:4-4:5::[Error] [L1268] variable 'X' is unused custom_function_matches: 3 @@ -35,7 +29,6 @@ Diagnostics reported in 9 modules: 1:8-1:34::[WeakWarning] [W0046] The module is not documented. 6:0-6:5::[Error] [L1309] missing specification for function a_fun/0 7:6-8:15::[Error] [L1318] expression updates a literal - spelling: 3 + spelling: 2 1:1-1:9::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. 0:8-0:16::[WeakWarning] [W0046] The module is not documented. diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 1afe56f0d4..c435e16efb 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -1106,15 +1106,31 @@ impl DiagnosticsConfig { } pub fn enable(mut self, code: DiagnosticCode) -> DiagnosticsConfig { - self.enabled.enable(code); + self.enabled.enable(code.clone()); + self.set_linter_enabled(code, true); self } pub fn disable(mut self, code: DiagnosticCode) -> DiagnosticsConfig { - self.disabled.insert(code); + self.disabled.insert(code.clone()); + self.set_linter_enabled(code, false); self } + fn set_linter_enabled(&mut self, code: DiagnosticCode, is_enabled: bool) { + let lint_config = self.lint_config.get_or_insert_with(LintConfig::default); + lint_config + .linters + .entry(code) + .and_modify(|linter_config| { + linter_config.is_enabled = Some(is_enabled); + }) + .or_insert_with(|| LinterConfig { + is_enabled: Some(is_enabled), + ..Default::default() + }); + } + pub fn set_lints_from_config( mut self, lints_from_config: &LintsFromConfig, @@ -1475,7 +1491,6 @@ pub fn diagnostics_descriptors<'a>() -> Vec<&'a DiagnosticDescriptor<'a>> { &map_find_to_syntax::DESCRIPTOR, &expression_can_be_simplified::DESCRIPTOR, &application_env::DESCRIPTOR, - &missing_compile_warn_missing_spec::DESCRIPTOR, &dependent_header::DESCRIPTOR, &deprecated_function::DESCRIPTOR, &head_mismatch::DESCRIPTOR_SEMANTIC, @@ -1566,7 +1581,10 @@ const SSR_PATTERN_LINTERS: &[&dyn SsrPatternsDiagnostics] = &[ ]; /// Generic linters -const GENERIC_LINTERS: &[&dyn GenericDiagnostics] = &[&unused_macro::LINTER]; +const GENERIC_LINTERS: &[&dyn GenericDiagnostics] = &[ + &unused_macro::LINTER, + &missing_compile_warn_missing_spec::LINTER, +]; /// Unified registry for all types of linters pub(crate) fn linters() -> Vec { diff --git a/crates/ide/src/diagnostics/missing_compile_warn_missing_spec.rs b/crates/ide/src/diagnostics/missing_compile_warn_missing_spec.rs index 6114658bf2..681672ba8e 100644 --- a/crates/ide/src/diagnostics/missing_compile_warn_missing_spec.rs +++ b/crates/ide/src/diagnostics/missing_compile_warn_missing_spec.rs @@ -1,10 +1,11 @@ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * - * This source code is licensed under both the MIT license found in the - * LICENSE-MIT file in the root directory of this source tree and the Apache + * 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. + * of this source tree. You may select, at your option, one of the + * above-listed licenses. */ //! Lint/fix: missing_compile_warn_missing_spec @@ -15,6 +16,7 @@ use elp_ide_assists::helpers::add_compile_option; use elp_ide_assists::helpers::rename_atom_in_compile_attribute; +use elp_ide_db::DiagnosticCode; use elp_ide_db::elp_base_db::FileId; use elp_ide_db::elp_base_db::FileKind; use elp_ide_db::source_change::SourceChangeBuilder; @@ -36,94 +38,162 @@ use hir::known; use lazy_static::lazy_static; use super::DIAGNOSTIC_WHOLE_FILE_RANGE; -use super::Diagnostic; -use super::DiagnosticConditions; -use super::DiagnosticDescriptor; +use crate::diagnostics::GenericLinter; +use crate::diagnostics::GenericLinterMatchContext; +use crate::diagnostics::Linter; +use crate::diagnostics::Severity; use crate::fix; -pub(crate) static DESCRIPTOR: DiagnosticDescriptor = DiagnosticDescriptor { - conditions: DiagnosticConditions { - experimental: false, - include_generated: false, - include_tests: false, - default_disabled: true, - }, - checker: &|diags, sema, file_id, file_kind| { - missing_compile_warn_missing_spec(diags, sema, file_id, file_kind); - }, -}; +pub(crate) struct MissingCompileWarnMissingSpec; -fn missing_compile_warn_missing_spec( - diags: &mut Vec, - sema: &Semantic, - file_id: FileId, - file_kind: FileKind, -) { - match file_kind { - FileKind::Header | FileKind::Other | FileKind::OutsideProjectModel => { - return; - } - _ => {} +impl Linter for MissingCompileWarnMissingSpec { + fn id(&self) -> DiagnosticCode { + DiagnosticCode::MissingCompileWarnMissingSpec } - - let form_list = sema.form_list(file_id); - if form_list.compile_attributes().next().is_none() { - report_diagnostic(sema, None, file_id, (Found::No, None), diags); + fn description(&self) -> &'static str { + "Please add \"-compile(warn_missing_spec_all).\" to the module. If exported functions are not all specced, they need to be specced." } - let attributes = form_list - .compile_attributes() - .map(|(idx, compile_attribute)| { - let co = sema.db.compile_body(InFile::new(file_id, idx)); - let is_present = FoldCtx::fold_term( - Strategy { - macros: MacroStrategy::Expand, - parens: ParenStrategy::InvisibleParens, - }, - &co.body, - co.value, - (Found::No, None), - &mut |acc, ctx| match &ctx.item { - AnyExpr::Term(Term::Literal(Literal::Atom(atom))) => { - let name = sema.db.lookup_atom(*atom); - if MISSING_SPEC_ALL_OPTIONS.contains(&name) { - (Found::WarnMissingSpecAll, Some(idx)) - } else if MISSING_SPEC_OPTIONS.contains(&name) { - (Found::WarnMissingSpec, Some(idx)) - } else { - acc - } - } - _ => acc, - }, - ); - (is_present, compile_attribute) - }) - .collect::>(); - - let what = attributes - .iter() - .fold((Found::No, None), |acc, ((present, idx), _)| { - if acc.0 == Found::No { - (*present, *idx) - } else { - acc - } - }); - if what.0 != Found::WarnMissingSpecAll { - // Report on first compile attribute only - if let Some((_, compile_attribute)) = attributes.first() { - let range = compile_attribute - .form_id - .get_ast(sema.db, file_id) - .syntax() - .text_range(); - report_diagnostic(sema, Some(range), file_id, what, diags) + fn severity(&self) -> Severity { + Severity::Error + } + fn should_process_test_files(&self) -> bool { + false + } + fn is_enabled(&self) -> bool { + false + } + fn should_process_file_id(&self, sema: &Semantic, file_id: FileId) -> bool { + let file_kind = sema.db.file_kind(file_id); + match file_kind { + FileKind::Header | FileKind::Other | FileKind::OutsideProjectModel => false, + _ => true, } } } -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct Context { + found: Found, + compile_option_id: Option, + target_range: TextRange, +} + +impl GenericLinter for MissingCompileWarnMissingSpec { + type Context = Context; + + fn matches( + &self, + sema: &Semantic, + file_id: FileId, + ) -> Option>> { + let mut res = Vec::new(); + let form_list = sema.form_list(file_id); + if form_list.compile_attributes().next().is_none() { + res.push(GenericLinterMatchContext { + range: DIAGNOSTIC_WHOLE_FILE_RANGE, + context: Context { + found: Found::No, + compile_option_id: None, + target_range: DIAGNOSTIC_WHOLE_FILE_RANGE, + }, + }); + } + let attributes = form_list + .compile_attributes() + .map(|(idx, compile_attribute)| { + let co = sema.db.compile_body(InFile::new(file_id, idx)); + let is_present = FoldCtx::fold_term( + Strategy { + macros: MacroStrategy::Expand, + parens: ParenStrategy::InvisibleParens, + }, + &co.body, + co.value, + (Found::No, None), + &mut |acc, ctx| match &ctx.item { + AnyExpr::Term(Term::Literal(Literal::Atom(atom))) => { + let name = sema.db.lookup_atom(*atom); + if MISSING_SPEC_ALL_OPTIONS.contains(&name) { + (Found::WarnMissingSpecAll, Some(idx)) + } else if MISSING_SPEC_OPTIONS.contains(&name) { + (Found::WarnMissingSpec, Some(idx)) + } else { + acc + } + } + _ => acc, + }, + ); + (is_present, compile_attribute) + }) + .collect::>(); + let what = attributes + .iter() + .fold((Found::No, None), |acc, ((present, idx), _)| { + if acc.0 == Found::No { + (*present, *idx) + } else { + acc + } + }); + if what.0 != Found::WarnMissingSpecAll { + // Report on first compile attribute only + if let Some((_, compile_attribute)) = attributes.first() { + let range = compile_attribute + .form_id + .get_ast(sema.db, file_id) + .syntax() + .text_range(); + res.push(GenericLinterMatchContext { + range, + context: Context { + found: what.0, + compile_option_id: what.1, + target_range: range, + }, + }); + } + } + Some(res) + } + + fn fixes( + &self, + context: &Self::Context, + sema: &Semantic, + file_id: FileId, + ) -> Option> { + let mut builder = SourceChangeBuilder::new(file_id); + if context.found == Found::No { + add_compile_option(sema, file_id, "warn_missing_spec_all", None, &mut builder); + } else { + // We already have warn_missing_spec, upgrade it to warn_missing_spec_all + if let Some(co_id) = context.compile_option_id { + rename_atom_in_compile_attribute( + sema, + file_id, + &co_id, + "warn_missing_spec", + "warn_missing_spec_all", + &mut builder, + ); + } + } + let edit = builder.finish(); + Some(vec![fix( + "add_warn_missing_spec_all", + "Add compile option 'warn_missing_spec_all'", + edit, + context.target_range, + )]) + } +} + +pub static LINTER: MissingCompileWarnMissingSpec = MissingCompileWarnMissingSpec; + +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)] enum Found { + #[default] No, WarnMissingSpec, WarnMissingSpecAll, @@ -146,43 +216,6 @@ lazy_static! { }; } -fn report_diagnostic( - sema: &Semantic, - range: Option, - file_id: FileId, - what: (Found, Option), - diags: &mut Vec, -) { - let range = range.unwrap_or(DIAGNOSTIC_WHOLE_FILE_RANGE); - - let mut builder = SourceChangeBuilder::new(file_id); - if what.0 == Found::No { - add_compile_option(sema, file_id, "warn_missing_spec_all", None, &mut builder); - } else { - // We already have warn_missing_spec, upgrade it to warn_missing_spec_all - if let Some(co_id) = what.1 { - rename_atom_in_compile_attribute( - sema, - file_id, - &co_id, - "warn_missing_spec", - "warn_missing_spec_all", - &mut builder, - ); - } - } - let edit = builder.finish(); - let d = Diagnostic::new( - crate::diagnostics::DiagnosticCode::MissingCompileWarnMissingSpec, - "Please add \"-compile(warn_missing_spec_all).\" to the module. If exported functions are not all specced, they need to be specced.".to_string(), - range, - ).with_fixes(Some(vec![fix("add_warn_missing_spec_all", - "Add compile option 'warn_missing_spec_all'", - edit, range)])) - .with_ignore_fix(sema, file_id); - diags.push(d); -} - #[cfg(test)] mod tests { @@ -197,8 +230,9 @@ mod tests { #[track_caller] pub(crate) fn check_fix(fixture_before: &str, fixture_after: Expect) { - let config = - DiagnosticsConfig::default().enable(DiagnosticCode::MissingCompileWarnMissingSpec); + let config = DiagnosticsConfig::default() + .disable(DiagnosticCode::NoNoWarnSuppressions) + .enable(DiagnosticCode::MissingCompileWarnMissingSpec); check_fix_with_config(config, fixture_before, fixture_after) } @@ -208,17 +242,17 @@ mod tests { fixture_before: &str, fixture_after: Expect, ) { - let config = - DiagnosticsConfig::default().enable(DiagnosticCode::MissingCompileWarnMissingSpec); + let config = DiagnosticsConfig::default() + .disable(DiagnosticCode::NoNoWarnSuppressions) + .enable(DiagnosticCode::MissingCompileWarnMissingSpec); check_specific_fix_with_config(Some(assist_label), fixture_before, fixture_after, config) } #[track_caller] pub(crate) fn check_diagnostics(fixture: &str) { let config = DiagnosticsConfig::default() - .enable(DiagnosticCode::MissingCompileWarnMissingSpec) .disable(DiagnosticCode::NoNoWarnSuppressions) - .disable(DiagnosticCode::UnspecificInclude); + .enable(DiagnosticCode::MissingCompileWarnMissingSpec); check_diagnostics_with_config(config, fixture) } @@ -367,19 +401,20 @@ mod tests { #[test] fn not_in_generated_file() { - check_diagnostics( + check_diagnostics(&format!( r#" //- /erl/my_app/src/main.erl %% -*- coding: utf-8 -*- %% Automatically generated, do not edit - %% @generated from blah + %% @{} from blah %% To generate, see targets and instructions in local Makefile %% Version source: git -module(main). -eqwalizer(ignore). "#, - ) + "generated" // Separate string, to avoid to mark this module itself as generated + )) } #[test] diff --git a/test_projects/linter/elp_lint_test2.toml b/test_projects/linter/elp_lint_test2.toml index 22878a22a6..0b70173c8b 100644 --- a/test_projects/linter/elp_lint_test2.toml +++ b/test_projects/linter/elp_lint_test2.toml @@ -1,2 +1,5 @@ enabled_lints =['L1268', 'W0011', 'W0012'] disabled_lints = [] + +[linters.compile-warn-missing-spec] +enabled = true From 20b8d9950a4295424b638c6f56ecfedeb2301219 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Fri, 14 Nov 2025 10:05:06 -0800 Subject: [PATCH 020/142] Convert undocumented_module linter to use trait Summary: Even if the `undocumented_module` is supposed to be disabled by default, it still runs. This is due to a flawed logic in `diagnostics.rs`, originally introduced in D59272525. This is problematic, since open-source projects such as OTP see a huge number of errors when running the `elp lint` command without specifying a custom configuration. By converting the linter to use the new trait, we incidentally fix the bug. One more linters uses a `default_disabled: true` property in its descriptor: `undocumented_function`. It will be converted next, and the logic to handle the `default_disabled` will eventually be removed. Reviewed By: alanz Differential Revision: D87059857 fbshipit-source-id: 7fb6eff8773c786c6894548c914b8e3c78c38745 --- .../resolves_generated_includes.stdout | 15 +-- ...e_elp_no_lint_specified_json_output.stdout | 8 -- .../parse_elp_no_lint_specified_output.stdout | 24 +--- .../test/linter/warnings_as_errors.stdout | 24 +--- crates/ide/src/diagnostics.rs | 2 +- .../src/diagnostics/undocumented_module.rs | 121 +++++++++++------- 6 files changed, 93 insertions(+), 101 deletions(-) diff --git a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout index b5ed320571..913a0281ee 100644 --- a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout +++ b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout @@ -1,19 +1,12 @@ Reporting all diagnostics codes -Diagnostics reported in 5 modules: - app_a: 2 +Diagnostics reported in 3 modules: + app_a: 1 5:9-5:31::[WeakWarning] [W0037] Unspecific include. - 0:8-0:13::[WeakWarning] [W0046] The module is not documented. - auto_gen_a: 1 - 0:8-0:18::[WeakWarning] [W0046] The module is not documented. - eqwalizer: 1 - 1:8-1:17::[WeakWarning] [W0046] The module is not documented. - top_includer: 3 - 0:8-0:20::[WeakWarning] [W0046] The module is not documented. + top_includer: 2 0:1-0:1::[Error] [L0000] Issue in included file 12:4-12:10::[Error] [E1508] undefined macro 'THIRD/2' - wa_buck2_module_search: 6 + wa_buck2_module_search: 5 19:0-19:18::[WeakWarning] [W0040] The function is non-trivial, exported, but not documented. - 2:8-2:30::[WeakWarning] [W0046] The module is not documented. 54:18-54:31::[WeakWarning] [W0051] Binary string can be written using sigil syntax. 56:18-56:28::[WeakWarning] [W0051] Binary string can be written using sigil syntax. 56:38-56:51::[WeakWarning] [W0051] Binary string can be written using sigil syntax. diff --git a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_json_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_json_output.stdout index b8924b40ed..0385484785 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_json_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_json_output.stdout @@ -1,23 +1,15 @@ {"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":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.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":1,"char":9,"code":"ELP","severity":"disabled","name":"W0046 (undocumented_module)","original":null,"replacement":null,"description":"The module is not documented.\n\nFor more information see: /erlang-error-index/w/W0046","docPath":"website/docs/erlang-error-index/w/W0046.md"} {"path":"app_a/src/app_a.erl","line":13,"char":5,"code":"ELP","severity":"warning","name":"W0026 (unexported_function)","original":null,"replacement":null,"description":"Function 'app_a:baz/2' is not exported.\n\nFor more information see: /erlang-error-index/w/W0026","docPath":"website/docs/erlang-error-index/w/W0026.md"} {"path":"app_a/src/app_a.erl","line":12,"char":1,"code":"ELP","severity":"warning","name":"L1230 (L1230)","original":null,"replacement":null,"description":"function bar/0 is unused\n\nFor more information see: /erlang-error-index/l/L1230","docPath":null} {"path":"app_a/src/app_a.erl","line":16,"char":1,"code":"ELP","severity":"warning","name":"L1230 (L1230)","original":null,"replacement":null,"description":"function baz/2 is unused\n\nFor more information see: /erlang-error-index/l/L1230","docPath":null} -{"path":"app_a/src/app_a_edoc.erl","line":1,"char":9,"code":"ELP","severity":"disabled","name":"W0046 (undocumented_module)","original":null,"replacement":null,"description":"The module is not documented.\n\nFor more information see: /erlang-error-index/w/W0046","docPath":"website/docs/erlang-error-index/w/W0046.md"} -{"path":"app_a/src/app_a_ssr.erl","line":1,"char":9,"code":"ELP","severity":"disabled","name":"W0046 (undocumented_module)","original":null,"replacement":null,"description":"The module is not documented.\n\nFor more information see: /erlang-error-index/w/W0046","docPath":"website/docs/erlang-error-index/w/W0046.md"} -{"path":"app_a/src/app_a_unused_param.erl","line":1,"char":9,"code":"ELP","severity":"disabled","name":"W0046 (undocumented_module)","original":null,"replacement":null,"description":"The module is not documented.\n\nFor more information see: /erlang-error-index/w/W0046","docPath":"website/docs/erlang-error-index/w/W0046.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_b/src/app_b.erl","line":5,"char":5,"code":"ELP","severity":"warning","name":"W0011 (application_get_env)","original":null,"replacement":null,"description":"module `app_b` belongs to app `app_b`, 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_b/src/app_b.erl","line":1,"char":9,"code":"ELP","severity":"disabled","name":"W0046 (undocumented_module)","original":null,"replacement":null,"description":"The module is not documented.\n\nFor more information see: /erlang-error-index/w/W0046","docPath":"website/docs/erlang-error-index/w/W0046.md"} -{"path":"app_b/src/app_b_unused_param.erl","line":1,"char":9,"code":"ELP","severity":"disabled","name":"W0046 (undocumented_module)","original":null,"replacement":null,"description":"The module is not documented.\n\nFor more information see: /erlang-error-index/w/W0046","docPath":"website/docs/erlang-error-index/w/W0046.md"} {"path":"app_b/src/app_b_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"} {"path":"app_a/src/custom_function_matches.erl","line":15,"char":5,"code":"ELP","severity":"warning","name":"W0017 (undefined_function)","original":null,"replacement":null,"description":"Function 'cross:call/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/expression_updates_literal.erl","line":2,"char":9,"code":"ELP","severity":"disabled","name":"W0046 (undocumented_module)","original":null,"replacement":null,"description":"The module is not documented.\n\nFor more information see: /erlang-error-index/w/W0046","docPath":"website/docs/erlang-error-index/w/W0046.md"} {"path":"app_a/src/expression_updates_literal.erl","line":7,"char":1,"code":"ELP","severity":"warning","name":"L1309 (L1309)","original":null,"replacement":null,"description":"missing specification for function a_fun/0\n\nFor more information see: /erlang-error-index/l/L1309","docPath":null} {"path":"app_a/src/expression_updates_literal.erl","line":8,"char":7,"code":"ELP","severity":"warning","name":"L1318 (L1318)","original":null,"replacement":null,"description":"expression updates a literal\n\nFor more information see: /erlang-error-index/l/L1318","docPath":null} {"path":"app_a/src/spelling.erl","line":2,"char":2,"code":"ELP","severity":"error","name":"W0013 (misspelled_attribute)","original":null,"replacement":null,"description":"misspelled attribute, saw 'dyalizer' but expected 'dialyzer'\n\nFor more information see: /erlang-error-index/w/W0013","docPath":"website/docs/erlang-error-index/w/W0013.md"} -{"path":"app_a/src/spelling.erl","line":1,"char":9,"code":"ELP","severity":"disabled","name":"W0046 (undocumented_module)","original":null,"replacement":null,"description":"The module is not documented.\n\nFor more information see: /erlang-error-index/w/W0046","docPath":"website/docs/erlang-error-index/w/W0046.md"} diff --git a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout index 6e3e3d56ea..2efe7fa2de 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout @@ -1,34 +1,24 @@ Reporting all diagnostics codes -Diagnostics reported in 9 modules: - app_a: 7 +Diagnostics reported in 7 modules: + app_a: 6 4:4-4:34::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` 8:0-8:4::[Error] [P1700] head mismatch 'fooX' vs 'food' 7:6-7:7::[Warning] [W0018] Unexpected ';' - 0:8-0:13::[WeakWarning] [W0046] The module is not documented. 12:4-12:13::[Warning] [W0026] Function 'app_a:baz/2' is not exported. 11:0-11:3::[Warning] [L1230] function bar/0 is unused 15:0-15:3::[Warning] [L1230] function baz/2 is unused - app_a_edoc: 1 - 0:8-0:18::[WeakWarning] [W0046] The module is not documented. - app_a_ssr: 1 - 0:8-0:17::[WeakWarning] [W0046] The module is not documented. - app_a_unused_param: 2 - 0:8-0:26::[WeakWarning] [W0046] The module is not documented. + app_a_unused_param: 1 4:4-4:5::[Warning] [L1268] variable 'X' is unused - app_b: 2 + app_b: 1 4:4-4:34::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` - 0:8-0:13::[WeakWarning] [W0046] The module is not documented. - app_b_unused_param: 2 - 0:8-0:26::[WeakWarning] [W0046] The module is not documented. + app_b_unused_param: 1 4:4-4:5::[Warning] [L1268] variable 'X' is unused custom_function_matches: 3 12:4-12:21::[Warning] [W0017] Function 'excluded:function/0' is undefined. 13:4-13:25::[Warning] [W0017] Function 'not_excluded:function/0' is undefined. 14:4-14:14::[Warning] [W0017] Function 'cross:call/0' is undefined. - expression_updates_literal: 3 - 1:8-1:34::[WeakWarning] [W0046] The module is not documented. + expression_updates_literal: 2 6:0-6:5::[Warning] [L1309] missing specification for function a_fun/0 7:6-8:15::[Warning] [L1318] expression updates a literal - spelling: 2 + spelling: 1 1:1-1:9::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' - 0:8-0:16::[WeakWarning] [W0046] The module is not documented. diff --git a/crates/elp/src/resources/test/linter/warnings_as_errors.stdout b/crates/elp/src/resources/test/linter/warnings_as_errors.stdout index 60edb2b6d4..831df4f97a 100644 --- a/crates/elp/src/resources/test/linter/warnings_as_errors.stdout +++ b/crates/elp/src/resources/test/linter/warnings_as_errors.stdout @@ -1,34 +1,24 @@ Reporting all diagnostics codes -Diagnostics reported in 9 modules: - app_a: 7 +Diagnostics reported in 7 modules: + app_a: 6 4:4-4:34::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` 8:0-8:4::[Error] [P1700] head mismatch 'fooX' vs 'food' 7:6-7:7::[Warning] [W0018] Unexpected ';' - 0:8-0:13::[WeakWarning] [W0046] The module is not documented. 12:4-12:13::[Warning] [W0026] Function 'app_a:baz/2' is not exported. 11:0-11:3::[Error] [L1230] function bar/0 is unused 15:0-15:3::[Error] [L1230] function baz/2 is unused - app_a_edoc: 1 - 0:8-0:18::[WeakWarning] [W0046] The module is not documented. - app_a_ssr: 1 - 0:8-0:17::[WeakWarning] [W0046] The module is not documented. - app_a_unused_param: 2 - 0:8-0:26::[WeakWarning] [W0046] The module is not documented. + app_a_unused_param: 1 4:4-4:5::[Error] [L1268] variable 'X' is unused - app_b: 2 + app_b: 1 4:4-4:34::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` - 0:8-0:13::[WeakWarning] [W0046] The module is not documented. - app_b_unused_param: 2 - 0:8-0:26::[WeakWarning] [W0046] The module is not documented. + app_b_unused_param: 1 4:4-4:5::[Error] [L1268] variable 'X' is unused custom_function_matches: 3 12:4-12:21::[Warning] [W0017] Function 'excluded:function/0' is undefined. 13:4-13:25::[Warning] [W0017] Function 'not_excluded:function/0' is undefined. 14:4-14:14::[Warning] [W0017] Function 'cross:call/0' is undefined. - expression_updates_literal: 3 - 1:8-1:34::[WeakWarning] [W0046] The module is not documented. + expression_updates_literal: 2 6:0-6:5::[Error] [L1309] missing specification for function a_fun/0 7:6-8:15::[Error] [L1318] expression updates a literal - spelling: 2 + spelling: 1 1:1-1:9::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' - 0:8-0:16::[WeakWarning] [W0046] The module is not documented. diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index c435e16efb..fa84826faf 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -1502,7 +1502,6 @@ pub fn diagnostics_descriptors<'a>() -> Vec<&'a DiagnosticDescriptor<'a>> { ¯o_precedence_suprise::DESCRIPTOR, &undocumented_function::DESCRIPTOR, &duplicate_module::DESCRIPTOR, - &undocumented_module::DESCRIPTOR, &no_dialyzer_attribute::DESCRIPTOR, &no_catch::DESCRIPTOR, &no_nowarn_suppressions::DESCRIPTOR, @@ -1584,6 +1583,7 @@ const SSR_PATTERN_LINTERS: &[&dyn SsrPatternsDiagnostics] = &[ const GENERIC_LINTERS: &[&dyn GenericDiagnostics] = &[ &unused_macro::LINTER, &missing_compile_warn_missing_spec::LINTER, + &undocumented_module::LINTER, ]; /// Unified registry for all types of linters diff --git a/crates/ide/src/diagnostics/undocumented_module.rs b/crates/ide/src/diagnostics/undocumented_module.rs index ad440d5acd..96a3a6c13a 100644 --- a/crates/ide/src/diagnostics/undocumented_module.rs +++ b/crates/ide/src/diagnostics/undocumented_module.rs @@ -14,62 +14,89 @@ use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChangeBuilder; use elp_syntax::AstNode; use elp_text_edit::TextRange; -use elp_text_edit::TextSize; use hir::Semantic; -use super::Diagnostic; -use super::DiagnosticCode; -use super::DiagnosticConditions; -use super::DiagnosticDescriptor; -use super::Severity; +use crate::diagnostics::DiagnosticCode; +use crate::diagnostics::GenericLinter; +use crate::diagnostics::GenericLinterMatchContext; +use crate::diagnostics::Linter; +use crate::diagnostics::Severity; +use crate::fix; -const DIAGNOSTIC_CODE: DiagnosticCode = DiagnosticCode::UndocumentedModule; -const DIAGNOSTIC_MESSAGE: &str = "The module is not documented."; -const DIAGNOSTIC_SEVERITY: Severity = Severity::WeakWarning; -const FIX_ID: &str = "add_moduledoc_false"; -const FIX_LABEL: &str = "Add `-moduledoc false.` attribute"; +pub(crate) struct UndocumentedModuleLinter; -pub(crate) static DESCRIPTOR: DiagnosticDescriptor = DiagnosticDescriptor { - conditions: DiagnosticConditions { - experimental: false, - include_generated: false, - include_tests: false, - default_disabled: true, - }, - checker: &|diags, sema, file_id, _ext| { - check(diags, sema, file_id); - }, -}; - -fn check(diagnostics: &mut Vec, sema: &Semantic, file_id: FileId) -> Option<()> { - let def_map = sema.def_map_local(file_id); - let module_attribute = sema.module_attribute(file_id)?; - let module_has_no_docs = def_map.moduledoc.is_empty() - && def_map.moduledoc_metadata.is_empty() - && sema - .module_edoc_header(file_id, &module_attribute) - .is_none(); - if module_has_no_docs { - let module_name = module_attribute.name()?; - let module_name_range = module_name.syntax().text_range(); - let moduledoc_insert_offset = helpers::moduledoc_insert_offset(sema, file_id)?; - let fixes = fixes(file_id, moduledoc_insert_offset, module_name_range); - let diagnostic = Diagnostic::new(DIAGNOSTIC_CODE, DIAGNOSTIC_MESSAGE, module_name_range) - .with_severity(DIAGNOSTIC_SEVERITY) - .with_fixes(Some(fixes)); - diagnostics.push(diagnostic); +impl Linter for UndocumentedModuleLinter { + fn id(&self) -> DiagnosticCode { + DiagnosticCode::UndocumentedModule + } + + fn description(&self) -> &'static str { + "The module is not documented." + } + + fn severity(&self) -> Severity { + Severity::WeakWarning + } + + fn is_enabled(&self) -> bool { + false + } + + fn should_process_test_files(&self) -> bool { + false } - Some(()) } -fn fixes(file_id: FileId, insert_offset: TextSize, show_range: TextRange) -> Vec { - let mut builder = SourceChangeBuilder::new(file_id); - builder.insert(insert_offset, "-moduledoc false.\n"); - let source_change = builder.finish(); - let fix = crate::fix(FIX_ID, FIX_LABEL, source_change, show_range); - vec![fix] +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct Context { + module_name_range: TextRange, } +impl GenericLinter for UndocumentedModuleLinter { + type Context = Context; + + fn matches( + &self, + sema: &Semantic, + file_id: FileId, + ) -> Option>> { + let mut res = Vec::new(); + let def_map = sema.def_map_local(file_id); + let module_attribute = sema.module_attribute(file_id)?; + let module_has_no_docs = def_map.moduledoc.is_empty() + && def_map.moduledoc_metadata.is_empty() + && sema + .module_edoc_header(file_id, &module_attribute) + .is_none(); + 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, + }); + } + Some(res) + } + + fn fixes(&self, context: &Context, sema: &Semantic, file_id: FileId) -> Option> { + let insert_offset = helpers::moduledoc_insert_offset(sema, file_id)?; + let mut builder = SourceChangeBuilder::new(file_id); + builder.insert(insert_offset, "-moduledoc false.\n"); + let source_change = builder.finish(); + let fix = fix( + "add_moduledoc_false", + "Add `-moduledoc false.` attribute", + source_change, + context.module_name_range, + ); + Some(vec![fix]) + } +} + +pub static LINTER: UndocumentedModuleLinter = UndocumentedModuleLinter; + #[cfg(test)] mod tests { From 30a992691fadfe520665c347996618e9fd4290bb Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Fri, 14 Nov 2025 10:05:06 -0800 Subject: [PATCH 021/142] Convert the undocumented_function linter to use trait Summary: Even if the `undocumented_function` is supposed to be disabled by default, it still runs. This is due to a flawed logic in `diagnostics.rs`, originally introduced in D59272525. This is problematic, since open-source projects such as OTP see a huge number of errors when running the `elp lint` command without specifying a custom configuration. By converting the linter to use the new trait, we incidentally fix the bug. This was the last linter using the `default_disabled: true` logic. At this point the enabled/disabled logic can be simplified. Reviewed By: alanz Differential Revision: D87060203 fbshipit-source-id: 1121d286c766fb461420e3268aca817169020d7d --- .../resolves_generated_includes.stdout | 3 +- crates/ide/src/diagnostics.rs | 2 +- .../src/diagnostics/undocumented_function.rs | 104 ++++++++++++------ 3 files changed, 70 insertions(+), 39 deletions(-) diff --git a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout index 913a0281ee..83f167f14a 100644 --- a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout +++ b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout @@ -5,8 +5,7 @@ Diagnostics reported in 3 modules: top_includer: 2 0:1-0:1::[Error] [L0000] Issue in included file 12:4-12:10::[Error] [E1508] undefined macro 'THIRD/2' - wa_buck2_module_search: 5 - 19:0-19:18::[WeakWarning] [W0040] The function is non-trivial, exported, but not documented. + wa_buck2_module_search: 4 54:18-54:31::[WeakWarning] [W0051] Binary string can be written using sigil syntax. 56:18-56:28::[WeakWarning] [W0051] Binary string can be written using sigil syntax. 56:38-56:51::[WeakWarning] [W0051] Binary string can be written using sigil syntax. diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index fa84826faf..d01bb86d87 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -1500,7 +1500,6 @@ pub fn diagnostics_descriptors<'a>() -> Vec<&'a DiagnosticDescriptor<'a>> { &unspecific_include::DESCRIPTOR, &edoc::DESCRIPTOR, ¯o_precedence_suprise::DESCRIPTOR, - &undocumented_function::DESCRIPTOR, &duplicate_module::DESCRIPTOR, &no_dialyzer_attribute::DESCRIPTOR, &no_catch::DESCRIPTOR, @@ -1584,6 +1583,7 @@ const GENERIC_LINTERS: &[&dyn GenericDiagnostics] = &[ &unused_macro::LINTER, &missing_compile_warn_missing_spec::LINTER, &undocumented_module::LINTER, + &undocumented_function::LINTER, ]; /// Unified registry for all types of linters diff --git a/crates/ide/src/diagnostics/undocumented_function.rs b/crates/ide/src/diagnostics/undocumented_function.rs index c895b52622..41fdeb0229 100644 --- a/crates/ide/src/diagnostics/undocumented_function.rs +++ b/crates/ide/src/diagnostics/undocumented_function.rs @@ -8,11 +8,12 @@ * above-listed licenses. */ -use elp_ide_assists::helpers::unwrap_parens; // Diagnostic: undocumented-function +use elp_ide_assists::helpers::unwrap_parens; use elp_ide_db::elp_base_db::FileId; use elp_syntax::ast; use elp_syntax::ast::Atom; +use elp_text_edit::TextRange; use fxhash::FxHashSet; use hir::AsName; use hir::FunctionDef; @@ -21,37 +22,66 @@ use hir::Semantic; use hir::form_list::ModuleDocAttribute; use hir::known; -use super::Diagnostic; -use super::DiagnosticCode; -use super::DiagnosticConditions; -use super::DiagnosticDescriptor; -use super::Severity; +use crate::diagnostics::DiagnosticCode; +use crate::diagnostics::GenericLinter; +use crate::diagnostics::GenericLinterMatchContext; +use crate::diagnostics::Linter; +use crate::diagnostics::Severity; -const DIAGNOSTIC_CODE: DiagnosticCode = DiagnosticCode::UndocumentedFunction; -const DIAGNOSTIC_MESSAGE: &str = "The function is non-trivial, exported, but not documented."; -const DIAGNOSTIC_SEVERITY: Severity = Severity::WeakWarning; +pub(crate) struct UndocumentedFunctionLinter; -pub(crate) static DESCRIPTOR: DiagnosticDescriptor = DiagnosticDescriptor { - conditions: DiagnosticConditions { - experimental: false, - include_generated: false, - include_tests: false, - default_disabled: true, - }, - checker: &|diags, sema, file_id, _ext| { - check(diags, sema, file_id); - }, -}; +impl Linter for UndocumentedFunctionLinter { + fn id(&self) -> DiagnosticCode { + DiagnosticCode::UndocumentedFunction + } -fn check(diagnostics: &mut Vec, sema: &Semantic, file_id: FileId) { - let callbacks = sema.resolve_callbacks(file_id); - if !contains_moduledoc_hidden_attribute(sema, file_id) { - sema.def_map_local(file_id) - .get_functions() - .for_each(|(_arity, def)| check_function(diagnostics, sema, def, &callbacks)); + fn description(&self) -> &'static str { + "The function is non-trivial, exported, but not documented." + } + + fn severity(&self) -> Severity { + Severity::WeakWarning + } + + fn is_enabled(&self) -> bool { + false + } + + fn should_process_test_files(&self) -> bool { + false } } +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct Context { + range: TextRange, +} + +impl GenericLinter for UndocumentedFunctionLinter { + type Context = Context; + + fn matches( + &self, + sema: &Semantic, + file_id: FileId, + ) -> Option>> { + let mut res = Vec::new(); + let callbacks = sema.resolve_callbacks(file_id); + if !contains_moduledoc_hidden_attribute(sema, file_id) { + sema.def_map_local(file_id) + .get_functions() + .for_each(|(_arity, def)| { + if let Some(match_context) = check_function(sema, def, &callbacks) { + res.push(match_context); + } + }); + } + Some(res) + } +} + +pub static LINTER: UndocumentedFunctionLinter = UndocumentedFunctionLinter; + fn contains_moduledoc_hidden_attribute(sema: &Semantic, file_id: FileId) -> bool { sema.form_list(file_id) .moduledoc_attributes() @@ -87,20 +117,22 @@ fn function_should_be_checked( } fn check_function( - diagnostics: &mut Vec, sema: &Semantic, def: &FunctionDef, callbacks: &FxHashSet, -) { +) -> Option> { if function_should_be_checked(sema, def, callbacks) && !def.has_doc_attribute() && !def.has_doc_attribute_metadata() && def.edoc_comments(sema.db).is_none() && let Some(name_range) = def.name_range(sema.db) { - let diagnostic = Diagnostic::new(DIAGNOSTIC_CODE, DIAGNOSTIC_MESSAGE, name_range) - .with_severity(DIAGNOSTIC_SEVERITY); - diagnostics.push(diagnostic); + Some(GenericLinterMatchContext { + range: name_range, + context: Context { range: name_range }, + }) + } else { + None } } @@ -130,7 +162,7 @@ mod tests { -module(main). -export([main/0]). main() -> - %% ^^^^ weak: W0040: The function is non-trivial, exported, but not documented. + %% ^^^^ 💡 weak: W0040: The function is non-trivial, exported, but not documented. [ok, ok, ok, @@ -147,7 +179,7 @@ mod tests { -module(main). -export([main/0]). main() -> - %% ^^^^ weak: W0040: The function is non-trivial, exported, but not documented. + %% ^^^^ 💡 weak: W0040: The function is non-trivial, exported, but not documented. [ok, ok, ok, @@ -317,7 +349,7 @@ mod tests { -export([handle_call/1]). main() -> - %%<^ weak: W0040: The function is non-trivial, exported, but not documented. + %%<^ 💡 weak: W0040: The function is non-trivial, exported, but not documented. [ok, ok, ok, @@ -349,7 +381,7 @@ mod tests { ok. complex() -> - %%<^^^^ weak: W0040: The function is non-trivial, exported, but not documented. + %%<^^^^ 💡 weak: W0040: The function is non-trivial, exported, but not documented. [ok, ok, ok, @@ -370,7 +402,7 @@ mod tests { ok. complex(a) -> - %%<^^^^ weak: W0040: The function is non-trivial, exported, but not documented. + %%<^^^^ 💡 weak: W0040: The function is non-trivial, exported, but not documented. [ok]; complex(b) -> [ok, From 717422ebfed8fc9d87473e2b410641ff02a84ae0 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Mon, 17 Nov 2025 02:22:16 -0800 Subject: [PATCH 022/142] Convert the no_catch linter to use a trait Summary: Simple conversion to use the new trait. Reviewed By: TD5 Differential Revision: D87078566 fbshipit-source-id: 5e019d3b968a78997d1ebe24f5b168cf8fce4b1f --- crates/ide/src/diagnostics.rs | 2 +- crates/ide/src/diagnostics/no_catch.rs | 77 +++++++++++++++++--------- 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index d01bb86d87..a6684a7144 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -1502,7 +1502,6 @@ pub fn diagnostics_descriptors<'a>() -> Vec<&'a DiagnosticDescriptor<'a>> { ¯o_precedence_suprise::DESCRIPTOR, &duplicate_module::DESCRIPTOR, &no_dialyzer_attribute::DESCRIPTOR, - &no_catch::DESCRIPTOR, &no_nowarn_suppressions::DESCRIPTOR, ] } @@ -1584,6 +1583,7 @@ const GENERIC_LINTERS: &[&dyn GenericDiagnostics] = &[ &missing_compile_warn_missing_spec::LINTER, &undocumented_module::LINTER, &undocumented_function::LINTER, + &no_catch::LINTER, ]; /// Unified registry for all types of linters diff --git a/crates/ide/src/diagnostics/no_catch.rs b/crates/ide/src/diagnostics/no_catch.rs index 3823131724..a804f248bd 100644 --- a/crates/ide/src/diagnostics/no_catch.rs +++ b/crates/ide/src/diagnostics/no_catch.rs @@ -8,7 +8,8 @@ * above-listed licenses. */ -use elp_ide_db::DiagnosticCode; +// Diagnostic: no-catch +use elp_ide_db::elp_base_db::FileId; use elp_syntax::AstNode; use hir::AnyExpr; use hir::AnyExprId; @@ -20,28 +21,48 @@ use hir::Strategy; use hir::fold::MacroStrategy; use hir::fold::ParenStrategy; -use super::DiagnosticConditions; -use super::DiagnosticDescriptor; -use crate::diagnostics::Diagnostic; -use crate::diagnostics::Severity; +use crate::diagnostics::DiagnosticCode; +use crate::diagnostics::GenericLinter; +use crate::diagnostics::GenericLinterMatchContext; +use crate::diagnostics::Linter; -const DIAGNOSTIC_CODE: DiagnosticCode = DiagnosticCode::NoCatch; -const DIAGNOSTIC_MESSAGE: &str = "Avoid `catch`."; -const DIAGNOSTIC_SEVERITY: Severity = Severity::Warning; +pub(crate) struct NoCatchLinter; -pub(crate) static DESCRIPTOR: DiagnosticDescriptor = DiagnosticDescriptor { - conditions: DiagnosticConditions { - experimental: false, - include_generated: false, - include_tests: true, - default_disabled: false, - }, - checker: &|diagnostics, sema, file_id, _ext| { - sema.for_each_function(file_id, |def| check_function(diagnostics, sema, def)); - }, -}; +impl Linter for NoCatchLinter { + fn id(&self) -> DiagnosticCode { + DiagnosticCode::NoCatch + } -fn check_function(diagnostics: &mut Vec, sema: &Semantic, def: &FunctionDef) { + fn description(&self) -> &'static str { + "Avoid `catch`." + } +} + +impl GenericLinter for NoCatchLinter { + type Context = (); + + fn matches( + &self, + sema: &Semantic, + file_id: FileId, + ) -> Option>> { + let mut res = Vec::new(); + sema.def_map_local(file_id) + .get_functions() + .for_each(|(_, def)| { + check_function(&mut res, sema, def); + }); + Some(res) + } +} + +pub static LINTER: NoCatchLinter = NoCatchLinter; + +fn check_function( + matches: &mut Vec>, + sema: &Semantic, + def: &FunctionDef, +) { let def_fb = def.in_function_body(sema, def); def_fb.fold_function( Strategy { @@ -52,15 +73,19 @@ fn check_function(diagnostics: &mut Vec, sema: &Semantic, def: &Func &mut |_acc, clause_id, ctx| { if let AnyExpr::Expr(Expr::Catch { expr: _ }) = ctx.item { let map = def_fb.get_body_map(clause_id); - if let Some(diagnostic) = make_diagnostic(sema, &map, ctx.item_id) { - diagnostics.push(diagnostic); + if let Some(match_context) = make_match_context(sema, &map, ctx.item_id) { + matches.push(match_context); } }; }, ) } -fn make_diagnostic(sema: &Semantic, map: &BodySourceMap, item_id: AnyExprId) -> Option { +fn make_match_context( + sema: &Semantic, + map: &BodySourceMap, + item_id: AnyExprId, +) -> Option> { match item_id { AnyExprId::Expr(expr_id) => { let ast_ptr = map.expr(expr_id)?; @@ -68,9 +93,7 @@ fn make_diagnostic(sema: &Semantic, map: &BodySourceMap, item_id: AnyExprId) -> elp_syntax::ast::Expr::CatchExpr(catch_expr) => { let catch_keyword = catch_expr.syntax().first_token()?; let range = catch_keyword.text_range(); - let diagnostic = Diagnostic::new(DIAGNOSTIC_CODE, DIAGNOSTIC_MESSAGE, range) - .with_severity(DIAGNOSTIC_SEVERITY); - Some(diagnostic) + Some(GenericLinterMatchContext { range, context: () }) } _ => None, } @@ -93,7 +116,7 @@ mod tests { catcher(X,Y) -> case catch X/Y of - %% ^^^^^ warning: W0052: Avoid `catch`. + %% ^^^^^ 💡 warning: W0052: Avoid `catch`. {'EXIT', {badarith,_}} -> "uh oh"; N -> N end. From 8394c84d6b4a5604b39c48a54deede5e084cfad5 Mon Sep 17 00:00:00 2001 From: Daniel Gorin Date: Mon, 17 Nov 2025 13:07:42 -0800 Subject: [PATCH 023/142] Bump docusaurus to 3.9.2 Summary: This also bumps js-yaml to 4.1.1, addressing CVE-2025-64718 Reviewed By: robertoaloi Differential Revision: D87222063 fbshipit-source-id: f36270df43f8412a880df7e730f562178a825156 --- website/package.json | 4 +- website/yarn.lock | 2233 +++++++++++++++++++----------------------- 2 files changed, 1026 insertions(+), 1211 deletions(-) diff --git a/website/package.json b/website/package.json index 4053a7477f..5d813e2035 100644 --- a/website/package.json +++ b/website/package.json @@ -16,8 +16,8 @@ "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { - "@docusaurus/core": "3.6.3", - "@docusaurus/preset-classic": "3.6.3", + "@docusaurus/core": "3.9.2", + "@docusaurus/preset-classic": "3.9.2", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "prism-react-renderer": "^2.3.0", diff --git a/website/yarn.lock b/website/yarn.lock index c07cd453c7..1aa091c893 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -2,274 +2,191 @@ # yarn lockfile v1 -"@algolia/autocomplete-core@1.17.7": - version "1.17.7" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz#2c410baa94a47c5c5f56ed712bb4a00ebe24088b" - integrity sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q== +"@ai-sdk/gateway@2.0.9": + version "2.0.9" + resolved "https://registry.yarnpkg.com/@ai-sdk/gateway/-/gateway-2.0.9.tgz#33b77dfee9a068df7bd8e11b4e10b9f1ee4228dd" + integrity sha512-E6x4h5CPPPJ0za1r5HsLtHbeI+Tp3H+YFtcH8G3dSSPFE6w+PZINzB4NxLZmg1QqSeA5HTP3ZEzzsohp0o2GEw== dependencies: - "@algolia/autocomplete-plugin-algolia-insights" "1.17.7" - "@algolia/autocomplete-shared" "1.17.7" + "@ai-sdk/provider" "2.0.0" + "@ai-sdk/provider-utils" "3.0.17" + "@vercel/oidc" "3.0.3" -"@algolia/autocomplete-plugin-algolia-insights@1.17.7": - version "1.17.7" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz#7d2b105f84e7dd8f0370aa4c4ab3b704e6760d82" - integrity sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A== +"@ai-sdk/provider-utils@3.0.17": + version "3.0.17" + resolved "https://registry.yarnpkg.com/@ai-sdk/provider-utils/-/provider-utils-3.0.17.tgz#2f3d0be398d3f165efe8dd252b63aea6ac3896d1" + integrity sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw== dependencies: - "@algolia/autocomplete-shared" "1.17.7" + "@ai-sdk/provider" "2.0.0" + "@standard-schema/spec" "^1.0.0" + eventsource-parser "^3.0.6" -"@algolia/autocomplete-preset-algolia@1.17.7": - version "1.17.7" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz#c9badc0d73d62db5bf565d839d94ec0034680ae9" - integrity sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA== +"@ai-sdk/provider@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@ai-sdk/provider/-/provider-2.0.0.tgz#b853c739d523b33675bc74b6c506b2c690bc602b" + integrity sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA== dependencies: - "@algolia/autocomplete-shared" "1.17.7" + json-schema "^0.4.0" -"@algolia/autocomplete-shared@1.17.7": - version "1.17.7" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz#105e84ad9d1a31d3fb86ba20dc890eefe1a313a0" - integrity sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg== - -"@algolia/cache-browser-local-storage@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.24.0.tgz#97bc6d067a9fd932b9c922faa6b7fd6e546e1348" - integrity sha512-t63W9BnoXVrGy9iYHBgObNXqYXM3tYXCjDSHeNwnsc324r4o5UiVKUiAB4THQ5z9U5hTj6qUvwg/Ez43ZD85ww== +"@ai-sdk/react@^2.0.30": + version "2.0.93" + resolved "https://registry.yarnpkg.com/@ai-sdk/react/-/react-2.0.93.tgz#c81f8550a2ac44799c7527a75be19b1c3cf60dac" + integrity sha512-2TzhpQr10HuWxpqyHpSAUMRUqD1G2O73J2sAaJChomVDbjr7BwpM0mdR3aRamCXNtuLiJmTFQhbNzw8fXMBdYw== dependencies: - "@algolia/cache-common" "4.24.0" + "@ai-sdk/provider-utils" "3.0.17" + ai "5.0.93" + swr "^2.2.5" + throttleit "2.1.0" -"@algolia/cache-common@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.24.0.tgz#81a8d3a82ceb75302abb9b150a52eba9960c9744" - integrity sha512-emi+v+DmVLpMGhp0V9q9h5CdkURsNmFC+cOS6uK9ndeJm9J4TiqSvPYVu+THUP8P/S08rxf5x2P+p3CfID0Y4g== - -"@algolia/cache-in-memory@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.24.0.tgz#ffcf8872f3a10cb85c4f4641bdffd307933a6e44" - integrity sha512-gDrt2so19jW26jY3/MkFg5mEypFIPbPoXsQGQWAi6TrCPsNOSEYepBMPlucqWigsmEy/prp5ug2jy/N3PVG/8w== +"@algolia/abtesting@1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@algolia/abtesting/-/abtesting-1.10.0.tgz#7f4c915d3e3188e6af101e6f4e829cda795d3caf" + integrity sha512-mQT3jwuTgX8QMoqbIR7mPlWkqQqBPQaPabQzm37xg2txMlaMogK/4hCiiESGdg39MlHZOVHeV+0VJuE7f5UK8A== dependencies: - "@algolia/cache-common" "4.24.0" + "@algolia/client-common" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" -"@algolia/client-abtesting@5.15.0": - version "5.15.0" - resolved "https://registry.yarnpkg.com/@algolia/client-abtesting/-/client-abtesting-5.15.0.tgz#6414895e2246dc7b7facd97bd98c3abe13cabe59" - integrity sha512-FaEM40iuiv1mAipYyiptP4EyxkJ8qHfowCpEeusdHUC4C7spATJYArD2rX3AxkVeREkDIgYEOuXcwKUbDCr7Nw== +"@algolia/autocomplete-core@1.19.2": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.19.2.tgz#702df67a08cb3cfe8c33ee1111ef136ec1a9e232" + integrity sha512-mKv7RyuAzXvwmq+0XRK8HqZXt9iZ5Kkm2huLjgn5JoCPtDy+oh9yxUMfDDaVCw0oyzZ1isdJBc7l9nuCyyR7Nw== dependencies: - "@algolia/client-common" "5.15.0" - "@algolia/requester-browser-xhr" "5.15.0" - "@algolia/requester-fetch" "5.15.0" - "@algolia/requester-node-http" "5.15.0" + "@algolia/autocomplete-plugin-algolia-insights" "1.19.2" + "@algolia/autocomplete-shared" "1.19.2" -"@algolia/client-account@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.24.0.tgz#eba7a921d828e7c8c40a32d4add21206c7fe12f1" - integrity sha512-adcvyJ3KjPZFDybxlqnf+5KgxJtBjwTPTeyG2aOyoJvx0Y8dUQAEOEVOJ/GBxX0WWNbmaSrhDURMhc+QeevDsA== +"@algolia/autocomplete-plugin-algolia-insights@1.19.2": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.2.tgz#3584b625b9317e333d1ae43664d02358e175c52d" + integrity sha512-TjxbcC/r4vwmnZaPwrHtkXNeqvlpdyR+oR9Wi2XyfORkiGkLTVhX2j+O9SaCCINbKoDfc+c2PB8NjfOnz7+oKg== dependencies: - "@algolia/client-common" "4.24.0" - "@algolia/client-search" "4.24.0" - "@algolia/transporter" "4.24.0" + "@algolia/autocomplete-shared" "1.19.2" -"@algolia/client-analytics@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.24.0.tgz#9d2576c46a9093a14e668833c505ea697a1a3e30" - integrity sha512-y8jOZt1OjwWU4N2qr8G4AxXAzaa8DBvyHTWlHzX/7Me1LX8OayfgHexqrsL4vSBcoMmVw2XnVW9MhL+Y2ZDJXg== +"@algolia/autocomplete-shared@1.19.2": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.2.tgz#c0b7b8dc30a5c65b70501640e62b009535e4578f" + integrity sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w== + +"@algolia/client-abtesting@5.44.0": + version "5.44.0" + resolved "https://registry.yarnpkg.com/@algolia/client-abtesting/-/client-abtesting-5.44.0.tgz#33e35fb59bfdb5bef26eb38902de5bdae3766e1e" + integrity sha512-KY5CcrWhRTUo/lV7KcyjrZkPOOF9bjgWpMj9z98VA+sXzVpZtkuskBLCKsWYFp2sbwchZFTd3wJM48H0IGgF7g== dependencies: - "@algolia/client-common" "4.24.0" - "@algolia/client-search" "4.24.0" - "@algolia/requester-common" "4.24.0" - "@algolia/transporter" "4.24.0" + "@algolia/client-common" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" -"@algolia/client-analytics@5.15.0": - version "5.15.0" - resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-5.15.0.tgz#7ca1043cba7ac225d30e8bb52579504946b95f58" - integrity sha512-lho0gTFsQDIdCwyUKTtMuf9nCLwq9jOGlLGIeQGKDxXF7HbiAysFIu5QW/iQr1LzMgDyM9NH7K98KY+BiIFriQ== +"@algolia/client-analytics@5.44.0": + version "5.44.0" + resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-5.44.0.tgz#2fdf0d41ac39fd071a9cde7c9a118b2ffb3ce8d9" + integrity sha512-LKOCE8S4ewI9bN3ot9RZoYASPi8b78E918/DVPW3HHjCMUe6i+NjbNG6KotU4RpP6AhRWZjjswbOkWelUO+OoA== dependencies: - "@algolia/client-common" "5.15.0" - "@algolia/requester-browser-xhr" "5.15.0" - "@algolia/requester-fetch" "5.15.0" - "@algolia/requester-node-http" "5.15.0" + "@algolia/client-common" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" -"@algolia/client-common@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.24.0.tgz#77c46eee42b9444a1d1c1583a83f7df4398a649d" - integrity sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA== +"@algolia/client-common@5.44.0": + version "5.44.0" + resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-5.44.0.tgz#aacc0fe3d07afae0d70bf7581b39d377f0bc0b7a" + integrity sha512-1yyJm4OYC2cztbS28XYVWwLXdwpLsMG4LoZLOltVglQ2+hc/i9q9fUDZyjRa2Bqt4DmkIfezagfMrokhyH4uxQ== + +"@algolia/client-insights@5.44.0": + version "5.44.0" + resolved "https://registry.yarnpkg.com/@algolia/client-insights/-/client-insights-5.44.0.tgz#350f93bab703fa102acf82685364732937369025" + integrity sha512-wVQWK6jYYsbEOjIMI+e5voLGPUIbXrvDj392IckXaCPvQ6vCMTXakQqOYCd+znQdL76S+3wHDo77HZWiAYKrtA== dependencies: - "@algolia/requester-common" "4.24.0" - "@algolia/transporter" "4.24.0" + "@algolia/client-common" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" -"@algolia/client-common@5.15.0": - version "5.15.0" - resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-5.15.0.tgz#cd47ae07a3afc7065438a2dab29f8434f848928e" - integrity sha512-IofrVh213VLsDkPoSKMeM9Dshrv28jhDlBDLRcVJQvlL8pzue7PEB1EZ4UoJFYS3NSn7JOcJ/V+olRQzXlJj1w== - -"@algolia/client-insights@5.15.0": - version "5.15.0" - resolved "https://registry.yarnpkg.com/@algolia/client-insights/-/client-insights-5.15.0.tgz#f3bead0edd10e69365895da4a96044064b504f4d" - integrity sha512-bDDEQGfFidDi0UQUCbxXOCdphbVAgbVmxvaV75cypBTQkJ+ABx/Npw7LkFGw1FsoVrttlrrQbwjvUB6mLVKs/w== +"@algolia/client-personalization@5.44.0": + version "5.44.0" + resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-5.44.0.tgz#b2c20dc59026babd695c571eb6015a8b46acb892" + integrity sha512-lkgRjOjOkqmIkebHjHpU9rLJcJNUDMm+eVSW/KJQYLjGqykEZxal+nYJJTBbLceEU2roByP/+27ZmgIwCdf0iA== dependencies: - "@algolia/client-common" "5.15.0" - "@algolia/requester-browser-xhr" "5.15.0" - "@algolia/requester-fetch" "5.15.0" - "@algolia/requester-node-http" "5.15.0" + "@algolia/client-common" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" -"@algolia/client-personalization@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.24.0.tgz#8b47789fb1cb0f8efbea0f79295b7c5a3850f6ae" - integrity sha512-l5FRFm/yngztweU0HdUzz1rC4yoWCFo3IF+dVIVTfEPg906eZg5BOd1k0K6rZx5JzyyoP4LdmOikfkfGsKVE9w== +"@algolia/client-query-suggestions@5.44.0": + version "5.44.0" + resolved "https://registry.yarnpkg.com/@algolia/client-query-suggestions/-/client-query-suggestions-5.44.0.tgz#d39d0b21fe6b38f8dab2f1f201db6d6e5225908f" + integrity sha512-sYfhgwKu6NDVmZHL1WEKVLsOx/jUXCY4BHKLUOcYa8k4COCs6USGgz6IjFkUf+niwq8NCECMmTC4o/fVQOalsA== dependencies: - "@algolia/client-common" "4.24.0" - "@algolia/requester-common" "4.24.0" - "@algolia/transporter" "4.24.0" + "@algolia/client-common" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" -"@algolia/client-personalization@5.15.0": - version "5.15.0" - resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-5.15.0.tgz#e962793ebf737a5ffa4867d2dfdfe17924be3833" - integrity sha512-LfaZqLUWxdYFq44QrasCDED5bSYOswpQjSiIL7Q5fYlefAAUO95PzBPKCfUhSwhb4rKxigHfDkd81AvEicIEoA== +"@algolia/client-search@5.44.0": + version "5.44.0" + resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-5.44.0.tgz#04ca43ee8181bf16f9df085286214ad3c06de126" + integrity sha512-/FRKUM1G4xn3vV8+9xH1WJ9XknU8rkBGlefruq9jDhYUAvYozKimhrmC2pRqw/RyHhPivmgZCRuC8jHP8piz4Q== dependencies: - "@algolia/client-common" "5.15.0" - "@algolia/requester-browser-xhr" "5.15.0" - "@algolia/requester-fetch" "5.15.0" - "@algolia/requester-node-http" "5.15.0" - -"@algolia/client-query-suggestions@5.15.0": - version "5.15.0" - resolved "https://registry.yarnpkg.com/@algolia/client-query-suggestions/-/client-query-suggestions-5.15.0.tgz#d9a2d0d0660241bdae5fc36a6f1fcf339abbafeb" - integrity sha512-wu8GVluiZ5+il8WIRsGKu8VxMK9dAlr225h878GGtpTL6VBvwyJvAyLdZsfFIpY0iN++jiNb31q2C1PlPL+n/A== - dependencies: - "@algolia/client-common" "5.15.0" - "@algolia/requester-browser-xhr" "5.15.0" - "@algolia/requester-fetch" "5.15.0" - "@algolia/requester-node-http" "5.15.0" - -"@algolia/client-search@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.24.0.tgz#75e6c02d33ef3e0f34afd9962c085b856fc4a55f" - integrity sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA== - dependencies: - "@algolia/client-common" "4.24.0" - "@algolia/requester-common" "4.24.0" - "@algolia/transporter" "4.24.0" - -"@algolia/client-search@5.15.0": - version "5.15.0" - resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-5.15.0.tgz#8645f5bc87a959b8008e021d8b31d55a47920b94" - integrity sha512-Z32gEMrRRpEta5UqVQA612sLdoqY3AovvUPClDfMxYrbdDAebmGDVPtSogUba1FZ4pP5dx20D3OV3reogLKsRA== - dependencies: - "@algolia/client-common" "5.15.0" - "@algolia/requester-browser-xhr" "5.15.0" - "@algolia/requester-fetch" "5.15.0" - "@algolia/requester-node-http" "5.15.0" + "@algolia/client-common" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" "@algolia/events@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@algolia/events/-/events-4.0.1.tgz#fd39e7477e7bc703d7f893b556f676c032af3950" integrity sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ== -"@algolia/ingestion@1.15.0": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@algolia/ingestion/-/ingestion-1.15.0.tgz#a3f3ec2139042f8597c2a975430a6f77cd764db3" - integrity sha512-MkqkAxBQxtQ5if/EX2IPqFA7LothghVyvPoRNA/meS2AW2qkHwcxjuiBxv4H6mnAVEPfJlhu9rkdVz9LgCBgJg== +"@algolia/ingestion@1.44.0": + version "1.44.0" + resolved "https://registry.yarnpkg.com/@algolia/ingestion/-/ingestion-1.44.0.tgz#f6bd13216c5a589414f43f9bec78db55cb022d90" + integrity sha512-5+S5ynwMmpTpCLXGjTDpeIa81J+R4BLH0lAojOhmeGSeGEHQTqacl/4sbPyDTcidvnWhaqtyf8m42ue6lvISAw== dependencies: - "@algolia/client-common" "5.15.0" - "@algolia/requester-browser-xhr" "5.15.0" - "@algolia/requester-fetch" "5.15.0" - "@algolia/requester-node-http" "5.15.0" + "@algolia/client-common" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" -"@algolia/logger-common@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.24.0.tgz#28d439976019ec0a46ba7a1a739ef493d4ef8123" - integrity sha512-LLUNjkahj9KtKYrQhFKCzMx0BY3RnNP4FEtO+sBybCjJ73E8jNdaKJ/Dd8A/VA4imVHP5tADZ8pn5B8Ga/wTMA== - -"@algolia/logger-console@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.24.0.tgz#c6ff486036cd90b81d07a95aaba04461da7e1c65" - integrity sha512-X4C8IoHgHfiUROfoRCV+lzSy+LHMgkoEEU1BbKcsfnV0i0S20zyy0NLww9dwVHUWNfPPxdMU+/wKmLGYf96yTg== +"@algolia/monitoring@1.44.0": + version "1.44.0" + resolved "https://registry.yarnpkg.com/@algolia/monitoring/-/monitoring-1.44.0.tgz#423fa61d68826a72cd5c468b45b232d8ba379775" + integrity sha512-xhaTN8pXJjR6zkrecg4Cc9YZaQK2LKm2R+LkbAq+AYGBCWJxtSGlNwftozZzkUyq4AXWoyoc0x2SyBtq5LRtqQ== dependencies: - "@algolia/logger-common" "4.24.0" + "@algolia/client-common" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" -"@algolia/monitoring@1.15.0": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@algolia/monitoring/-/monitoring-1.15.0.tgz#1eb58722ec9ea6e5de3621150f97a43571bd312e" - integrity sha512-QPrFnnGLMMdRa8t/4bs7XilPYnoUXDY8PMQJ1sf9ZFwhUysYYhQNX34/enoO0LBjpoOY6rLpha39YQEFbzgKyQ== +"@algolia/recommend@5.44.0": + version "5.44.0" + resolved "https://registry.yarnpkg.com/@algolia/recommend/-/recommend-5.44.0.tgz#7e52b3e612bfd5bf82d14e05f577888a038279fa" + integrity sha512-GNcite/uOIS7wgRU1MT7SdNIupGSW+vbK9igIzMePvD2Dl8dy0O3urKPKIbTuZQqiVH1Cb84y5cgLvwNrdCj/Q== dependencies: - "@algolia/client-common" "5.15.0" - "@algolia/requester-browser-xhr" "5.15.0" - "@algolia/requester-fetch" "5.15.0" - "@algolia/requester-node-http" "5.15.0" + "@algolia/client-common" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" -"@algolia/recommend@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@algolia/recommend/-/recommend-4.24.0.tgz#8a3f78aea471ee0a4836b78fd2aad4e9abcaaf34" - integrity sha512-P9kcgerfVBpfYHDfVZDvvdJv0lEoCvzNlOy2nykyt5bK8TyieYyiD0lguIJdRZZYGre03WIAFf14pgE+V+IBlw== +"@algolia/requester-browser-xhr@5.44.0": + version "5.44.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.44.0.tgz#a007b2c7b665224b04e61a1246fa015f06bd4100" + integrity sha512-YZHBk72Cd7pcuNHzbhNzF/FbbYszlc7JhZlDyQAchnX5S7tcemSS96F39Sy8t4O4WQLpFvUf1MTNedlitWdOsQ== dependencies: - "@algolia/cache-browser-local-storage" "4.24.0" - "@algolia/cache-common" "4.24.0" - "@algolia/cache-in-memory" "4.24.0" - "@algolia/client-common" "4.24.0" - "@algolia/client-search" "4.24.0" - "@algolia/logger-common" "4.24.0" - "@algolia/logger-console" "4.24.0" - "@algolia/requester-browser-xhr" "4.24.0" - "@algolia/requester-common" "4.24.0" - "@algolia/requester-node-http" "4.24.0" - "@algolia/transporter" "4.24.0" + "@algolia/client-common" "5.44.0" -"@algolia/recommend@5.15.0": - version "5.15.0" - resolved "https://registry.yarnpkg.com/@algolia/recommend/-/recommend-5.15.0.tgz#8f3359ee7e855849ac3872f67c0672f6835c8f79" - integrity sha512-5eupMwSqMLDObgSMF0XG958zR6GJP3f7jHDQ3/WlzCM9/YIJiWIUoJFGsko9GYsA5xbLDHE/PhWtq4chcCdaGQ== +"@algolia/requester-fetch@5.44.0": + version "5.44.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-fetch/-/requester-fetch-5.44.0.tgz#055202db6b1ab37e042f207acb9e7f788e710089" + integrity sha512-B9WHl+wQ7uf46t9cq+vVM/ypVbOeuldVDq9OtKsX2ApL2g/htx6ImB9ugDOOJmB5+fE31/XPTuCcYz/j03+idA== dependencies: - "@algolia/client-common" "5.15.0" - "@algolia/requester-browser-xhr" "5.15.0" - "@algolia/requester-fetch" "5.15.0" - "@algolia/requester-node-http" "5.15.0" + "@algolia/client-common" "5.44.0" -"@algolia/requester-browser-xhr@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz#313c5edab4ed73a052e75803855833b62dd19c16" - integrity sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA== +"@algolia/requester-node-http@5.44.0": + version "5.44.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-5.44.0.tgz#3e2f56491303d1a94d172e98a1560af350950c01" + integrity sha512-MULm0qeAIk4cdzZ/ehJnl1o7uB5NMokg83/3MKhPq0Pk7+I0uELGNbzIfAkvkKKEYcHALemKdArtySF9eKzh/A== dependencies: - "@algolia/requester-common" "4.24.0" - -"@algolia/requester-browser-xhr@5.15.0": - version "5.15.0" - resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.15.0.tgz#5ffdccdf5cd7814ed3486bed418edb6db25c32a2" - integrity sha512-Po/GNib6QKruC3XE+WKP1HwVSfCDaZcXu48kD+gwmtDlqHWKc7Bq9lrS0sNZ456rfCKhXksOmMfUs4wRM/Y96w== - dependencies: - "@algolia/client-common" "5.15.0" - -"@algolia/requester-common@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.24.0.tgz#1c60c198031f48fcdb9e34c4057a3ea987b9a436" - integrity sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA== - -"@algolia/requester-fetch@5.15.0": - version "5.15.0" - resolved "https://registry.yarnpkg.com/@algolia/requester-fetch/-/requester-fetch-5.15.0.tgz#2ce94d4855090fac192b208d95eeea22e1ca4489" - integrity sha512-rOZ+c0P7ajmccAvpeeNrUmEKoliYFL8aOR5qGW5pFq3oj3Iept7Y5mEtEsOBYsRt6qLnaXn4zUKf+N8nvJpcIw== - dependencies: - "@algolia/client-common" "5.15.0" - -"@algolia/requester-node-http@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz#4461593714031d02aa7da221c49df675212f482f" - integrity sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw== - dependencies: - "@algolia/requester-common" "4.24.0" - -"@algolia/requester-node-http@5.15.0": - version "5.15.0" - resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-5.15.0.tgz#e2020afcdaea56dc204bc6c82daab41478b32d87" - integrity sha512-b1jTpbFf9LnQHEJP5ddDJKE2sAlhYd7EVSOWgzo/27n/SfCoHfqD0VWntnWYD83PnOKvfe8auZ2+xCb0TXotrQ== - dependencies: - "@algolia/client-common" "5.15.0" - -"@algolia/transporter@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.24.0.tgz#226bb1f8af62430374c1972b2e5c8580ab275102" - integrity sha512-86nI7w6NzWxd1Zp9q3413dRshDqAzSbsQjhcDhPIatEFiZrL1/TjnHL8S7jVKFePlIMzDsZWXAXwXzcok9c5oA== - dependencies: - "@algolia/cache-common" "4.24.0" - "@algolia/logger-common" "4.24.0" - "@algolia/requester-common" "4.24.0" + "@algolia/client-common" "5.44.0" "@ampproject/remapping@^2.2.0": version "2.3.0" @@ -292,7 +209,7 @@ resolved "https://registry.yarnpkg.com/@antfu/utils/-/utils-8.1.1.tgz#95b1947d292a9a2efffba2081796dcaa05ecedfb" integrity sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ== -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2", "@babel/code-frame@^7.8.3": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2": version "7.26.2" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== @@ -1246,92 +1163,136 @@ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== -"@csstools/cascade-layer-name-parser@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.4.tgz#64d128529397aa1e1c986f685713363b262b81b1" - integrity sha512-7DFHlPuIxviKYZrOiwVU/PiHLm3lLUR23OMuEEtfEOQTOp9hzQ2JjdY6X5H18RVuUPJqSCI+qNnD5iOLMVE0bA== +"@csstools/cascade-layer-name-parser@^2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.5.tgz#43f962bebead0052a9fed1a2deeb11f85efcbc72" + integrity sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A== -"@csstools/color-helpers@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@csstools/color-helpers/-/color-helpers-5.0.1.tgz#829f1c76f5800b79c51c709e2f36821b728e0e10" - integrity sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA== +"@csstools/color-helpers@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@csstools/color-helpers/-/color-helpers-5.1.0.tgz#106c54c808cabfd1ab4c602d8505ee584c2996ef" + integrity sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA== -"@csstools/css-calc@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@csstools/css-calc/-/css-calc-2.1.0.tgz#3f28b8f8f736b8f78abbc75eebd55c756207e773" - integrity sha512-X69PmFOrjTZfN5ijxtI8hZ9kRADFSLrmmQ6hgDJ272Il049WGKpDY64KhrFm/7rbWve0z81QepawzjkKlqkNGw== +"@csstools/css-calc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@csstools/css-calc/-/css-calc-2.1.4.tgz#8473f63e2fcd6e459838dd412401d5948f224c65" + integrity sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ== -"@csstools/css-color-parser@^3.0.6": - version "3.0.6" - resolved "https://registry.yarnpkg.com/@csstools/css-color-parser/-/css-color-parser-3.0.6.tgz#e646838f6aab4618aeea7ba0c4921a254e180276" - integrity sha512-S/IjXqTHdpI4EtzGoNCHfqraXF37x12ZZHA1Lk7zoT5pm2lMjFuqhX/89L7dqX4CcMacKK+6ZCs5TmEGb/+wKw== +"@csstools/css-color-parser@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz#4e386af3a99dd36c46fef013cfe4c1c341eed6f0" + integrity sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA== dependencies: - "@csstools/color-helpers" "^5.0.1" - "@csstools/css-calc" "^2.1.0" + "@csstools/color-helpers" "^5.1.0" + "@csstools/css-calc" "^2.1.4" -"@csstools/css-parser-algorithms@^3.0.4": +"@csstools/css-parser-algorithms@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz#5755370a9a29abaec5515b43c8b3f2cf9c2e3076" + integrity sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ== + +"@csstools/css-tokenizer@^3.0.4": version "3.0.4" - resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz#74426e93bd1c4dcab3e441f5cc7ba4fb35d94356" - integrity sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A== + resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz#333fedabc3fd1a8e5d0100013731cf19e6a8c5d3" + integrity sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw== -"@csstools/css-tokenizer@^3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz#a5502c8539265fecbd873c1e395a890339f119c2" - integrity sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw== +"@csstools/media-query-list-parser@^4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz#7aec77bcb89c2da80ef207e73f474ef9e1b3cdf1" + integrity sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ== -"@csstools/media-query-list-parser@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz#e80e17eba1693fceafb8d6f2cfc68c0e7a9ab78a" - integrity sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A== +"@csstools/postcss-alpha-function@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-alpha-function/-/postcss-alpha-function-1.0.1.tgz#7989605711de7831bc7cd75b94c9b5bac9c3728e" + integrity sha512-isfLLwksH3yHkFXfCI2Gcaqg7wGGHZZwunoJzEZk0yKYIokgre6hYVFibKL3SYAoR1kBXova8LB+JoO5vZzi9w== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" -"@csstools/postcss-cascade-layers@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.1.tgz#9640313e64b5e39133de7e38a5aa7f40dc259597" - integrity sha512-XOfhI7GShVcKiKwmPAnWSqd2tBR0uxt+runAxttbSp/LY2U16yAVPmAf7e9q4JJ0d+xMNmpwNDLBXnmRCl3HMQ== +"@csstools/postcss-cascade-layers@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.2.tgz#dd2c70db3867b88975f2922da3bfbae7d7a2cae7" + integrity sha512-nWBE08nhO8uWl6kSAeCx4im7QfVko3zLrtgWZY4/bP87zrSPpSyN/3W3TDqz1jJuH+kbKOHXg5rJnK+ZVYcFFg== dependencies: "@csstools/selector-specificity" "^5.0.0" postcss-selector-parser "^7.0.0" -"@csstools/postcss-color-function@^4.0.6": - version "4.0.6" - resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function/-/postcss-color-function-4.0.6.tgz#dabd1e516ccd4c7bd5803e37075a503b5f7f0ac4" - integrity sha512-EcvXfC60cTIumzpsxWuvVjb7rsJEHPvqn3jeMEBUaE3JSc4FRuP7mEQ+1eicxWmIrs3FtzMH9gR3sgA5TH+ebQ== +"@csstools/postcss-color-function-display-p3-linear@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function-display-p3-linear/-/postcss-color-function-display-p3-linear-1.0.1.tgz#3017ff5e1f65307d6083e58e93d76724fb1ebf9f" + integrity sha512-E5qusdzhlmO1TztYzDIi8XPdPoYOjoTY6HBYBCYSj+Gn4gQRBlvjgPQXzfzuPQqt8EhkC/SzPKObg4Mbn8/xMg== dependencies: - "@csstools/css-color-parser" "^3.0.6" - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" - "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" -"@csstools/postcss-color-mix-function@^3.0.6": - version "3.0.6" - resolved "https://registry.yarnpkg.com/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.6.tgz#d971832ec30b3b60363bceddfeb4b90c7cc0f4b8" - integrity sha512-jVKdJn4+JkASYGhyPO+Wa5WXSx1+oUgaXb3JsjJn/BlrtFh5zjocCY7pwWi0nuP24V1fY7glQsxEYcYNy0dMFg== +"@csstools/postcss-color-function@^4.0.12": + version "4.0.12" + resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function/-/postcss-color-function-4.0.12.tgz#a7c85a98c77b522a194a1bbb00dd207f40c7a771" + integrity sha512-yx3cljQKRaSBc2hfh8rMZFZzChaFgwmO2JfFgFr1vMcF3C/uyy5I4RFIBOIWGq1D+XbKCG789CGkG6zzkLpagA== dependencies: - "@csstools/css-color-parser" "^3.0.6" - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" - "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" -"@csstools/postcss-content-alt-text@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.4.tgz#76f4687fb15ed45bc1139bb71e5775779762897a" - integrity sha512-YItlZUOuZJCBlRaCf8Aucc1lgN41qYGALMly0qQllrxYJhiyzlI6RxOTMUvtWk+KhS8GphMDsDhKQ7KTPfEMSw== +"@csstools/postcss-color-mix-function@^3.0.12": + version "3.0.12" + resolved "https://registry.yarnpkg.com/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.12.tgz#2f1ee9f8208077af069545c9bd79bb9733382c2a" + integrity sha512-4STERZfCP5Jcs13P1U5pTvI9SkgLgfMUMhdXW8IlJWkzOOOqhZIjcNhWtNJZes2nkBDsIKJ0CJtFtuaZ00moag== dependencies: - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" - "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" -"@csstools/postcss-exponential-functions@^2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.5.tgz#0c39f75df3357ee1e444b0aa0ede4e12aafea0e9" - integrity sha512-mi8R6dVfA2nDoKM3wcEi64I8vOYEgQVtVKCfmLHXupeLpACfGAided5ddMt5f+CnEodNu4DifuVwb0I6fQDGGQ== +"@csstools/postcss-color-mix-variadic-function-arguments@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@csstools/postcss-color-mix-variadic-function-arguments/-/postcss-color-mix-variadic-function-arguments-1.0.2.tgz#b4012b62a4eaa24d694172bb7137f9d2319cb8f2" + integrity sha512-rM67Gp9lRAkTo+X31DUqMEq+iK+EFqsidfecmhrteErxJZb6tUoJBVQca1Vn1GpDql1s1rD1pKcuYzMsg7Z1KQ== dependencies: - "@csstools/css-calc" "^2.1.0" - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-content-alt-text@^2.0.8": + version "2.0.8" + resolved "https://registry.yarnpkg.com/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.8.tgz#1d52da1762893c32999ff76839e48d6ec7c7a4cb" + integrity sha512-9SfEW9QCxEpTlNMnpSqFaHyzsiRpZ5J5+KqCu1u5/eEJAWsMhzT40qf0FIbeeglEvrGRMdDzAxMIz3wqoGSb+Q== + dependencies: + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-contrast-color-function@^2.0.12": + version "2.0.12" + resolved "https://registry.yarnpkg.com/@csstools/postcss-contrast-color-function/-/postcss-contrast-color-function-2.0.12.tgz#ca46986d095c60f208d9e3f24704d199c9172637" + integrity sha512-YbwWckjK3qwKjeYz/CijgcS7WDUCtKTd8ShLztm3/i5dhh4NaqzsbYnhm4bjrpFpnLZ31jVcbK8YL77z3GBPzA== + dependencies: + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-exponential-functions@^2.0.9": + version "2.0.9" + resolved "https://registry.yarnpkg.com/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.9.tgz#fc03d1272888cb77e64cc1a7d8a33016e4f05c69" + integrity sha512-abg2W/PI3HXwS/CZshSa79kNWNZHdJPMBXeZNyPQFbbj8sKO3jXxOt/wF7juJVjyDTc6JrvaUZYFcSBZBhaxjw== + dependencies: + "@csstools/css-calc" "^2.1.4" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" "@csstools/postcss-font-format-keywords@^4.0.0": version "4.0.0" @@ -1341,67 +1302,67 @@ "@csstools/utilities" "^2.0.0" postcss-value-parser "^4.2.0" -"@csstools/postcss-gamut-mapping@^2.0.6": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.6.tgz#04ec6a50fdbca2a30dec56e6bb780c79621e47a7" - integrity sha512-0ke7fmXfc8H+kysZz246yjirAH6JFhyX9GTlyRnM0exHO80XcA9zeJpy5pOp5zo/AZiC/q5Pf+Hw7Pd6/uAoYA== +"@csstools/postcss-gamut-mapping@^2.0.11": + version "2.0.11" + resolved "https://registry.yarnpkg.com/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.11.tgz#be0e34c9f0142852cccfc02b917511f0d677db8b" + integrity sha512-fCpCUgZNE2piVJKC76zFsgVW1apF6dpYsqGyH8SIeCcM4pTEsRTWTLCaJIMKFEundsCKwY1rwfhtrio04RJ4Dw== dependencies: - "@csstools/css-color-parser" "^3.0.6" - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" -"@csstools/postcss-gradients-interpolation-method@^5.0.6": - version "5.0.6" - resolved "https://registry.yarnpkg.com/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.6.tgz#67fa61ada95e4534687fa76cd2d15ac74386560e" - integrity sha512-Itrbx6SLUzsZ6Mz3VuOlxhbfuyLTogG5DwEF1V8dAi24iMuvQPIHd7Ti+pNDp7j6WixndJGZaoNR0f9VSzwuTg== +"@csstools/postcss-gradients-interpolation-method@^5.0.12": + version "5.0.12" + resolved "https://registry.yarnpkg.com/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.12.tgz#0955cce4d97203b861bf66742bbec611b2f3661c" + integrity sha512-jugzjwkUY0wtNrZlFeyXzimUL3hN4xMvoPnIXxoZqxDvjZRiSh+itgHcVUWzJ2VwD/VAMEgCLvtaJHX+4Vj3Ow== dependencies: - "@csstools/css-color-parser" "^3.0.6" - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" - "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" -"@csstools/postcss-hwb-function@^4.0.6": - version "4.0.6" - resolved "https://registry.yarnpkg.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.6.tgz#c40f557a54ed45e75c601a9ba7a08d315f64dbd7" - integrity sha512-927Pqy3a1uBP7U8sTfaNdZVB0mNXzIrJO/GZ8us9219q9n06gOqCdfZ0E6d1P66Fm0fYHvxfDbfcUuwAn5UwhQ== +"@csstools/postcss-hwb-function@^4.0.12": + version "4.0.12" + resolved "https://registry.yarnpkg.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.12.tgz#07f7ecb08c50e094673bd20eaf7757db0162beee" + integrity sha512-mL/+88Z53KrE4JdePYFJAQWFrcADEqsLprExCM04GDNgHIztwFzj0Mbhd/yxMBngq0NIlz58VVxjt5abNs1VhA== dependencies: - "@csstools/css-color-parser" "^3.0.6" - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" - "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" -"@csstools/postcss-ic-unit@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.0.tgz#b60ec06500717c337447c39ae7fe7952eeb9d48f" - integrity sha512-9QT5TDGgx7wD3EEMN3BSUG6ckb6Eh5gSPT5kZoVtUuAonfPmLDJyPhqR4ntPpMYhUKAMVKAg3I/AgzqHMSeLhA== +"@csstools/postcss-ic-unit@^4.0.4": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.4.tgz#2ee2da0690db7edfbc469279711b9e69495659d2" + integrity sha512-yQ4VmossuOAql65sCPppVO1yfb7hDscf4GseF0VCA/DTDaBc0Wtf8MTqVPfjGYlT5+2buokG0Gp7y0atYZpwjg== dependencies: - "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" postcss-value-parser "^4.2.0" -"@csstools/postcss-initial@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-initial/-/postcss-initial-2.0.0.tgz#a86f5fc59ab9f16f1422dade4c58bd941af5df22" - integrity sha512-dv2lNUKR+JV+OOhZm9paWzYBXOCi+rJPqJ2cJuhh9xd8USVrd0cBEPczla81HNOyThMQWeCcdln3gZkQV2kYxA== +"@csstools/postcss-initial@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-initial/-/postcss-initial-2.0.1.tgz#c385bd9d8ad31ad159edd7992069e97ceea4d09a" + integrity sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg== -"@csstools/postcss-is-pseudo-class@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.1.tgz#12041448fedf01090dd4626022c28b7f7623f58e" - integrity sha512-JLp3POui4S1auhDR0n8wHd/zTOWmMsmK3nQd3hhL6FhWPaox5W7j1se6zXOG/aP07wV2ww0lxbKYGwbBszOtfQ== +"@csstools/postcss-is-pseudo-class@^5.0.3": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.3.tgz#d34e850bcad4013c2ed7abe948bfa0448aa8eb74" + integrity sha512-jS/TY4SpG4gszAtIg7Qnf3AS2pjcUM5SzxpApOrlndMeGhIbaTzWBzzP/IApXoNWEW7OhcjkRT48jnAUIFXhAQ== dependencies: "@csstools/selector-specificity" "^5.0.0" postcss-selector-parser "^7.0.0" -"@csstools/postcss-light-dark-function@^2.0.7": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.7.tgz#807c170cd28eebb0c00e64dfc6ab0bf418f19209" - integrity sha512-ZZ0rwlanYKOHekyIPaU+sVm3BEHCe+Ha0/px+bmHe62n0Uc1lL34vbwrLYn6ote8PHlsqzKeTQdIejQCJ05tfw== +"@csstools/postcss-light-dark-function@^2.0.11": + version "2.0.11" + resolved "https://registry.yarnpkg.com/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.11.tgz#0df448aab9a33cb9a085264ff1f396fb80c4437d" + integrity sha512-fNJcKXJdPM3Lyrbmgw2OBbaioU7yuKZtiXClf4sGdQttitijYlZMD5K7HrC/eF83VRWRrYq6OZ0Lx92leV2LFA== dependencies: - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" - "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" "@csstools/postcss-logical-float-and-clear@^3.0.0": @@ -1426,32 +1387,32 @@ dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-logical-viewport-units@^3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.3.tgz#f6cc63520ca2a6eb76b9cd946070c38dda66d733" - integrity sha512-OC1IlG/yoGJdi0Y+7duz/kU/beCwO+Gua01sD6GtOtLi7ByQUpcIqs7UE/xuRPay4cHgOMatWdnDdsIDjnWpPw== +"@csstools/postcss-logical-viewport-units@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.4.tgz#016d98a8b7b5f969e58eb8413447eb801add16fc" + integrity sha512-q+eHV1haXA4w9xBwZLKjVKAWn3W2CMqmpNpZUk5kRprvSiBEGMgrNH3/sJZ8UA3JgyHaOt3jwT9uFa4wLX4EqQ== dependencies: - "@csstools/css-tokenizer" "^3.0.3" + "@csstools/css-tokenizer" "^3.0.4" "@csstools/utilities" "^2.0.0" -"@csstools/postcss-media-minmax@^2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.5.tgz#66970aa8d8057f84b88aff21f385194fbe03eb11" - integrity sha512-sdh5i5GToZOIAiwhdntRWv77QDtsxP2r2gXW/WbLSCoLr00KTq/yiF1qlQ5XX2+lmiFa8rATKMcbwl3oXDMNew== +"@csstools/postcss-media-minmax@^2.0.9": + version "2.0.9" + resolved "https://registry.yarnpkg.com/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.9.tgz#184252d5b93155ae526689328af6bdf3fc113987" + integrity sha512-af9Qw3uS3JhYLnCbqtZ9crTvvkR+0Se+bBqSr7ykAnl9yKhk6895z9rf+2F4dClIDJWxgn0iZZ1PSdkhrbs2ig== dependencies: - "@csstools/css-calc" "^2.1.0" - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" - "@csstools/media-query-list-parser" "^4.0.2" + "@csstools/css-calc" "^2.1.4" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/media-query-list-parser" "^4.0.3" -"@csstools/postcss-media-queries-aspect-ratio-number-values@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.4.tgz#d71102172c74baf3f892fac88cf1ea46a961600d" - integrity sha512-AnGjVslHMm5xw9keusQYvjVWvuS7KWK+OJagaG0+m9QnIjZsrysD2kJP/tr/UJIyYtMCtu8OkUd+Rajb4DqtIQ== +"@csstools/postcss-media-queries-aspect-ratio-number-values@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.5.tgz#f485c31ec13d6b0fb5c528a3474334a40eff5f11" + integrity sha512-zhAe31xaaXOY2Px8IYfoVTB3wglbJUVigGphFLj6exb7cjZRH9A6adyE22XfFK3P2PzwRk0VDeTJmaxpluyrDg== dependencies: - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" - "@csstools/media-query-list-parser" "^4.0.2" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/media-query-list-parser" "^4.0.3" "@csstools/postcss-nested-calc@^4.0.0": version "4.0.0" @@ -1468,42 +1429,42 @@ dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-oklab-function@^4.0.6": - version "4.0.6" - resolved "https://registry.yarnpkg.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.6.tgz#17e8dfb6422dfd8d77256def5d5be8335ea7af34" - integrity sha512-Hptoa0uX+XsNacFBCIQKTUBrFKDiplHan42X73EklG6XmQLG7/aIvxoNhvZ7PvOWMt67Pw3bIlUY2nD6p5vL8A== +"@csstools/postcss-oklab-function@^4.0.12": + version "4.0.12" + resolved "https://registry.yarnpkg.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.12.tgz#416640ef10227eea1375b47b72d141495950971d" + integrity sha512-HhlSmnE1NKBhXsTnNGjxvhryKtO7tJd1w42DKOGFD6jSHtYOrsJTQDKPMwvOfrzUAk8t7GcpIfRyM7ssqHpFjg== dependencies: - "@csstools/css-color-parser" "^3.0.6" - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" - "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" -"@csstools/postcss-progressive-custom-properties@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.0.0.tgz#ecdb85bcdb1852d73970a214a376684a91f82bdc" - integrity sha512-XQPtROaQjomnvLUSy/bALTR5VCtTVUFwYs1SblvYgLSeTo2a/bMNwUwo2piXw5rTv/FEYiy5yPSXBqg9OKUx7Q== +"@csstools/postcss-progressive-custom-properties@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.2.1.tgz#c39780b9ff0d554efb842b6bd75276aa6f1705db" + integrity sha512-uPiiXf7IEKtUQXsxu6uWtOlRMXd2QWWy5fhxHDnPdXKCQckPP3E34ZgDoZ62r2iT+UOgWsSbM4NvHE5m3mAEdw== dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-random-function@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@csstools/postcss-random-function/-/postcss-random-function-1.0.1.tgz#73a0b62b5dbbc03c25a28f085235eb61b09a2fb0" - integrity sha512-Ab/tF8/RXktQlFwVhiC70UNfpFQRhtE5fQQoP2pO+KCPGLsLdWFiOuHgSRtBOqEshCVAzR4H6o38nhvRZq8deA== +"@csstools/postcss-random-function@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-random-function/-/postcss-random-function-2.0.1.tgz#3191f32fe72936e361dadf7dbfb55a0209e2691e" + integrity sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w== dependencies: - "@csstools/css-calc" "^2.1.0" - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" + "@csstools/css-calc" "^2.1.4" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" -"@csstools/postcss-relative-color-syntax@^3.0.6": - version "3.0.6" - resolved "https://registry.yarnpkg.com/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.6.tgz#4b8bc219b34b16f5abdbbcf09ac13e65bff6ef16" - integrity sha512-yxP618Xb+ji1I624jILaYM62uEmZcmbdmFoZHoaThw896sq0vU39kqTTF+ZNic9XyPtPMvq0vyvbgmHaszq8xg== +"@csstools/postcss-relative-color-syntax@^3.0.12": + version "3.0.12" + resolved "https://registry.yarnpkg.com/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.12.tgz#ced792450102441f7c160e1d106f33e4b44181f8" + integrity sha512-0RLIeONxu/mtxRtf3o41Lq2ghLimw0w9ByLWnnEVuy89exmEEq8bynveBxNW3nyHqLAFEeNtVEmC1QK9MZ8Huw== dependencies: - "@csstools/css-color-parser" "^3.0.6" - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" - "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" "@csstools/postcss-scope-pseudo-class@^4.0.1": @@ -1513,50 +1474,50 @@ dependencies: postcss-selector-parser "^7.0.0" -"@csstools/postcss-sign-functions@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.0.tgz#a524fae1374b0e167729f612ca875d7b1b334262" - integrity sha512-SLcc20Nujx/kqbSwDmj6oaXgpy3UjFhBy1sfcqPgDkHfOIfUtUVH7OXO+j7BU4v/At5s61N5ZX6shvgPwluhsA== +"@csstools/postcss-sign-functions@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.4.tgz#a9ac56954014ae4c513475b3f1b3e3424a1e0c12" + integrity sha512-P97h1XqRPcfcJndFdG95Gv/6ZzxUBBISem0IDqPZ7WMvc/wlO+yU0c5D/OCpZ5TJoTt63Ok3knGk64N+o6L2Pg== dependencies: - "@csstools/css-calc" "^2.1.0" - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" + "@csstools/css-calc" "^2.1.4" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" -"@csstools/postcss-stepped-value-functions@^4.0.5": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.5.tgz#4d68633d502fbe2b6ef3898e368e3540488a0d8a" - integrity sha512-G6SJ6hZJkhxo6UZojVlLo14MohH4J5J7z8CRBrxxUYy9JuZiIqUo5TBYyDGcE0PLdzpg63a7mHSJz3VD+gMwqw== +"@csstools/postcss-stepped-value-functions@^4.0.9": + version "4.0.9" + resolved "https://registry.yarnpkg.com/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.9.tgz#36036f1a0e5e5ee2308e72f3c9cb433567c387b9" + integrity sha512-h9btycWrsex4dNLeQfyU3y3w40LMQooJWFMm/SK9lrKguHDcFl4VMkncKKoXi2z5rM9YGWbUQABI8BT2UydIcA== dependencies: - "@csstools/css-calc" "^2.1.0" - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" + "@csstools/css-calc" "^2.1.4" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" -"@csstools/postcss-text-decoration-shorthand@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.1.tgz#251fab0939d50c6fd73bb2b830b2574188efa087" - integrity sha512-xPZIikbx6jyzWvhms27uugIc0I4ykH4keRvoa3rxX5K7lEhkbd54rjj/dv60qOCTisoS+3bmwJTeyV1VNBrXaw== +"@csstools/postcss-text-decoration-shorthand@^4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.3.tgz#fae1b70f07d1b7beb4c841c86d69e41ecc6f743c" + integrity sha512-KSkGgZfx0kQjRIYnpsD7X2Om9BUXX/Kii77VBifQW9Ih929hK0KNjVngHDH0bFB9GmfWcR9vJYJJRvw/NQjkrA== dependencies: - "@csstools/color-helpers" "^5.0.1" + "@csstools/color-helpers" "^5.1.0" postcss-value-parser "^4.2.0" -"@csstools/postcss-trigonometric-functions@^4.0.5": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.5.tgz#267b95a8bd45536e0360596b6da660a9eb6aac83" - integrity sha512-/YQThYkt5MLvAmVu7zxjhceCYlKrYddK6LEmK5I4ojlS6BmO9u2yO4+xjXzu2+NPYmHSTtP4NFSamBCMmJ1NJA== +"@csstools/postcss-trigonometric-functions@^4.0.9": + version "4.0.9" + resolved "https://registry.yarnpkg.com/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.9.tgz#3f94ed2e319b57f2c59720b64e4d0a8a6fb8c3b2" + integrity sha512-Hnh5zJUdpNrJqK9v1/E3BbrQhaDTj5YiX7P61TOvUhoDHnUmsNNxcDAgkQ32RrcWx9GVUvfUNPcUkn8R3vIX6A== dependencies: - "@csstools/css-calc" "^2.1.0" - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" + "@csstools/css-calc" "^2.1.4" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" "@csstools/postcss-unset-value@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz#7caa981a34196d06a737754864baf77d64de4bba" integrity sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA== -"@csstools/selector-resolve-nested@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.0.0.tgz#704a9b637975680e025e069a4c58b3beb3e2752a" - integrity sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ== +"@csstools/selector-resolve-nested@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz#848c6f44cb65e3733e478319b9342b7aa436fac7" + integrity sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g== "@csstools/selector-specificity@^5.0.0": version "5.0.0" @@ -1573,25 +1534,34 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@docsearch/css@3.8.0": - version "3.8.0" - resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.8.0.tgz#c70a1a326249d878ab7c630d7a908c6769a38db3" - integrity sha512-pieeipSOW4sQ0+bE5UFC51AOZp9NGxg89wAlZ1BAQFaiRAGK1IKUaPQ0UGZeNctJXyqZ1UvBtOQh2HH+U5GtmA== +"@docsearch/core@4.3.1": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@docsearch/core/-/core-4.3.1.tgz#88a97a6fe4d4025269b6dee8b9d070b76758ad82" + integrity sha512-ktVbkePE+2h9RwqCUMbWXOoebFyDOxHqImAqfs+lC8yOU+XwEW4jgvHGJK079deTeHtdhUNj0PXHSnhJINvHzQ== -"@docsearch/react@^3.5.2": - version "3.8.0" - resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.8.0.tgz#c32165e34fadea8a0283c8b61cd73e6e1844797d" - integrity sha512-WnFK720+iwTVt94CxY3u+FgX6exb3BfN5kE9xUY6uuAH/9W/UFboBZFLlrw/zxFRHoHZCOXRtOylsXF+6LHI+Q== +"@docsearch/css@4.3.2": + version "4.3.2" + resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-4.3.2.tgz#d47d25336c9516b419245fa74e8dd5ae84a17492" + integrity sha512-K3Yhay9MgkBjJJ0WEL5MxnACModX9xuNt3UlQQkDEDZJZ0+aeWKtOkxHNndMRkMBnHdYvQjxkm6mdlneOtU1IQ== + +"@docsearch/react@^3.9.0 || ^4.1.0": + version "4.3.2" + resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-4.3.2.tgz#450b8341cb5cca03737a00075d4dfd3a904a3e3e" + integrity sha512-74SFD6WluwvgsOPqifYOviEEVwDxslxfhakTlra+JviaNcs7KK/rjsPj89kVEoQc9FUxRkAofaJnHIR7pb4TSQ== dependencies: - "@algolia/autocomplete-core" "1.17.7" - "@algolia/autocomplete-preset-algolia" "1.17.7" - "@docsearch/css" "3.8.0" - algoliasearch "^5.12.0" + "@ai-sdk/react" "^2.0.30" + "@algolia/autocomplete-core" "1.19.2" + "@docsearch/core" "4.3.1" + "@docsearch/css" "4.3.2" + ai "^5.0.30" + algoliasearch "^5.28.0" + marked "^16.3.0" + zod "^4.1.8" -"@docusaurus/babel@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/babel/-/babel-3.6.3.tgz#016714fe7a8807d0fc2f7180eace5e82bebbb8a6" - integrity sha512-7dW9Hat9EHYCVicFXYA4hjxBY38+hPuCURL8oRF9fySRm7vzNWuEOghA1TXcykuXZp0HLG2td4RhDxCvGG7tNw== +"@docusaurus/babel@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/babel/-/babel-3.9.2.tgz#f956c638baeccf2040e482c71a742bc7e35fdb22" + integrity sha512-GEANdi/SgER+L7Japs25YiGil/AUDnFFHaCGPBbundxoWtCkA2lmy7/tFmgED4y1htAy6Oi4wkJEQdGssnw9MA== dependencies: "@babel/core" "^7.25.9" "@babel/generator" "^7.25.9" @@ -1603,55 +1573,54 @@ "@babel/runtime" "^7.25.9" "@babel/runtime-corejs3" "^7.25.9" "@babel/traverse" "^7.25.9" - "@docusaurus/logger" "3.6.3" - "@docusaurus/utils" "3.6.3" + "@docusaurus/logger" "3.9.2" + "@docusaurus/utils" "3.9.2" babel-plugin-dynamic-import-node "^2.3.3" fs-extra "^11.1.1" tslib "^2.6.0" -"@docusaurus/bundler@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/bundler/-/bundler-3.6.3.tgz#f09c2e29613f988b874a4be2247708e121b7fc5c" - integrity sha512-47JLuc8D4wA+6VOvmMd5fUC9rFppBQpQOnxDYiVXffm/DeV/wmm3sbpNd5Y+O+G2+nevLTRnvCm/qyancv0Y3A== +"@docusaurus/bundler@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/bundler/-/bundler-3.9.2.tgz#0ca82cda4acf13a493e3f66061aea351e9d356cf" + integrity sha512-ZOVi6GYgTcsZcUzjblpzk3wH1Fya2VNpd5jtHoCCFcJlMQ1EYXZetfAnRHLcyiFeBABaI1ltTYbOBtH/gahGVA== dependencies: "@babel/core" "^7.25.9" - "@docusaurus/babel" "3.6.3" - "@docusaurus/cssnano-preset" "3.6.3" - "@docusaurus/logger" "3.6.3" - "@docusaurus/types" "3.6.3" - "@docusaurus/utils" "3.6.3" + "@docusaurus/babel" "3.9.2" + "@docusaurus/cssnano-preset" "3.9.2" + "@docusaurus/logger" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils" "3.9.2" babel-loader "^9.2.1" - clean-css "^5.3.2" + clean-css "^5.3.3" copy-webpack-plugin "^11.0.0" - css-loader "^6.8.1" + css-loader "^6.11.0" css-minimizer-webpack-plugin "^5.0.1" cssnano "^6.1.2" file-loader "^6.2.0" html-minifier-terser "^7.2.0" - mini-css-extract-plugin "^2.9.1" + mini-css-extract-plugin "^2.9.2" null-loader "^4.0.1" - postcss "^8.4.26" - postcss-loader "^7.3.3" - postcss-preset-env "^10.1.0" - react-dev-utils "^12.0.1" + postcss "^8.5.4" + postcss-loader "^7.3.4" + postcss-preset-env "^10.2.1" terser-webpack-plugin "^5.3.9" tslib "^2.6.0" url-loader "^4.1.1" webpack "^5.95.0" webpackbar "^6.0.1" -"@docusaurus/core@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-3.6.3.tgz#6bf968ee26a36d71387bab293f27ccffc0e428b6" - integrity sha512-xL7FRY9Jr5DWqB6pEnqgKqcMPJOX5V0pgWXi5lCiih11sUBmcFKM7c3+GyxcVeeWFxyYSDP3grLTWqJoP4P9Vw== +"@docusaurus/core@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-3.9.2.tgz#cc970f29b85a8926d63c84f8cffdcda43ed266ff" + integrity sha512-HbjwKeC+pHUFBfLMNzuSjqFE/58+rLVKmOU3lxQrpsxLBOGosYco/Q0GduBb0/jEMRiyEqjNT/01rRdOMWq5pw== dependencies: - "@docusaurus/babel" "3.6.3" - "@docusaurus/bundler" "3.6.3" - "@docusaurus/logger" "3.6.3" - "@docusaurus/mdx-loader" "3.6.3" - "@docusaurus/utils" "3.6.3" - "@docusaurus/utils-common" "3.6.3" - "@docusaurus/utils-validation" "3.6.3" + "@docusaurus/babel" "3.9.2" + "@docusaurus/bundler" "3.9.2" + "@docusaurus/logger" "3.9.2" + "@docusaurus/mdx-loader" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-common" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" boxen "^6.2.1" chalk "^4.1.2" chokidar "^3.5.3" @@ -1659,69 +1628,68 @@ combine-promises "^1.1.0" commander "^5.1.0" core-js "^3.31.1" - del "^6.1.1" detect-port "^1.5.1" escape-html "^1.0.3" eta "^2.2.0" eval "^0.1.8" + execa "5.1.1" fs-extra "^11.1.1" html-tags "^3.3.1" html-webpack-plugin "^5.6.0" leven "^3.1.0" lodash "^4.17.21" + open "^8.4.0" p-map "^4.0.0" prompts "^2.4.2" - react-dev-utils "^12.0.1" - react-helmet-async "^1.3.0" + react-helmet-async "npm:@slorber/react-helmet-async@1.3.0" react-loadable "npm:@docusaurus/react-loadable@6.0.0" react-loadable-ssr-addon-v5-slorber "^1.0.1" react-router "^5.3.4" react-router-config "^5.1.1" react-router-dom "^5.3.4" - rtl-detect "^1.0.4" semver "^7.5.4" serve-handler "^6.1.6" - shelljs "^0.8.5" + tinypool "^1.0.2" tslib "^2.6.0" update-notifier "^6.0.2" webpack "^5.95.0" webpack-bundle-analyzer "^4.10.2" - webpack-dev-server "^4.15.2" + webpack-dev-server "^5.2.2" webpack-merge "^6.0.1" -"@docusaurus/cssnano-preset@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-3.6.3.tgz#ea19b307183ec20dea4927efc4ddf249150b8c6a" - integrity sha512-qP7SXrwZ+23GFJdPN4aIHQrZW+oH/7tzwEuc/RNL0+BdZdmIjYQqUxdXsjE4lFxLNZjj0eUrSNYIS6xwfij+5Q== +"@docusaurus/cssnano-preset@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-3.9.2.tgz#523aab65349db3c51a77f2489048d28527759428" + integrity sha512-8gBKup94aGttRduABsj7bpPFTX7kbwu+xh3K9NMCF5K4bWBqTFYW+REKHF6iBVDHRJ4grZdIPbvkiHd/XNKRMQ== dependencies: cssnano-preset-advanced "^6.1.2" - postcss "^8.4.38" + postcss "^8.5.4" postcss-sort-media-queries "^5.2.0" tslib "^2.6.0" -"@docusaurus/logger@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-3.6.3.tgz#c6e514c9429487ef38be2f2129b2b842740d92fd" - integrity sha512-xSubJixcNyMV9wMV4q0s47CBz3Rlc5jbcCCuij8pfQP8qn/DIpt0ks8W6hQWzHAedg/J/EwxxUOUrnEoKzJo8g== +"@docusaurus/logger@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-3.9.2.tgz#6ec6364b90f5a618a438cc9fd01ac7376869f92a" + integrity sha512-/SVCc57ByARzGSU60c50rMyQlBuMIJCjcsJlkphxY6B0GV4UH3tcA1994N8fFfbJ9kX3jIBe/xg3XP5qBtGDbA== dependencies: chalk "^4.1.2" tslib "^2.6.0" -"@docusaurus/mdx-loader@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-3.6.3.tgz#127babc7cdb26d37c723bc3ae518bda17ce40160" - integrity sha512-3iJdiDz9540ppBseeI93tWTDtUGVkxzh59nMq4ignylxMuXBLK8dFqVeaEor23v1vx6TrGKZ2FuLaTB+U7C0QQ== +"@docusaurus/mdx-loader@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-3.9.2.tgz#78d238de6c6203fa811cc2a7e90b9b79e111408c" + integrity sha512-wiYoGwF9gdd6rev62xDU8AAM8JuLI/hlwOtCzMmYcspEkzecKrP8J8X+KpYnTlACBUUtXNJpSoCwFWJhLRevzQ== dependencies: - "@docusaurus/logger" "3.6.3" - "@docusaurus/utils" "3.6.3" - "@docusaurus/utils-validation" "3.6.3" + "@docusaurus/logger" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" "@mdx-js/mdx" "^3.0.0" "@slorber/remark-comment" "^1.0.0" escape-html "^1.0.3" estree-util-value-to-estree "^3.0.1" file-loader "^6.2.0" fs-extra "^11.1.1" - image-size "^1.0.2" + image-size "^2.0.2" mdast-util-mdx "^3.0.0" mdast-util-to-string "^4.0.0" rehype-raw "^7.0.0" @@ -1737,182 +1705,209 @@ vfile "^6.0.1" webpack "^5.88.1" -"@docusaurus/module-type-aliases@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-3.6.3.tgz#1f7030b1cf1f658cf664d41b6eadba93bbe51d87" - integrity sha512-MjaXX9PN/k5ugNvfRZdWyKWq4FsrhN4LEXaj0pEmMebJuBNlFeGyKQUa9DRhJHpadNaiMLrbo9m3U7Ig5YlsZg== +"@docusaurus/module-type-aliases@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-3.9.2.tgz#993c7cb0114363dea5ef6855e989b3ad4b843a34" + integrity sha512-8qVe2QA9hVLzvnxP46ysuofJUIc/yYQ82tvA/rBTrnpXtCjNSFLxEZfd5U8cYZuJIVlkPxamsIgwd5tGZXfvew== dependencies: - "@docusaurus/types" "3.6.3" + "@docusaurus/types" "3.9.2" "@types/history" "^4.7.11" "@types/react" "*" "@types/react-router-config" "*" "@types/react-router-dom" "*" - react-helmet-async "*" + react-helmet-async "npm:@slorber/react-helmet-async@1.3.0" react-loadable "npm:@docusaurus/react-loadable@6.0.0" -"@docusaurus/plugin-content-blog@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.6.3.tgz#d6a597e4bfdeb3f1f6ce06d2ac86207296988cc9" - integrity sha512-k0ogWwwJU3pFRFfvW1kRVHxzf2DutLGaaLjAnHVEU6ju+aRP0Z5ap/13DHyPOfHeE4WKpn/M0TqjdwZAcY3kAw== +"@docusaurus/plugin-content-blog@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.9.2.tgz#d5ce51eb7757bdab0515e2dd26a793ed4e119df9" + integrity sha512-3I2HXy3L1QcjLJLGAoTvoBnpOwa6DPUa3Q0dMK19UTY9mhPkKQg/DYhAGTiBUKcTR0f08iw7kLPqOhIgdV3eVQ== dependencies: - "@docusaurus/core" "3.6.3" - "@docusaurus/logger" "3.6.3" - "@docusaurus/mdx-loader" "3.6.3" - "@docusaurus/theme-common" "3.6.3" - "@docusaurus/types" "3.6.3" - "@docusaurus/utils" "3.6.3" - "@docusaurus/utils-common" "3.6.3" - "@docusaurus/utils-validation" "3.6.3" + "@docusaurus/core" "3.9.2" + "@docusaurus/logger" "3.9.2" + "@docusaurus/mdx-loader" "3.9.2" + "@docusaurus/theme-common" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-common" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" cheerio "1.0.0-rc.12" feed "^4.2.2" fs-extra "^11.1.1" lodash "^4.17.21" - reading-time "^1.5.0" + schema-dts "^1.1.2" srcset "^4.0.0" tslib "^2.6.0" unist-util-visit "^5.0.0" utility-types "^3.10.0" webpack "^5.88.1" -"@docusaurus/plugin-content-docs@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.6.3.tgz#aae044d2af6996d1a6de8d815aca8a83b485e0a5" - integrity sha512-r2wS8y/fsaDcxkm20W5bbYJFPzdWdEaTWVYjNxlHlcmX086eqQR1Fomlg9BHTJ0dLXPzAlbC8EN4XqMr3QzNCQ== +"@docusaurus/plugin-content-docs@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.2.tgz#cd8f2d1c06e53c3fa3d24bdfcb48d237bf2d6b2e" + integrity sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg== dependencies: - "@docusaurus/core" "3.6.3" - "@docusaurus/logger" "3.6.3" - "@docusaurus/mdx-loader" "3.6.3" - "@docusaurus/module-type-aliases" "3.6.3" - "@docusaurus/theme-common" "3.6.3" - "@docusaurus/types" "3.6.3" - "@docusaurus/utils" "3.6.3" - "@docusaurus/utils-common" "3.6.3" - "@docusaurus/utils-validation" "3.6.3" + "@docusaurus/core" "3.9.2" + "@docusaurus/logger" "3.9.2" + "@docusaurus/mdx-loader" "3.9.2" + "@docusaurus/module-type-aliases" "3.9.2" + "@docusaurus/theme-common" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-common" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" "@types/react-router-config" "^5.0.7" combine-promises "^1.1.0" fs-extra "^11.1.1" js-yaml "^4.1.0" lodash "^4.17.21" + schema-dts "^1.1.2" tslib "^2.6.0" utility-types "^3.10.0" webpack "^5.88.1" -"@docusaurus/plugin-content-pages@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.6.3.tgz#0a5a43d1677ee519f63a54634653c54ddf41f475" - integrity sha512-eHrmTgjgLZsuqfsYr5X2xEwyIcck0wseSofWrjTwT9FLOWp+KDmMAuVK+wRo7sFImWXZk3oV/xX/g9aZrhD7OA== +"@docusaurus/plugin-content-pages@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.9.2.tgz#22db6c88ade91cec0a9e87a00b8089898051b08d" + integrity sha512-s4849w/p4noXUrGpPUF0BPqIAfdAe76BLaRGAGKZ1gTDNiGxGcpsLcwJ9OTi1/V8A+AzvsmI9pkjie2zjIQZKA== dependencies: - "@docusaurus/core" "3.6.3" - "@docusaurus/mdx-loader" "3.6.3" - "@docusaurus/types" "3.6.3" - "@docusaurus/utils" "3.6.3" - "@docusaurus/utils-validation" "3.6.3" + "@docusaurus/core" "3.9.2" + "@docusaurus/mdx-loader" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" fs-extra "^11.1.1" tslib "^2.6.0" webpack "^5.88.1" -"@docusaurus/plugin-debug@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-3.6.3.tgz#4e62ddfbae4d597b073f8e3c632cc12d012339e3" - integrity sha512-zB9GXfIZNPRfzKnNjU6xGVrqn9bPXuGhpjgsuc/YtcTDjnjhasg38NdYd5LEqXex5G/zIorQgWB3n6x/Ut62vQ== +"@docusaurus/plugin-css-cascade-layers@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.9.2.tgz#358c85f63f1c6a11f611f1b8889d9435c11b22f8" + integrity sha512-w1s3+Ss+eOQbscGM4cfIFBlVg/QKxyYgj26k5AnakuHkKxH6004ZtuLe5awMBotIYF2bbGDoDhpgQ4r/kcj4rQ== dependencies: - "@docusaurus/core" "3.6.3" - "@docusaurus/types" "3.6.3" - "@docusaurus/utils" "3.6.3" + "@docusaurus/core" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" + tslib "^2.6.0" + +"@docusaurus/plugin-debug@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-3.9.2.tgz#b5df4db115583f5404a252dbf66f379ff933e53c" + integrity sha512-j7a5hWuAFxyQAkilZwhsQ/b3T7FfHZ+0dub6j/GxKNFJp2h9qk/P1Bp7vrGASnvA9KNQBBL1ZXTe7jlh4VdPdA== + dependencies: + "@docusaurus/core" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils" "3.9.2" fs-extra "^11.1.1" - react-json-view-lite "^1.2.0" + react-json-view-lite "^2.3.0" tslib "^2.6.0" -"@docusaurus/plugin-google-analytics@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.6.3.tgz#63648d469b1e3c50fad8878e7a7db9856e503d5f" - integrity sha512-rCDNy1QW8Dag7nZq67pcum0bpFLrwvxJhYuVprhFh8BMBDxV0bY+bAkGHbSf68P3Bk9C3hNOAXX1srGLIDvcTA== +"@docusaurus/plugin-google-analytics@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.9.2.tgz#857fe075fdeccdf6959e62954d9efe39769fa247" + integrity sha512-mAwwQJ1Us9jL/lVjXtErXto4p4/iaLlweC54yDUK1a97WfkC6Z2k5/769JsFgwOwOP+n5mUQGACXOEQ0XDuVUw== dependencies: - "@docusaurus/core" "3.6.3" - "@docusaurus/types" "3.6.3" - "@docusaurus/utils-validation" "3.6.3" + "@docusaurus/core" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" tslib "^2.6.0" -"@docusaurus/plugin-google-gtag@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.6.3.tgz#8a1388b4123904be17e661ea7aa71d798d0c046e" - integrity sha512-+OyDvhM6rqVkQOmLVkQWVJAizEEfkPzVWtIHXlWPOCFGK9X4/AWeBSrU0WG4iMg9Z4zD4YDRrU+lvI4s6DSC+w== +"@docusaurus/plugin-google-gtag@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.9.2.tgz#df75b1a90ae9266b0471909ba0265f46d5dcae62" + integrity sha512-YJ4lDCphabBtw19ooSlc1MnxtYGpjFV9rEdzjLsUnBCeis2djUyCozZaFhCg6NGEwOn7HDDyMh0yzcdRpnuIvA== dependencies: - "@docusaurus/core" "3.6.3" - "@docusaurus/types" "3.6.3" - "@docusaurus/utils-validation" "3.6.3" + "@docusaurus/core" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" "@types/gtag.js" "^0.0.12" tslib "^2.6.0" -"@docusaurus/plugin-google-tag-manager@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.6.3.tgz#38cbe416803f29782807cebf3ebf240cb47c3c74" - integrity sha512-1M6UPB13gWUtN2UHX083/beTn85PlRI9ABItTl/JL1FJ5dJTWWFXXsHf9WW/6hrVwthwTeV/AGbGKvLKV+IlCA== +"@docusaurus/plugin-google-tag-manager@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.9.2.tgz#d1a3cf935acb7d31b84685e92d70a1d342946677" + integrity sha512-LJtIrkZN/tuHD8NqDAW1Tnw0ekOwRTfobWPsdO15YxcicBo2ykKF0/D6n0vVBfd3srwr9Z6rzrIWYrMzBGrvNw== dependencies: - "@docusaurus/core" "3.6.3" - "@docusaurus/types" "3.6.3" - "@docusaurus/utils-validation" "3.6.3" + "@docusaurus/core" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" tslib "^2.6.0" -"@docusaurus/plugin-sitemap@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.6.3.tgz#0458e6f7476ab6fd1466e01b153a3211d3223c53" - integrity sha512-94qOO4M9Fwv9KfVQJsgbe91k+fPJ4byf1L3Ez8TUa6TAFPo/BrLwQ80zclHkENlL1824TuxkcMKv33u6eydQCg== +"@docusaurus/plugin-sitemap@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.9.2.tgz#e1d9f7012942562cc0c6543d3cb2cdc4ae713dc4" + integrity sha512-WLh7ymgDXjG8oPoM/T4/zUP7KcSuFYRZAUTl8vR6VzYkfc18GBM4xLhcT+AKOwun6kBivYKUJf+vlqYJkm+RHw== dependencies: - "@docusaurus/core" "3.6.3" - "@docusaurus/logger" "3.6.3" - "@docusaurus/types" "3.6.3" - "@docusaurus/utils" "3.6.3" - "@docusaurus/utils-common" "3.6.3" - "@docusaurus/utils-validation" "3.6.3" + "@docusaurus/core" "3.9.2" + "@docusaurus/logger" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-common" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" fs-extra "^11.1.1" sitemap "^7.1.1" tslib "^2.6.0" -"@docusaurus/preset-classic@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-3.6.3.tgz#072298b5b6d0de7d0346b1e9b550a30ef2add56d" - integrity sha512-VHSYWROT3flvNNI1SrnMOtW1EsjeHNK9dhU6s9eY5hryZe79lUqnZJyze/ymDe2LXAqzyj6y5oYvyBoZZk6ErA== +"@docusaurus/plugin-svgr@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-svgr/-/plugin-svgr-3.9.2.tgz#62857ed79d97c0150d25f7e7380fdee65671163a" + integrity sha512-n+1DE+5b3Lnf27TgVU5jM1d4x5tUh2oW5LTsBxJX4PsAPV0JGcmI6p3yLYtEY0LRVEIJh+8RsdQmRE66wSV8mw== dependencies: - "@docusaurus/core" "3.6.3" - "@docusaurus/plugin-content-blog" "3.6.3" - "@docusaurus/plugin-content-docs" "3.6.3" - "@docusaurus/plugin-content-pages" "3.6.3" - "@docusaurus/plugin-debug" "3.6.3" - "@docusaurus/plugin-google-analytics" "3.6.3" - "@docusaurus/plugin-google-gtag" "3.6.3" - "@docusaurus/plugin-google-tag-manager" "3.6.3" - "@docusaurus/plugin-sitemap" "3.6.3" - "@docusaurus/theme-classic" "3.6.3" - "@docusaurus/theme-common" "3.6.3" - "@docusaurus/theme-search-algolia" "3.6.3" - "@docusaurus/types" "3.6.3" + "@docusaurus/core" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" + "@svgr/core" "8.1.0" + "@svgr/webpack" "^8.1.0" + tslib "^2.6.0" + webpack "^5.88.1" -"@docusaurus/theme-classic@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-3.6.3.tgz#00599a9de5fd5c122fd1b8c59d3b755878f2a72c" - integrity sha512-1RRLK1tSArI2c00qugWYO3jRocjOZwGF1mBzPPylDVRwWCS/rnWWR91ChdbbaxIupRJ+hX8ZBYrwr5bbU0oztQ== +"@docusaurus/preset-classic@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-3.9.2.tgz#85cc4f91baf177f8146c9ce896dfa1f0fd377050" + integrity sha512-IgyYO2Gvaigi21LuDIe+nvmN/dfGXAiMcV/murFqcpjnZc7jxFAxW+9LEjdPt61uZLxG4ByW/oUmX/DDK9t/8w== dependencies: - "@docusaurus/core" "3.6.3" - "@docusaurus/logger" "3.6.3" - "@docusaurus/mdx-loader" "3.6.3" - "@docusaurus/module-type-aliases" "3.6.3" - "@docusaurus/plugin-content-blog" "3.6.3" - "@docusaurus/plugin-content-docs" "3.6.3" - "@docusaurus/plugin-content-pages" "3.6.3" - "@docusaurus/theme-common" "3.6.3" - "@docusaurus/theme-translations" "3.6.3" - "@docusaurus/types" "3.6.3" - "@docusaurus/utils" "3.6.3" - "@docusaurus/utils-common" "3.6.3" - "@docusaurus/utils-validation" "3.6.3" + "@docusaurus/core" "3.9.2" + "@docusaurus/plugin-content-blog" "3.9.2" + "@docusaurus/plugin-content-docs" "3.9.2" + "@docusaurus/plugin-content-pages" "3.9.2" + "@docusaurus/plugin-css-cascade-layers" "3.9.2" + "@docusaurus/plugin-debug" "3.9.2" + "@docusaurus/plugin-google-analytics" "3.9.2" + "@docusaurus/plugin-google-gtag" "3.9.2" + "@docusaurus/plugin-google-tag-manager" "3.9.2" + "@docusaurus/plugin-sitemap" "3.9.2" + "@docusaurus/plugin-svgr" "3.9.2" + "@docusaurus/theme-classic" "3.9.2" + "@docusaurus/theme-common" "3.9.2" + "@docusaurus/theme-search-algolia" "3.9.2" + "@docusaurus/types" "3.9.2" + +"@docusaurus/theme-classic@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-3.9.2.tgz#6e514f99a0ff42b80afcf42d5e5d042618311ce0" + integrity sha512-IGUsArG5hhekXd7RDb11v94ycpJpFdJPkLnt10fFQWOVxAtq5/D7hT6lzc2fhyQKaaCE62qVajOMKL7OiAFAIA== + dependencies: + "@docusaurus/core" "3.9.2" + "@docusaurus/logger" "3.9.2" + "@docusaurus/mdx-loader" "3.9.2" + "@docusaurus/module-type-aliases" "3.9.2" + "@docusaurus/plugin-content-blog" "3.9.2" + "@docusaurus/plugin-content-docs" "3.9.2" + "@docusaurus/plugin-content-pages" "3.9.2" + "@docusaurus/theme-common" "3.9.2" + "@docusaurus/theme-translations" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-common" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" "@mdx-js/react" "^3.0.0" clsx "^2.0.0" - copy-text-to-clipboard "^3.2.0" infima "0.2.0-alpha.45" lodash "^4.17.21" nprogress "^0.2.0" - postcss "^8.4.26" + postcss "^8.5.4" prism-react-renderer "^2.3.0" prismjs "^1.29.0" react-router-dom "^5.3.4" @@ -1920,15 +1915,15 @@ tslib "^2.6.0" utility-types "^3.10.0" -"@docusaurus/theme-common@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-3.6.3.tgz#a8a6ebd2b0fd7a5cca4d0c6a2f9ccff905fa7438" - integrity sha512-b8ZkhczXHDxWWyvz+YJy4t/PlPbEogTTbgnHoflYnH7rmRtyoodTsu8WVM12la5LmlMJBclBXFl29OH8kPE7gg== +"@docusaurus/theme-common@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-3.9.2.tgz#487172c6fef9815c2746ef62a71e4f5b326f9ba5" + integrity sha512-6c4DAbR6n6nPbnZhY2V3tzpnKnGL+6aOsLvFL26VRqhlczli9eWG0VDUNoCQEPnGwDMhPS42UhSAnz5pThm5Ag== dependencies: - "@docusaurus/mdx-loader" "3.6.3" - "@docusaurus/module-type-aliases" "3.6.3" - "@docusaurus/utils" "3.6.3" - "@docusaurus/utils-common" "3.6.3" + "@docusaurus/mdx-loader" "3.9.2" + "@docusaurus/module-type-aliases" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-common" "3.9.2" "@types/history" "^4.7.11" "@types/react" "*" "@types/react-router-config" "*" @@ -1938,21 +1933,21 @@ tslib "^2.6.0" utility-types "^3.10.0" -"@docusaurus/theme-search-algolia@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.6.3.tgz#1a3331a489f392f5b032c4efc5f431e57eddf7ce" - integrity sha512-rt+MGCCpYgPyWCGXtbxlwFbTSobu15jWBTPI2LHsHNa5B0zSmOISX6FWYAPt5X1rNDOqMGM0FATnh7TBHRohVA== +"@docusaurus/theme-search-algolia@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.9.2.tgz#420fd5b27fc1673b48151fdc9fe7167ba135ed50" + integrity sha512-GBDSFNwjnh5/LdkxCKQHkgO2pIMX1447BxYUBG2wBiajS21uj64a+gH/qlbQjDLxmGrbrllBrtJkUHxIsiwRnw== dependencies: - "@docsearch/react" "^3.5.2" - "@docusaurus/core" "3.6.3" - "@docusaurus/logger" "3.6.3" - "@docusaurus/plugin-content-docs" "3.6.3" - "@docusaurus/theme-common" "3.6.3" - "@docusaurus/theme-translations" "3.6.3" - "@docusaurus/utils" "3.6.3" - "@docusaurus/utils-validation" "3.6.3" - algoliasearch "^4.18.0" - algoliasearch-helper "^3.13.3" + "@docsearch/react" "^3.9.0 || ^4.1.0" + "@docusaurus/core" "3.9.2" + "@docusaurus/logger" "3.9.2" + "@docusaurus/plugin-content-docs" "3.9.2" + "@docusaurus/theme-common" "3.9.2" + "@docusaurus/theme-translations" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-validation" "3.9.2" + algoliasearch "^5.37.0" + algoliasearch-helper "^3.26.0" clsx "^2.0.0" eta "^2.2.0" fs-extra "^11.1.1" @@ -1960,61 +1955,62 @@ tslib "^2.6.0" utility-types "^3.10.0" -"@docusaurus/theme-translations@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-3.6.3.tgz#6e473835ea016ce4acd7d2997f411811db8c4f6b" - integrity sha512-Gb0regclToVlngSIIwUCtBMQBq48qVUaN1XQNKW4XwlsgUyk0vP01LULdqbem7czSwIeBAFXFoORJ0RPX7ht/w== +"@docusaurus/theme-translations@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-3.9.2.tgz#238cd69c2da92d612be3d3b4f95944c1d0f1e041" + integrity sha512-vIryvpP18ON9T9rjgMRFLr2xJVDpw1rtagEGf8Ccce4CkTrvM/fRB8N2nyWYOW5u3DdjkwKw5fBa+3tbn9P4PA== dependencies: fs-extra "^11.1.1" tslib "^2.6.0" -"@docusaurus/types@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-3.6.3.tgz#e87592e31616da1b8dc473e4c8205c61885a1518" - integrity sha512-xD9oTGDrouWzefkhe9ogB2fDV96/82cRpNGx2HIvI5L87JHNhQVIWimQ/3JIiiX/TEd5S9s+VO6FFguwKNRVow== +"@docusaurus/types@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-3.9.2.tgz#e482cf18faea0d1fa5ce0e3f1e28e0f32d2593eb" + integrity sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q== dependencies: "@mdx-js/mdx" "^3.0.0" "@types/history" "^4.7.11" + "@types/mdast" "^4.0.2" "@types/react" "*" commander "^5.1.0" joi "^17.9.2" - react-helmet-async "^1.3.0" + react-helmet-async "npm:@slorber/react-helmet-async@1.3.0" utility-types "^3.10.0" webpack "^5.95.0" webpack-merge "^5.9.0" -"@docusaurus/utils-common@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-3.6.3.tgz#57f840bd6f0928cf10060198cb421f1b9212c8f5" - integrity sha512-v4nKDaANLgT3pMBewHYEMAl/ufY0LkXao1QkFWzI5huWFOmNQ2UFzv2BiKeHX5Ownis0/w6cAyoxPhVdDonlSQ== +"@docusaurus/utils-common@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-3.9.2.tgz#e89bfcf43d66359f43df45293fcdf22814847460" + integrity sha512-I53UC1QctruA6SWLvbjbhCpAw7+X7PePoe5pYcwTOEXD/PxeP8LnECAhTHHwWCblyUX5bMi4QLRkxvyZ+IT8Aw== dependencies: - "@docusaurus/types" "3.6.3" + "@docusaurus/types" "3.9.2" tslib "^2.6.0" -"@docusaurus/utils-validation@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-3.6.3.tgz#3eca7125235eb90983ff660b97a71f331e331f57" - integrity sha512-bhEGGiN5BE38h21vjqD70Gxg++j+PfYVddDUE5UFvLDup68QOcpD33CLr+2knPorlxRbEaNfz6HQDUMQ3HuqKw== +"@docusaurus/utils-validation@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-3.9.2.tgz#04aec285604790806e2fc5aa90aa950dc7ba75ae" + integrity sha512-l7yk3X5VnNmATbwijJkexdhulNsQaNDwoagiwujXoxFbWLcxHQqNQ+c/IAlzrfMMOfa/8xSBZ7KEKDesE/2J7A== dependencies: - "@docusaurus/logger" "3.6.3" - "@docusaurus/utils" "3.6.3" - "@docusaurus/utils-common" "3.6.3" + "@docusaurus/logger" "3.9.2" + "@docusaurus/utils" "3.9.2" + "@docusaurus/utils-common" "3.9.2" fs-extra "^11.2.0" joi "^17.9.2" js-yaml "^4.1.0" lodash "^4.17.21" tslib "^2.6.0" -"@docusaurus/utils@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-3.6.3.tgz#8dcb1969e4011a84dfb0a031da806dadddebf0ea" - integrity sha512-0R/FR3bKVl4yl8QwbL4TYFfR+OXBRpVUaTJdENapBGR3YMwfM6/JnhGilWQO8AOwPJGtGoDK7ib8+8UF9f3OZQ== +"@docusaurus/utils@3.9.2": + version "3.9.2" + resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-3.9.2.tgz#ffab7922631c7e0febcb54e6d499f648bf8a89eb" + integrity sha512-lBSBiRruFurFKXr5Hbsl2thmGweAPmddhF3jb99U4EMDA5L+e5Y1rAkOS07Nvrup7HUMBDrCV45meaxZnt28nQ== dependencies: - "@docusaurus/logger" "3.6.3" - "@docusaurus/types" "3.6.3" - "@docusaurus/utils-common" "3.6.3" - "@svgr/webpack" "^8.1.0" + "@docusaurus/logger" "3.9.2" + "@docusaurus/types" "3.9.2" + "@docusaurus/utils-common" "3.9.2" escape-string-regexp "^4.0.0" + execa "5.1.1" file-loader "^6.2.0" fs-extra "^11.1.1" github-slugger "^1.5.0" @@ -2024,9 +2020,9 @@ js-yaml "^4.1.0" lodash "^4.17.21" micromatch "^4.0.5" + p-queue "^6.6.2" prompts "^2.4.2" resolve-pathname "^3.0.0" - shelljs "^0.8.5" tslib "^2.6.0" url-loader "^4.1.1" utility-types "^3.10.0" @@ -2240,6 +2236,11 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@opentelemetry/api@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" + integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" @@ -2312,6 +2313,11 @@ micromark-util-character "^1.1.0" micromark-util-symbol "^1.0.1" +"@standard-schema/spec@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.0.0.tgz#f193b73dc316c4170f2e82a881da0f550d551b9c" + integrity sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA== + "@svgr/babel-plugin-add-jsx-attribute@8.0.0": version "8.0.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz#4001f5d5dd87fa13303e36ee106e3ff3a7eb8b22" @@ -2835,7 +2841,7 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== @@ -2921,11 +2927,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== -"@types/parse-json@^4.0.0": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" - integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== - "@types/parse5@^5.0.0": version "5.0.3" resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109" @@ -3092,6 +3093,11 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== +"@vercel/oidc@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@vercel/oidc/-/oidc-3.0.3.tgz#82c2b6dd4d5c3b37dcb1189718cdeb9db402d052" + integrity sha512-yNEQvPcVrK9sIe637+I0jD6leluPxzwJKx/Haw6F4H77CdDsszUn5V3o96LPziXkSNE2B83+Z3mjqGKBK/R6Gg== + "@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": version "1.14.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.14.1.tgz#a9f6a07f2b03c95c8d38c4536a1fdfb521ff55b6" @@ -3258,7 +3264,7 @@ acorn@^8.0.0, acorn@^8.0.4, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.8.2: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== -address@^1.0.1, address@^1.1.2: +address@^1.0.1: version "1.2.2" resolved "https://registry.yarnpkg.com/address/-/address-1.2.2.tgz#2b5248dac5485a6390532c6a517fda2e3faac89e" integrity sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA== @@ -3271,6 +3277,16 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" +ai@5.0.93, ai@^5.0.30: + version "5.0.93" + resolved "https://registry.yarnpkg.com/ai/-/ai-5.0.93.tgz#040fff47ce6603b36ed9b8b7ca228c2400d94be3" + integrity sha512-9eGcu+1PJgPg4pRNV4L7tLjRR3wdJC9CXQoNMvtqvYNOLZHFCzjHtVIOr2SIkoJJeu2+sOy3hyiSuTmy2MA40g== + dependencies: + "@ai-sdk/gateway" "2.0.9" + "@ai-sdk/provider" "2.0.0" + "@ai-sdk/provider-utils" "3.0.17" + "@opentelemetry/api" "1.9.0" + ajv-formats@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" @@ -3278,7 +3294,7 @@ ajv-formats@^2.1.1: dependencies: ajv "^8.0.0" -ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: +ajv-keywords@^3.5.2: version "3.5.2" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== @@ -3290,7 +3306,7 @@ ajv-keywords@^5.1.0: dependencies: fast-deep-equal "^3.1.3" -ajv@^6.12.2, ajv@^6.12.5: +ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -3310,52 +3326,32 @@ ajv@^8.0.0, ajv@^8.9.0: json-schema-traverse "^1.0.0" require-from-string "^2.0.2" -algoliasearch-helper@^3.13.3: - version "3.22.5" - resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.22.5.tgz#2fcc26814e10a121a2c2526a1b05c754061c56c0" - integrity sha512-lWvhdnc+aKOKx8jyA3bsdEgHzm/sglC4cYdMG4xSQyRiPLJVJtH/IVYZG3Hp6PkTEhQqhyVYkeP9z2IlcHJsWw== +algoliasearch-helper@^3.26.0: + version "3.26.1" + resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.26.1.tgz#5b7f0874a2751c3d6de675d5403d8fa2f015023f" + integrity sha512-CAlCxm4fYBXtvc5MamDzP6Svu8rW4z9me4DCBY1rQ2UDJ0u0flWmusQ8M3nOExZsLLRcUwUPoRAPMrhzOG3erw== dependencies: "@algolia/events" "^4.0.1" -algoliasearch@^4.18.0: - version "4.24.0" - resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.24.0.tgz#b953b3e2309ef8f25da9de311b95b994ac918275" - integrity sha512-bf0QV/9jVejssFBmz2HQLxUadxk574t4iwjCKp5E7NBzwKkrDEhKPISIIjAU/p6K5qDx3qoeh4+26zWN1jmw3g== +algoliasearch@^5.28.0, algoliasearch@^5.37.0: + version "5.44.0" + resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-5.44.0.tgz#25017f7ea7afcd35b35fb5a790a78694e5dddf4b" + integrity sha512-f8IpsbdQjzTjr/4mJ/jv5UplrtyMnnciGax6/B0OnLCs2/GJTK13O4Y7Ff1AvJVAaztanH+m5nzPoUq6EAy+aA== dependencies: - "@algolia/cache-browser-local-storage" "4.24.0" - "@algolia/cache-common" "4.24.0" - "@algolia/cache-in-memory" "4.24.0" - "@algolia/client-account" "4.24.0" - "@algolia/client-analytics" "4.24.0" - "@algolia/client-common" "4.24.0" - "@algolia/client-personalization" "4.24.0" - "@algolia/client-search" "4.24.0" - "@algolia/logger-common" "4.24.0" - "@algolia/logger-console" "4.24.0" - "@algolia/recommend" "4.24.0" - "@algolia/requester-browser-xhr" "4.24.0" - "@algolia/requester-common" "4.24.0" - "@algolia/requester-node-http" "4.24.0" - "@algolia/transporter" "4.24.0" - -algoliasearch@^5.12.0: - version "5.15.0" - resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-5.15.0.tgz#09cef5a2555c4554b37a99f0488ea6ab2347e625" - integrity sha512-Yf3Swz1s63hjvBVZ/9f2P1Uu48GjmjCN+Esxb6MAONMGtZB1fRX8/S1AhUTtsuTlcGovbYLxpHgc7wEzstDZBw== - dependencies: - "@algolia/client-abtesting" "5.15.0" - "@algolia/client-analytics" "5.15.0" - "@algolia/client-common" "5.15.0" - "@algolia/client-insights" "5.15.0" - "@algolia/client-personalization" "5.15.0" - "@algolia/client-query-suggestions" "5.15.0" - "@algolia/client-search" "5.15.0" - "@algolia/ingestion" "1.15.0" - "@algolia/monitoring" "1.15.0" - "@algolia/recommend" "5.15.0" - "@algolia/requester-browser-xhr" "5.15.0" - "@algolia/requester-fetch" "5.15.0" - "@algolia/requester-node-http" "5.15.0" + "@algolia/abtesting" "1.10.0" + "@algolia/client-abtesting" "5.44.0" + "@algolia/client-analytics" "5.44.0" + "@algolia/client-common" "5.44.0" + "@algolia/client-insights" "5.44.0" + "@algolia/client-personalization" "5.44.0" + "@algolia/client-query-suggestions" "5.44.0" + "@algolia/client-search" "5.44.0" + "@algolia/ingestion" "1.44.0" + "@algolia/monitoring" "1.44.0" + "@algolia/recommend" "5.44.0" + "@algolia/requester-browser-xhr" "5.44.0" + "@algolia/requester-fetch" "5.44.0" + "@algolia/requester-node-http" "5.44.0" ansi-align@^3.0.1: version "3.0.1" @@ -3459,11 +3455,6 @@ astring@^1.8.0: resolved "https://registry.yarnpkg.com/astring/-/astring-1.9.0.tgz#cc73e6062a7eb03e7d19c22d8b0b3451fd9bfeef" integrity sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg== -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== - autocomplete.js@^0.37.0: version "0.37.1" resolved "https://registry.yarnpkg.com/autocomplete.js/-/autocomplete.js-0.37.1.tgz#a29a048d827e7d2bf8f7df8b831766e5cc97df01" @@ -3483,6 +3474,18 @@ autoprefixer@^10.4.19: picocolors "^1.0.1" postcss-value-parser "^4.2.0" +autoprefixer@^10.4.21: + version "10.4.22" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.22.tgz#90b27ab55ec0cf0684210d1f056f7d65dac55f16" + integrity sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg== + dependencies: + browserslist "^4.27.0" + caniuse-lite "^1.0.30001754" + fraction.js "^5.3.4" + normalize-range "^0.1.2" + picocolors "^1.1.1" + postcss-value-parser "^4.2.0" + available-typed-arrays@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" @@ -3549,6 +3552,11 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +baseline-browser-mapping@^2.8.25: + version "2.8.28" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.28.tgz#9ef511f5a7c19d74a94cafcbf951608398e9bdb3" + integrity sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ== + batch@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" @@ -3642,7 +3650,7 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" -browserslist@^4.0.0, browserslist@^4.18.1, browserslist@^4.23.0, browserslist@^4.23.1, browserslist@^4.23.3, browserslist@^4.24.0, browserslist@^4.24.2: +browserslist@^4.0.0, browserslist@^4.23.0, browserslist@^4.23.3, browserslist@^4.24.0, browserslist@^4.24.2: version "4.24.2" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.2.tgz#f5845bc91069dbd55ee89faf9822e1d885d16580" integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg== @@ -3652,6 +3660,17 @@ browserslist@^4.0.0, browserslist@^4.18.1, browserslist@^4.23.0, browserslist@^4 node-releases "^2.0.18" update-browserslist-db "^1.1.1" +browserslist@^4.26.0, browserslist@^4.27.0: + version "4.28.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.0.tgz#9cefece0a386a17a3cd3d22ebf67b9deca1b5929" + integrity sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ== + dependencies: + baseline-browser-mapping "^2.8.25" + caniuse-lite "^1.0.30001754" + electron-to-chromium "^1.5.249" + node-releases "^2.0.27" + update-browserslist-db "^1.1.4" + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -3756,12 +3775,17 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001669: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz#d0ac634d043648498eedf7a3932836beba90ebae" integrity sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ== +caniuse-lite@^1.0.30001754: + version "1.0.30001755" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001755.tgz#c01cfb1c30f5acf1229391666ec03492f4c332ff" + integrity sha512-44V+Jm6ctPj7R52Na4TLi3Zri4dWUljJd+RDm+j8LtNCc/ihLCT+X1TzoOAkRETEWqjuLnh9581Tl80FvK7jVA== + ccount@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: +chalk@^4.0.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -3843,7 +3867,7 @@ chevrotain@~11.0.3: "@chevrotain/utils" "11.0.3" lodash-es "4.17.21" -chokidar@^3.4.2, chokidar@^3.5.3, chokidar@^3.6.0: +chokidar@^3.5.3, chokidar@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== @@ -3868,7 +3892,7 @@ ci-info@^3.2.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== -clean-css@^5.2.2, clean-css@^5.3.2, clean-css@~5.3.2: +clean-css@^5.2.2, clean-css@^5.3.3, clean-css@~5.3.2: version "5.3.3" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.3.tgz#b330653cd3bd6b75009cc25c714cae7b93351ccd" integrity sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg== @@ -4105,11 +4129,6 @@ cookie@0.7.1, cookie@>=0.7.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.0.2.tgz#27360701532116bd3f1f9416929d176afe1e4610" integrity sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA== -copy-text-to-clipboard@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz#0202b2d9bdae30a49a53f898626dcc3b49ad960b" - integrity sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q== - copy-webpack-plugin@^11.0.0: version "11.0.0" resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz#96d4dbdb5f73d02dd72d0528d1958721ab72e04a" @@ -4158,17 +4177,6 @@ cose-base@^2.2.0: dependencies: layout-base "^2.0.0" -cosmiconfig@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" - integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== - dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.1.0" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.7.2" - cosmiconfig@^8.1.3, cosmiconfig@^8.3.5: version "8.3.6" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" @@ -4207,16 +4215,16 @@ css-declaration-sorter@^7.2.0: resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz#6dec1c9523bc4a643e088aab8f09e67a54961024" integrity sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow== -css-has-pseudo@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-7.0.1.tgz#adbb51821e51f7a7c1d2df4d12827870cc311137" - integrity sha512-EOcoyJt+OsuKfCADgLT7gADZI5jMzIe/AeI6MeAYKiFBDmNmM7kk46DtSfMj5AohUJisqVzopBpnQTlvbyaBWg== +css-has-pseudo@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-7.0.3.tgz#a5ee2daf5f70a2032f3cefdf1e36e7f52a243873" + integrity sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA== dependencies: "@csstools/selector-specificity" "^5.0.0" postcss-selector-parser "^7.0.0" postcss-value-parser "^4.2.0" -css-loader@^6.8.1: +css-loader@^6.11.0: version "6.11.0" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.11.0.tgz#33bae3bf6363d0a7c2cf9031c96c744ff54d85ba" integrity sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g== @@ -4295,10 +4303,10 @@ css-what@^6.0.1, css-what@^6.1.0: resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== -cssdb@^8.2.1: - version "8.2.2" - resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-8.2.2.tgz#0a5bcbc47a297e6b0296e6082f60363e17b337d4" - integrity sha512-Z3kpWyvN68aKyeMxOUGmffQeHjvrzDxbre2B2ikr/WqQ4ZMkhHu2nOD6uwSeq3TpuOYU7ckvmJRAUIt6orkYUg== +cssdb@^8.4.2: + version "8.4.2" + resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-8.4.2.tgz#1a367ab1904c97af0bb2c7ae179764deae7b078b" + integrity sha512-PzjkRkRUS+IHDJohtxkIczlxPPZqRo0nXplsYXOMBRPjcVRjj1W4DfvRgshUYTVuUigU7ptVYkFJQ7abUB0nyg== cssesc@^3.0.0: version "3.0.0" @@ -4687,7 +4695,7 @@ debounce@^1.2.1: resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== -debug@2.6.9, debug@^2.6.0: +debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -4734,7 +4742,7 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -deepmerge@^4.2.2, deepmerge@^4.3.1: +deepmerge@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== @@ -4785,20 +4793,6 @@ define-properties@^1.1.3, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" -del@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a" - integrity sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg== - dependencies: - globby "^11.0.1" - graceful-fs "^4.2.4" - is-glob "^4.0.1" - is-path-cwd "^2.2.0" - is-path-inside "^3.0.2" - p-map "^4.0.0" - rimraf "^3.0.2" - slash "^3.0.0" - delaunator@5: version "5.0.1" resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.1.tgz#39032b08053923e924d6094fe2cde1a99cc51278" @@ -4816,7 +4810,7 @@ depd@~1.1.2: resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== -dequal@^2.0.0: +dequal@^2.0.0, dequal@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== @@ -4831,14 +4825,6 @@ detect-node@^2.0.4: resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== -detect-port-alt@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275" - integrity sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q== - dependencies: - address "^1.0.1" - debug "^2.6.0" - detect-port@^1.5.1: version "1.6.1" resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.6.1.tgz#45e4073997c5f292b957cb678fb0bb8ed4250a67" @@ -5028,6 +5014,11 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== +electron-to-chromium@^1.5.249: + version "1.5.254" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.254.tgz#94b84c0a5faff94b334536090a9dec1c74b10130" + integrity sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg== + electron-to-chromium@^1.5.41: version "1.5.71" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.71.tgz#d8b5dba1e55b320f2f4e9b1ca80738f53fcfec2b" @@ -5317,7 +5308,7 @@ eval@^0.1.8: "@types/node" "*" require-like ">= 0.1.1" -eventemitter3@^4.0.0: +eventemitter3@^4.0.0, eventemitter3@^4.0.4: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== @@ -5327,6 +5318,26 @@ events@^3.2.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== +eventsource-parser@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.6.tgz#292e165e34cacbc936c3c92719ef326d4aeb4e90" + integrity sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg== + +execa@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + exenv@^1.2.0: version "1.2.2" resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" @@ -5455,11 +5466,6 @@ file-loader@^6.2.0: loader-utils "^2.0.0" schema-utils "^3.0.0" -filesize@^8.0.6: - version "8.0.7" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" - integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== - fill-range@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" @@ -5488,21 +5494,6 @@ find-cache-dir@^4.0.0: common-path-prefix "^3.0.0" pkg-dir "^7.0.0" -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - find-up@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-6.3.0.tgz#2abab3d3280b2dc7ac10199ef324c4e002c8c790" @@ -5536,25 +5527,6 @@ foreground-child@^3.1.0: cross-spawn "^7.0.0" signal-exit "^4.0.1" -fork-ts-checker-webpack-plugin@^6.5.0: - version "6.5.3" - resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz#eda2eff6e22476a2688d10661688c47f611b37f3" - integrity sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ== - dependencies: - "@babel/code-frame" "^7.8.3" - "@types/json-schema" "^7.0.5" - chalk "^4.1.0" - chokidar "^3.4.2" - cosmiconfig "^6.0.0" - deepmerge "^4.2.2" - fs-extra "^9.0.0" - glob "^7.1.6" - memfs "^3.1.2" - minimatch "^3.0.4" - schema-utils "2.7.0" - semver "^7.3.2" - tapable "^1.0.0" - form-data-encoder@^2.1.2: version "2.1.4" resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-2.1.4.tgz#261ea35d2a70d48d30ec7a9603130fa5515e9cd5" @@ -5575,6 +5547,11 @@ fraction.js@^4.3.7: resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== +fraction.js@^5.3.4: + version "5.3.4" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-5.3.4.tgz#8c0fcc6a9908262df4ed197427bdeef563e0699a" + integrity sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ== + fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" @@ -5598,26 +5575,6 @@ fs-extra@^11.1.1, fs-extra@^11.2.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^9.0.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-monkey@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.6.tgz#8ead082953e88d992cf3ff844faa907b26756da2" - integrity sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - fsevents@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" @@ -5664,7 +5621,7 @@ get-own-enumerable-property-symbols@^3.0.0: resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== -get-stream@^6.0.1: +get-stream@^6.0.0, get-stream@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== @@ -5705,18 +5662,6 @@ glob@^10.3.10: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" -glob@^7.0.0, glob@^7.1.3, glob@^7.1.6: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - global-dirs@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.1.tgz#0c488971f066baceda21447aecb1a8b911d22485" @@ -5724,22 +5669,6 @@ global-dirs@^3.0.0: dependencies: ini "2.0.0" -global-modules@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" - integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== - dependencies: - global-prefix "^3.0.0" - -global-prefix@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" - integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== - dependencies: - ini "^1.3.5" - kind-of "^6.0.2" - which "^1.3.1" - globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -5750,7 +5679,7 @@ globals@^15.14.0: resolved "https://registry.yarnpkg.com/globals/-/globals-15.15.0.tgz#7c4761299d41c32b075715a4ce1ede7897ff72a8" integrity sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg== -globby@^11.0.1, globby@^11.0.4, globby@^11.1.0: +globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -6276,6 +6205,11 @@ http2-wrapper@^2.1.10: quick-lru "^5.1.1" resolve-alpn "^1.2.0" +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + hyperdyperid@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/hyperdyperid/-/hyperdyperid-1.2.0.tgz#59668d323ada92228d2a869d3e474d5a33b69e6b" @@ -6310,7 +6244,7 @@ ignore@^5.2.0, ignore@^5.2.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== -image-size@>=1.2.1, image-size@^1.0.2: +image-size@>=1.2.1, image-size@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/image-size/-/image-size-2.0.2.tgz#84a7b43704db5736f364bf0d1b029821299b4bdc" integrity sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w== @@ -6320,12 +6254,7 @@ immediate@^3.2.3: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.3.0.tgz#1aef225517836bcdf7f2a2de2600c79ff0269266" integrity sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q== -immer@^9.0.7: - version "9.0.21" - resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176" - integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== - -import-fresh@^3.1.0, import-fresh@^3.3.0: +import-fresh@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -6353,30 +6282,22 @@ infima@0.2.0-alpha.45: resolved "https://registry.yarnpkg.com/infima/-/infima-0.2.0-alpha.45.tgz#542aab5a249274d81679631b492973dd2c1e7466" integrity sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw== -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - inherits@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== +inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + ini@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== -ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: +ini@^1.3.4, ini@~1.3.0: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== @@ -6401,11 +6322,6 @@ internmap@^1.0.0: resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95" integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw== -interpret@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" - integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== - invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -6577,11 +6493,6 @@ is-obj@^2.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== -is-path-cwd@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" - integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== - is-path-inside@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" @@ -6621,10 +6532,10 @@ is-regexp@^1.0.0: resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" integrity sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA== -is-root@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" - integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== is-typed-array@^1.1.3: version "1.1.13" @@ -6744,17 +6655,17 @@ joi@^17.9.2: integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + version "3.14.2" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.2.tgz#77485ce1dd7f33c061fd1b16ecea23b55fcb04b0" + integrity sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg== dependencies: argparse "^1.0.7" esprima "^4.0.0" js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + version "4.1.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== dependencies: argparse "^2.0.1" @@ -6783,6 +6694,11 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== +json-schema@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + json5@^2.1.2, json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" @@ -6901,11 +6817,6 @@ loader-utils@^2.0.0: emojis-list "^3.0.0" json5 "^2.1.2" -loader-utils@^3.2.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.3.1.tgz#735b9a19fd63648ca7adbd31c2327dfe281304e5" - integrity sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg== - local-pkg@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-1.1.2.tgz#c03d208787126445303f8161619dc701afa4abb5" @@ -6915,21 +6826,6 @@ local-pkg@^1.0.0: pkg-types "^2.3.0" quansync "^0.2.11" -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - locate-path@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-7.2.0.tgz#69cb1779bd90b35ab1e771e1f2f89a202c2a8a8a" @@ -7040,6 +6936,11 @@ marked@^16.0.0: resolved "https://registry.yarnpkg.com/marked/-/marked-16.2.0.tgz#c407a4f7ed3acc1110812525cfd1b0ed8502792c" integrity sha512-LbbTuye+0dWRz2TS9KJ7wsnD4KAtpj0MVkWc90XvBa6AslXsT0hTBVH5k32pcSyHH1fst9XEFJunXHktVy0zlg== +marked@^16.3.0: + version "16.4.2" + resolved "https://registry.yarnpkg.com/marked/-/marked-16.4.2.tgz#4959a64be6c486f0db7467ead7ce288de54290a3" + integrity sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA== + mdast-util-definitions@^5.0.0: version "5.1.2" resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz#9910abb60ac5d7115d6819b57ae0bcef07a3f7a7" @@ -7461,13 +7362,6 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== -memfs@^3.1.2: - version "3.6.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" - integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== - dependencies: - fs-monkey "^1.0.4" - memfs@^4.6.0: version "4.17.2" resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.17.2.tgz#1f71a6d85c8c53b4f1b388234ed981a690c7e227" @@ -8325,6 +8219,11 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + mimic-response@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" @@ -8335,10 +8234,10 @@ mimic-response@^4.0.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-4.0.0.tgz#35468b19e7c75d10f5165ea25e75a5ceea7cf70f" integrity sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg== -mini-css-extract-plugin@^2.9.1: - version "2.9.2" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz#966031b468917a5446f4c24a80854b2947503c5b" - integrity sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w== +mini-css-extract-plugin@^2.9.2: + version "2.9.4" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz#cafa1a42f8c71357f49cd1566810d74ff1cb0200" + integrity sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ== dependencies: schema-utils "^4.0.0" tapable "^2.2.1" @@ -8348,7 +8247,7 @@ minimalistic-assert@^1.0.0: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@3.1.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1: +minimatch@3.1.2, minimatch@^3.0.4: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -8424,6 +8323,11 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" +nanoid@^3.3.11: + version "3.3.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + nanoid@^3.3.7: version "3.3.8" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" @@ -8486,6 +8390,11 @@ node-releases@^2.0.18: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== +node-releases@^2.0.27: + version "2.0.27" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" + integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== + nopt@1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" @@ -8513,6 +8422,13 @@ not@^0.1.0: resolved "https://registry.yarnpkg.com/not/-/not-0.1.0.tgz#c9691c1746c55dcfbe54cbd8bd4ff041bc2b519d" integrity sha512-5PDmaAsVfnWUgTUbJ3ERwn7u79Z0dYxN9ErxCpVJJqe2RK0PJ3z+iFUxuqjwtlDDegXvtWoxD/3Fzxox7tFGWA== +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + nprogress@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1" @@ -8583,12 +8499,12 @@ on-headers@>=1.1.0, on-headers@~1.0.2: resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65" integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: - wrappy "1" + mimic-fn "^2.1.0" open@^10.0.3: version "10.1.2" @@ -8619,19 +8535,10 @@ p-cancelable@^3.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-3.0.0.tgz#63826694b54d61ca1c20ebcb6d3ecf5e14cd8050" integrity sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw== -p-limit@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== p-limit@^4.0.0: version "4.0.0" @@ -8640,20 +8547,6 @@ p-limit@^4.0.0: dependencies: yocto-queue "^1.0.0" -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== - dependencies: - p-limit "^2.0.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - p-locate@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-6.0.0.tgz#3da9a49d4934b901089dca3302fa65dc5a05c04f" @@ -8668,6 +8561,14 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" +p-queue@^6.6.2: + version "6.6.2" + resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" + integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== + dependencies: + eventemitter3 "^4.0.4" + p-timeout "^3.2.0" + p-retry@^6.2.0: version "6.2.1" resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-6.2.1.tgz#81828f8dc61c6ef5a800585491572cc9892703af" @@ -8677,10 +8578,12 @@ p-retry@^6.2.0: is-network-error "^1.0.0" retry "^0.13.1" -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +p-timeout@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" package-json-from-dist@^1.0.0: version "1.0.1" @@ -8731,7 +8634,7 @@ parse-entities@^4.0.0: is-decimal "^2.0.0" is-hexadecimal "^2.0.0" -parse-json@^5.0.0, parse-json@^5.2.0: +parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== @@ -8789,32 +8692,17 @@ path-data-parser@0.1.0, path-data-parser@^0.1.0: resolved "https://registry.yarnpkg.com/path-data-parser/-/path-data-parser-0.1.0.tgz#8f5ba5cc70fc7becb3dcefaea08e2659aba60b8c" integrity sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w== -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - path-exists@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-5.0.0.tgz#a6aad9489200b21fab31e49cf09277e5116fb9e7" integrity sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ== -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - path-is-inside@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== -path-key@^3.1.0: +path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== @@ -8920,13 +8808,6 @@ pkg-types@^2.3.0: exsolve "^1.0.7" pathe "^2.0.3" -pkg-up@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" - integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== - dependencies: - find-up "^3.0.0" - points-on-curve@0.2.0, points-on-curve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/points-on-curve/-/points-on-curve-0.2.0.tgz#7dbb98c43791859434284761330fa893cb81b4d1" @@ -8967,15 +8848,15 @@ postcss-clamp@^4.1.0: dependencies: postcss-value-parser "^4.2.0" -postcss-color-functional-notation@^7.0.6: - version "7.0.6" - resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.6.tgz#d74c1e2294b72287eb9af079c04b7ddeff7ec5b3" - integrity sha512-wLXvm8RmLs14Z2nVpB4CWlnvaWPRcOZFltJSlcbYwSJ1EDZKsKDhPKIMecCnuU054KSmlmubkqczmm6qBPCBhA== +postcss-color-functional-notation@^7.0.12: + version "7.0.12" + resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.12.tgz#9a3df2296889e629fde18b873bb1f50a4ecf4b83" + integrity sha512-TLCW9fN5kvO/u38/uesdpbx3e8AkTYhMvDZYa9JpmImWuTE99bDQ7GU7hdOADIZsiI9/zuxfAJxny/khknp1Zw== dependencies: - "@csstools/css-color-parser" "^3.0.6" - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" - "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" postcss-color-hex-alpha@^10.0.0: @@ -9012,35 +8893,35 @@ postcss-convert-values@^6.1.0: browserslist "^4.23.0" postcss-value-parser "^4.2.0" -postcss-custom-media@^11.0.5: - version "11.0.5" - resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-11.0.5.tgz#2fcd88a9b1d4da41c67dac6f2def903063a3377d" - integrity sha512-SQHhayVNgDvSAdX9NQ/ygcDQGEY+aSF4b/96z7QUX6mqL5yl/JgG/DywcF6fW9XbnCRE+aVYk+9/nqGuzOPWeQ== +postcss-custom-media@^11.0.6: + version "11.0.6" + resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-11.0.6.tgz#6b450e5bfa209efb736830066682e6567bd04967" + integrity sha512-C4lD4b7mUIw+RZhtY7qUbf4eADmb7Ey8BFA2px9jUbwg7pjTZDl4KY4bvlUV+/vXQvzQRfiGEVJyAbtOsCMInw== dependencies: - "@csstools/cascade-layer-name-parser" "^2.0.4" - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" - "@csstools/media-query-list-parser" "^4.0.2" + "@csstools/cascade-layer-name-parser" "^2.0.5" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/media-query-list-parser" "^4.0.3" -postcss-custom-properties@^14.0.4: - version "14.0.4" - resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-14.0.4.tgz#de9c663285a98833a946d7003a34369d3ce373a9" - integrity sha512-QnW8FCCK6q+4ierwjnmXF9Y9KF8q0JkbgVfvQEMa93x1GT8FvOiUevWCN2YLaOWyByeDX8S6VFbZEeWoAoXs2A== +postcss-custom-properties@^14.0.6: + version "14.0.6" + resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-14.0.6.tgz#1af73a650bf115ba052cf915287c9982825fc90e" + integrity sha512-fTYSp3xuk4BUeVhxCSJdIPhDLpJfNakZKoiTDx7yRGCdlZrSJR7mWKVOBS4sBF+5poPQFMj2YdXx1VHItBGihQ== dependencies: - "@csstools/cascade-layer-name-parser" "^2.0.4" - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" + "@csstools/cascade-layer-name-parser" "^2.0.5" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" "@csstools/utilities" "^2.0.0" postcss-value-parser "^4.2.0" -postcss-custom-selectors@^8.0.4: - version "8.0.4" - resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-8.0.4.tgz#95ef8268fdbbbd84f34cf84a4517c9d99d419c5a" - integrity sha512-ASOXqNvDCE0dAJ/5qixxPeL1aOVGHGW2JwSy7HyjWNbnWTQCl+fDc968HY1jCmZI0+BaYT5CxsOiUhavpG/7eg== +postcss-custom-selectors@^8.0.5: + version "8.0.5" + resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-8.0.5.tgz#9448ed37a12271d7ab6cb364b6f76a46a4a323e8" + integrity sha512-9PGmckHQswiB2usSO6XMSswO2yFWVoCAuih1yl9FVcwkscLjRKjwsjM3t+NIWpSU2Jx3eOiK2+t4vVTQaoCHHg== dependencies: - "@csstools/cascade-layer-name-parser" "^2.0.4" - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" + "@csstools/cascade-layer-name-parser" "^2.0.5" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" postcss-selector-parser "^7.0.0" postcss-dir-pseudo-class@^9.0.1: @@ -9077,12 +8958,12 @@ postcss-discard-unused@^6.0.5: dependencies: postcss-selector-parser "^6.0.16" -postcss-double-position-gradients@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.0.tgz#eddd424ec754bb543d057d4d2180b1848095d4d2" - integrity sha512-JkIGah3RVbdSEIrcobqj4Gzq0h53GG4uqDPsho88SgY84WnpkTpI0k50MFK/sX7XqVisZ6OqUfFnoUO6m1WWdg== +postcss-double-position-gradients@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.4.tgz#b482d08b5ced092b393eb297d07976ab482d4cad" + integrity sha512-m6IKmxo7FxSP5nF2l63QbCC3r+bWpFUWmZXZf096WxG0m7Vl1Q1+ruFOhpdDRmKrRS+S3Jtk+TVk/7z0+BVK6g== dependencies: - "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" postcss-value-parser "^4.2.0" @@ -9118,18 +8999,18 @@ postcss-image-set-function@^7.0.0: "@csstools/utilities" "^2.0.0" postcss-value-parser "^4.2.0" -postcss-lab-function@^7.0.6: - version "7.0.6" - resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-7.0.6.tgz#3121800fc7939ed1d9a1e87abeb33c407151252c" - integrity sha512-HPwvsoK7C949vBZ+eMyvH2cQeMr3UREoHvbtra76/UhDuiViZH6pir+z71UaJQohd7VDSVUdR6TkWYKExEc9aQ== +postcss-lab-function@^7.0.12: + version "7.0.12" + resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-7.0.12.tgz#eb555ac542607730eb0a87555074e4a5c6eef6e4" + integrity sha512-tUcyRk1ZTPec3OuKFsqtRzW2Go5lehW29XA21lZ65XmzQkz43VY2tyWEC202F7W3mILOjw0voOiuxRGTsN+J9w== dependencies: - "@csstools/css-color-parser" "^3.0.6" - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" - "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/css-color-parser" "^3.1.0" + "@csstools/css-parser-algorithms" "^3.0.5" + "@csstools/css-tokenizer" "^3.0.4" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" "@csstools/utilities" "^2.0.0" -postcss-loader@^7.3.3: +postcss-loader@^7.3.4: version "7.3.4" resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-7.3.4.tgz#aed9b79ce4ed7e9e89e56199d25ad1ec8f606209" integrity sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A== @@ -9138,10 +9019,10 @@ postcss-loader@^7.3.3: jiti "^1.20.0" semver "^7.5.4" -postcss-logical@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-8.0.0.tgz#0db0b90c2dc53b485a8074a4b7a906297544f58d" - integrity sha512-HpIdsdieClTjXLOyYdUPAX/XQASNIwdKt5hoZW08ZOAiI+tbV0ta1oclkpVkW5ANU+xJvk3KkA0FejkjGLXUkg== +postcss-logical@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-8.1.0.tgz#4092b16b49e3ecda70c4d8945257da403d167228" + integrity sha512-pL1hXFQ2fEXNKiNiAgtfA005T9FBxky5zkX6s4GZM2D8RkVgRqz3f4g1JUoq925zXv495qk8UNldDwh8uGEDoA== dependencies: postcss-value-parser "^4.2.0" @@ -9231,12 +9112,12 @@ postcss-modules-values@^4.0.0: dependencies: icss-utils "^5.0.0" -postcss-nesting@^13.0.1: - version "13.0.1" - resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-13.0.1.tgz#c405796d7245a3e4c267a9956cacfe9670b5d43e" - integrity sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ== +postcss-nesting@^13.0.2: + version "13.0.2" + resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-13.0.2.tgz#fde0d4df772b76d03b52eccc84372e8d1ca1402e" + integrity sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ== dependencies: - "@csstools/selector-resolve-nested" "^3.0.0" + "@csstools/selector-resolve-nested" "^3.1.0" "@csstools/selector-specificity" "^5.0.0" postcss-selector-parser "^7.0.0" @@ -9334,67 +9215,71 @@ postcss-place@^10.0.0: dependencies: postcss-value-parser "^4.2.0" -postcss-preset-env@^10.1.0: - version "10.1.1" - resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-10.1.1.tgz#6ee631272353fb1c4a9711943e9b80a178ffce44" - integrity sha512-wqqsnBFD6VIwcHHRbhjTOcOi4qRVlB26RwSr0ordPj7OubRRxdWebv/aLjKLRR8zkZrbxZyuus03nOIgC5elMQ== +postcss-preset-env@^10.2.1: + version "10.4.0" + resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-10.4.0.tgz#fa6167a307f337b2bcdd1d125604ff97cdeb5142" + integrity sha512-2kqpOthQ6JhxqQq1FSAAZGe9COQv75Aw8WbsOvQVNJ2nSevc9Yx/IKZGuZ7XJ+iOTtVon7LfO7ELRzg8AZ+sdw== dependencies: - "@csstools/postcss-cascade-layers" "^5.0.1" - "@csstools/postcss-color-function" "^4.0.6" - "@csstools/postcss-color-mix-function" "^3.0.6" - "@csstools/postcss-content-alt-text" "^2.0.4" - "@csstools/postcss-exponential-functions" "^2.0.5" + "@csstools/postcss-alpha-function" "^1.0.1" + "@csstools/postcss-cascade-layers" "^5.0.2" + "@csstools/postcss-color-function" "^4.0.12" + "@csstools/postcss-color-function-display-p3-linear" "^1.0.1" + "@csstools/postcss-color-mix-function" "^3.0.12" + "@csstools/postcss-color-mix-variadic-function-arguments" "^1.0.2" + "@csstools/postcss-content-alt-text" "^2.0.8" + "@csstools/postcss-contrast-color-function" "^2.0.12" + "@csstools/postcss-exponential-functions" "^2.0.9" "@csstools/postcss-font-format-keywords" "^4.0.0" - "@csstools/postcss-gamut-mapping" "^2.0.6" - "@csstools/postcss-gradients-interpolation-method" "^5.0.6" - "@csstools/postcss-hwb-function" "^4.0.6" - "@csstools/postcss-ic-unit" "^4.0.0" - "@csstools/postcss-initial" "^2.0.0" - "@csstools/postcss-is-pseudo-class" "^5.0.1" - "@csstools/postcss-light-dark-function" "^2.0.7" + "@csstools/postcss-gamut-mapping" "^2.0.11" + "@csstools/postcss-gradients-interpolation-method" "^5.0.12" + "@csstools/postcss-hwb-function" "^4.0.12" + "@csstools/postcss-ic-unit" "^4.0.4" + "@csstools/postcss-initial" "^2.0.1" + "@csstools/postcss-is-pseudo-class" "^5.0.3" + "@csstools/postcss-light-dark-function" "^2.0.11" "@csstools/postcss-logical-float-and-clear" "^3.0.0" "@csstools/postcss-logical-overflow" "^2.0.0" "@csstools/postcss-logical-overscroll-behavior" "^2.0.0" "@csstools/postcss-logical-resize" "^3.0.0" - "@csstools/postcss-logical-viewport-units" "^3.0.3" - "@csstools/postcss-media-minmax" "^2.0.5" - "@csstools/postcss-media-queries-aspect-ratio-number-values" "^3.0.4" + "@csstools/postcss-logical-viewport-units" "^3.0.4" + "@csstools/postcss-media-minmax" "^2.0.9" + "@csstools/postcss-media-queries-aspect-ratio-number-values" "^3.0.5" "@csstools/postcss-nested-calc" "^4.0.0" "@csstools/postcss-normalize-display-values" "^4.0.0" - "@csstools/postcss-oklab-function" "^4.0.6" - "@csstools/postcss-progressive-custom-properties" "^4.0.0" - "@csstools/postcss-random-function" "^1.0.1" - "@csstools/postcss-relative-color-syntax" "^3.0.6" + "@csstools/postcss-oklab-function" "^4.0.12" + "@csstools/postcss-progressive-custom-properties" "^4.2.1" + "@csstools/postcss-random-function" "^2.0.1" + "@csstools/postcss-relative-color-syntax" "^3.0.12" "@csstools/postcss-scope-pseudo-class" "^4.0.1" - "@csstools/postcss-sign-functions" "^1.1.0" - "@csstools/postcss-stepped-value-functions" "^4.0.5" - "@csstools/postcss-text-decoration-shorthand" "^4.0.1" - "@csstools/postcss-trigonometric-functions" "^4.0.5" + "@csstools/postcss-sign-functions" "^1.1.4" + "@csstools/postcss-stepped-value-functions" "^4.0.9" + "@csstools/postcss-text-decoration-shorthand" "^4.0.3" + "@csstools/postcss-trigonometric-functions" "^4.0.9" "@csstools/postcss-unset-value" "^4.0.0" - autoprefixer "^10.4.19" - browserslist "^4.23.1" + autoprefixer "^10.4.21" + browserslist "^4.26.0" css-blank-pseudo "^7.0.1" - css-has-pseudo "^7.0.1" + css-has-pseudo "^7.0.3" css-prefers-color-scheme "^10.0.0" - cssdb "^8.2.1" + cssdb "^8.4.2" postcss-attribute-case-insensitive "^7.0.1" postcss-clamp "^4.1.0" - postcss-color-functional-notation "^7.0.6" + postcss-color-functional-notation "^7.0.12" postcss-color-hex-alpha "^10.0.0" postcss-color-rebeccapurple "^10.0.0" - postcss-custom-media "^11.0.5" - postcss-custom-properties "^14.0.4" - postcss-custom-selectors "^8.0.4" + postcss-custom-media "^11.0.6" + postcss-custom-properties "^14.0.6" + postcss-custom-selectors "^8.0.5" postcss-dir-pseudo-class "^9.0.1" - postcss-double-position-gradients "^6.0.0" + postcss-double-position-gradients "^6.0.4" postcss-focus-visible "^10.0.1" postcss-focus-within "^9.0.1" postcss-font-variant "^5.0.0" postcss-gap-properties "^6.0.0" postcss-image-set-function "^7.0.0" - postcss-lab-function "^7.0.6" - postcss-logical "^8.0.0" - postcss-nesting "^13.0.1" + postcss-lab-function "^7.0.12" + postcss-logical "^8.1.0" + postcss-nesting "^13.0.2" postcss-opacity-percentage "^3.0.0" postcss-overflow-shorthand "^6.0.0" postcss-page-break "^3.0.4" @@ -9492,7 +9377,7 @@ postcss-zindex@^6.0.2: resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-6.0.2.tgz#e498304b83a8b165755f53db40e2ea65a99b56e1" integrity sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg== -postcss@^8.4.21, postcss@^8.4.24, postcss@^8.4.26, postcss@^8.4.33, postcss@^8.4.38: +postcss@^8.4.21, postcss@^8.4.24, postcss@^8.4.33: version "8.4.49" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.49.tgz#4ea479048ab059ab3ae61d082190fabfd994fe19" integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA== @@ -9501,6 +9386,15 @@ postcss@^8.4.21, postcss@^8.4.24, postcss@^8.4.26, postcss@^8.4.33, postcss@^8.4 picocolors "^1.1.1" source-map-js "^1.2.1" +postcss@^8.5.4: + version "8.5.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" + integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== + dependencies: + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" + pretty-error@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6" @@ -9645,36 +9539,6 @@ rc@1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-dev-utils@^12.0.1: - version "12.0.1" - resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.1.tgz#ba92edb4a1f379bd46ccd6bcd4e7bc398df33e73" - integrity sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ== - dependencies: - "@babel/code-frame" "^7.16.0" - address "^1.1.2" - browserslist "^4.18.1" - chalk "^4.1.2" - cross-spawn "^7.0.3" - detect-port-alt "^1.1.6" - escape-string-regexp "^4.0.0" - filesize "^8.0.6" - find-up "^5.0.0" - fork-ts-checker-webpack-plugin "^6.5.0" - global-modules "^2.0.0" - globby "^11.0.4" - gzip-size "^6.0.0" - immer "^9.0.7" - is-root "^2.1.0" - loader-utils "^3.2.0" - open "^8.4.0" - pkg-up "^3.1.0" - prompts "^2.4.2" - react-error-overlay "^6.0.11" - recursive-readdir "^2.2.2" - shell-quote "^1.7.3" - strip-ansi "^6.0.1" - text-table "^0.2.0" - react-dom@^18.0.0: version "18.3.1" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" @@ -9683,29 +9547,15 @@ react-dom@^18.0.0: loose-envify "^1.1.0" scheduler "^0.23.2" -react-error-overlay@^6.0.11: - version "6.0.11" - resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" - integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== - -react-fast-compare@^3.2.0, react-fast-compare@^3.2.2: +react-fast-compare@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== -react-helmet-async@*: - version "2.0.5" - resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-2.0.5.tgz#cfc70cd7bb32df7883a8ed55502a1513747223ec" - integrity sha512-rYUYHeus+i27MvFE+Jaa4WsyBKGkL6qVgbJvSBoX8mbsWoABJXdEO0bZyi0F6i+4f0NuIb8AvqPMj3iXFHkMwg== - dependencies: - invariant "^2.2.4" - react-fast-compare "^3.2.2" - shallowequal "^1.1.0" - -react-helmet-async@^1.3.0: +"react-helmet-async@npm:@slorber/react-helmet-async@1.3.0": version "1.3.0" - resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.3.0.tgz#7bd5bf8c5c69ea9f02f6083f14ce33ef545c222e" - integrity sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg== + resolved "https://registry.yarnpkg.com/@slorber/react-helmet-async/-/react-helmet-async-1.3.0.tgz#11fbc6094605cf60aa04a28c17e0aab894b4ecff" + integrity sha512-e9/OK8VhwUSc67diWI8Rb3I0YgI9/SBQtnhe9aEuK6MhZm7ntZZimXgwXnd8W96YTmSOb9M4d8LwhRZyhWr/1A== dependencies: "@babel/runtime" "^7.12.5" invariant "^2.2.4" @@ -9718,10 +9568,10 @@ react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-json-view-lite@^1.2.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/react-json-view-lite/-/react-json-view-lite-1.5.0.tgz#377cc302821717ac79a1b6d099e1891df54c8662" - integrity sha512-nWqA1E4jKPklL2jvHWs6s+7Na0qNgw9HCP6xehdQJeg6nPBTFZgGwyko9Q0oj+jQWKTTVRS30u0toM5wiuL3iw== +react-json-view-lite@^2.3.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/react-json-view-lite/-/react-json-view-lite-2.5.0.tgz#c7ff011c7cc80e9900abc7aa4916c6a5c6d6c1c6" + integrity sha512-tk7o7QG9oYyELWHL8xiMQ8x4WzjCzbWNyig3uexmkLb54r8jO0yH3WCWx8UZS0c49eSA4QUmG5caiRJ8fAn58g== react-lifecycles-compat@^3.0.0: version "3.0.4" @@ -9832,18 +9682,6 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -reading-time@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/reading-time/-/reading-time-1.5.0.tgz#d2a7f1b6057cb2e169beaf87113cc3411b5bc5bb" - integrity sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg== - -rechoir@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" - integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== - dependencies: - resolve "^1.1.6" - recma-build-jsx@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz#c02f29e047e103d2fab2054954e1761b8ea253c4" @@ -9884,13 +9722,6 @@ recma-stringify@^1.0.0: unified "^11.0.0" vfile "^6.0.0" -recursive-readdir@^2.2.2: - version "2.2.3" - resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.3.tgz#e726f328c0d69153bcabd5c322d3195252379372" - integrity sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA== - dependencies: - minimatch "^3.0.5" - regenerate-unicode-properties@^10.2.0: version "10.2.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz#626e39df8c372338ea9b8028d1f99dc3fd9c3db0" @@ -10164,7 +9995,7 @@ resolve-pathname@^3.0.0: resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== -resolve@^1.1.6, resolve@^1.14.2: +resolve@^1.14.2: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -10190,13 +10021,6 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - robust-predicates@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771" @@ -10212,11 +10036,6 @@ roughjs@^4.6.6: points-on-curve "^0.2.0" points-on-path "^0.2.1" -rtl-detect@^1.0.4: - version "1.1.2" - resolved "https://registry.yarnpkg.com/rtl-detect/-/rtl-detect-1.1.2.tgz#ca7f0330af5c6bb626c15675c642ba85ad6273c6" - integrity sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ== - rtlcss@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/rtlcss/-/rtlcss-4.3.0.tgz#f8efd4d5b64f640ec4af8fa25b65bacd9e07cc97" @@ -10278,14 +10097,10 @@ scheduler@^0.23.2: dependencies: loose-envify "^1.1.0" -schema-utils@2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" - integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== - dependencies: - "@types/json-schema" "^7.0.4" - ajv "^6.12.2" - ajv-keywords "^3.4.1" +schema-dts@^1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/schema-dts/-/schema-dts-1.1.5.tgz#9237725d305bac3469f02b292a035107595dc324" + integrity sha512-RJr9EaCmsLzBX2NDiO5Z3ux2BVosNZN5jo0gWgsyKvxKIUL5R3swNvoorulAeL9kLB0iTSX7V6aokhla2m7xbg== schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.2.0: version "3.3.0" @@ -10349,7 +10164,7 @@ semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.2, semver@^7.5.4, semver@^7.6.3: +semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.2, semver@^7.5.4, semver@^7.6.3: version "7.6.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== @@ -10462,20 +10277,11 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@^1.7.3, shell-quote@^1.8.1: +shell-quote@^1.8.1: version "1.8.2" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.2.tgz#d2d83e057959d53ec261311e9e9b8f51dcb2934a" integrity sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA== -shelljs@^0.8.5: - version "0.8.5" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" - integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== - dependencies: - glob "^7.0.0" - interpret "^1.0.0" - rechoir "^0.6.2" - side-channel@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" @@ -10486,7 +10292,7 @@ side-channel@^1.0.6: get-intrinsic "^1.2.4" object-inspect "^1.13.1" -signal-exit@^3.0.0, signal-exit@^3.0.2: +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -10713,6 +10519,11 @@ strip-bom-string@^1.0.0: resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" integrity sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g== +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -10800,6 +10611,14 @@ svgo@^3.0.2, svgo@^3.2.0: csso "^5.0.5" picocolors "^1.0.0" +swr@^2.2.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/swr/-/swr-2.3.6.tgz#5fee0ee8a0762a16871ee371075cb09422b64f50" + integrity sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw== + dependencies: + dequal "^2.0.3" + use-sync-external-store "^1.4.0" + synp@^1.9.10: version "1.9.14" resolved "https://registry.yarnpkg.com/synp/-/synp-1.9.14.tgz#1feb222d273f6092c6c264746277e95655c60717" @@ -10815,11 +10634,6 @@ synp@^1.9.10: semver "^7.6.3" sort-object-keys "^1.1.3" -tapable@^1.0.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" - integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== - tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" @@ -10846,11 +10660,6 @@ terser@^5.10.0, terser@^5.15.1, terser@^5.26.0: commander "^2.20.0" source-map-support "~0.5.20" -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== - thenify-all@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" @@ -10870,6 +10679,11 @@ thingies@^1.20.0: resolved "https://registry.yarnpkg.com/thingies/-/thingies-1.21.0.tgz#e80fbe58fd6fdaaab8fad9b67bd0a5c943c445c1" integrity sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g== +throttleit@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-2.1.0.tgz#a7e4aa0bf4845a5bd10daa39ea0c783f631a07b4" + integrity sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw== + thunky@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" @@ -10890,6 +10704,11 @@ tinyexec@^1.0.1: resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-1.0.1.tgz#70c31ab7abbb4aea0a24f55d120e5990bfa1e0b1" integrity sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw== +tinypool@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.1.1.tgz#059f2d042bd37567fbc017d3d426bdd2a2612591" + integrity sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg== + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -11234,6 +11053,14 @@ update-browserslist-db@^1.1.1: escalade "^3.2.0" picocolors "^1.1.0" +update-browserslist-db@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz#7802aa2ae91477f255b86e0e46dbc787a206ad4a" + integrity sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + update-notifier@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-6.0.2.tgz#a6990253dfe6d5a02bd04fbb6a61543f55026b60" @@ -11275,6 +11102,11 @@ use-editable@^2.3.3: resolved "https://registry.yarnpkg.com/use-editable/-/use-editable-2.3.3.tgz#a292fe9ba4c291cd28d1cc2728c75a5fc8d9a33f" integrity sha512-7wVD2JbfAFJ3DK0vITvXBdpd9JAz5BcKAAolsnLBuBn6UDDwBGuCIAGvR3yA2BNKm578vAMVHFCWaOcA+BhhiA== +use-sync-external-store@^1.4.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d" + integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w== + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -11511,7 +11343,7 @@ webpack-dev-middleware@^7.4.2: range-parser "^1.2.1" schema-utils "^4.0.0" -webpack-dev-server@>=5.2.1, webpack-dev-server@^4.15.2: +webpack-dev-server@>=5.2.1, webpack-dev-server@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz#96a143d50c58fef0c79107e61df911728d7ceb39" integrity sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg== @@ -11651,13 +11483,6 @@ which@4.0.0: dependencies: isexe "^3.1.1" -which@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -11702,11 +11527,6 @@ wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: string-width "^5.0.1" strip-ansi "^7.0.1" -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - write-file-atomic@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" @@ -11749,11 +11569,6 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yaml@^1.7.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - yarn-audit-fix@^9.3.10: version "9.3.12" resolved "https://registry.yarnpkg.com/yarn-audit-fix/-/yarn-audit-fix-9.3.12.tgz#cc34e87aa080bace32f2f105be6b581a3cb6eb24" @@ -11778,16 +11593,16 @@ yarn-audit-fix@^9.3.10: synp "^1.9.10" tslib "^2.5.3" -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - yocto-queue@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.1.1.tgz#fef65ce3ac9f8a32ceac5a634f74e17e5b232110" integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g== +zod@^4.1.8: + version "4.1.12" + resolved "https://registry.yarnpkg.com/zod/-/zod-4.1.12.tgz#64f1ea53d00eab91853195653b5af9eee68970f0" + integrity sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ== + zwitch@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" From dcf4e3a4ffca424a3e8caa99b32ce74d345b3f76 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 19 Nov 2025 03:56:10 -0800 Subject: [PATCH 024/142] add file_id to diagnostic RelatedInformation Summary: A given diagnostic can have related information in it, which carries relevant context to the provided diagnostic. For example, for a head mismatch diagnostic, it reports the specific head location(s) that do not match. The LSP spec uses a `Location` for these, which includes a file URL, meaning the related info can be in a different file from where the diagnostic is reported. In ELP, we currently do not provide a way of populating this information, so can only refer to related information within the same file. Thic can result in erroneous outcomes. ## This diff We include a `FileId` in our internal `RelatedInformation` structure, and map it to a URL when serializing for transmission to the LSP client Reviewed By: robertoaloi Differential Revision: D87316542 fbshipit-source-id: 5f94eb4fe5bb73e7bf1fff7cad458231c51bd6f0 --- crates/elp/src/bin/elp_parse_cli.rs | 27 +++++---------- crates/elp/src/convert.rs | 34 ++++++++++++------- crates/elp/src/server.rs | 22 ++++++++++-- crates/ide/src/diagnostics.rs | 7 ++-- crates/ide/src/diagnostics/head_mismatch.rs | 4 ++- .../src/diagnostics/misspelled_attribute.rs | 1 + crates/ide/src/diagnostics_collection.rs | 4 +-- crates/ide/src/tests.rs | 3 +- 8 files changed, 63 insertions(+), 39 deletions(-) diff --git a/crates/elp/src/bin/elp_parse_cli.rs b/crates/elp/src/bin/elp_parse_cli.rs index fcc8652ec2..16d4675d7d 100644 --- a/crates/elp/src/bin/elp_parse_cli.rs +++ b/crates/elp/src/bin/elp_parse_cli.rs @@ -24,7 +24,6 @@ use elp::cli::Cli; use elp::convert; use elp::memory_usage::MemoryUsage; use elp::otp_file_to_ignore; -use elp::server::file_id_to_url; use elp_eqwalizer::Mode; use elp_ide::Analysis; use elp_ide::diagnostics; @@ -132,8 +131,7 @@ pub fn parse_all( (None, _, true) => do_parse_all_seq(cli, &loaded, &cfg, &args.to)?, (None, _, false) => do_parse_all_par(cli, &loaded, &cfg, &args.to)?, (Some(file_id), Some(name), _) => { - do_parse_one(&analysis, &loaded.vfs, &cfg, &args.to, file_id, &name)? - .map_or(vec![], |x| vec![x]) + do_parse_one(&analysis, &cfg, &args.to, file_id, &name)?.map_or(vec![], |x| vec![x]) } (Some(file_id), _, _) => panic!("Could not get name from file_id for {file_id:?}"), }; @@ -144,10 +142,6 @@ pub fn parse_all( let db = loaded.analysis_host.raw_database(); - // We need a `Url` for converting to the lsp_types::Diagnostic for - // printing, but do not print it out. So just create a dummy value - let url = lsp_types::Url::parse("file:///unused_url").ok().unwrap(); - telemetry::report_elapsed_time("parse-elp operational", start_time); let memory_end = MemoryUsage::now(); @@ -197,7 +191,7 @@ pub fn parse_all( cli, )?; } else { - print_diagnostic(&diag, &line_index, &url, &mut err_in_diag, cli)?; + print_diagnostic(&diag, &line_index, &mut err_in_diag, cli)?; } } } @@ -242,11 +236,10 @@ fn print_diagnostic_json( fn print_diagnostic( diag: &diagnostics::Diagnostic, line_index: &LineIndex, - url: &lsp_types::Url, err_in_diag: &mut bool, cli: &mut dyn Cli, ) -> Result<(), anyhow::Error> { - let diag = convert::ide_to_lsp_diagnostic(line_index, url, diag); + let diag = convert::ide_to_lsp_diagnostic(line_index, diag, |_file_id| None); let severity = match diag.severity { None => DiagnosticSeverity::ERROR, Some(sev) => { @@ -289,7 +282,6 @@ fn do_parse_all_par( let pb = cli.progress(module_iter.len() as u64, "Parsing modules"); - let vfs = &loaded.vfs; Ok(module_iter .par_bridge() .progress_with(pb) @@ -300,7 +292,7 @@ fn do_parse_all_par( && file_source == FileSource::Src && db.file_app_type(file_id).ok() != Some(Some(AppType::Dep)) { - do_parse_one(db, vfs, config, to, file_id, module_name.as_str()).unwrap() + do_parse_one(db, config, to, file_id, module_name.as_str()).unwrap() } else { None } @@ -321,7 +313,6 @@ fn do_parse_all_seq( let pb = cli.progress(module_iter.len() as u64, "Parsing modules (sequential)"); - let vfs = &loaded.vfs; let db = loaded.analysis(); Ok(module_iter .progress_with(pb) @@ -330,7 +321,7 @@ fn do_parse_all_seq( && file_source == FileSource::Src && db.file_app_type(file_id).ok() != Some(Some(AppType::Dep)) { - do_parse_one(&db, vfs, config, to, file_id, module_name.as_str()).unwrap() + do_parse_one(&db, config, to, file_id, module_name.as_str()).unwrap() } else { None } @@ -340,13 +331,11 @@ fn do_parse_all_seq( fn do_parse_one( db: &Analysis, - vfs: &Vfs, config: &DiagnosticsConfig, to: &Option, file_id: FileId, name: &str, ) -> Result> { - let url = file_id_to_url(vfs, file_id); let native = db.native_diagnostics(config, &vec![], file_id)?; let erlang_service_diagnostics = db.erlang_service_diagnostics(file_id, config, RemoveElpReported::Yes)?; @@ -364,11 +353,13 @@ fn do_parse_one( let mut output = File::create(to_path)?; for diagnostic in native.iter() { - let diagnostic = convert::ide_to_lsp_diagnostic(&line_index, &url, diagnostic); + let diagnostic = + convert::ide_to_lsp_diagnostic(&line_index, diagnostic, |_file_id| None); writeln!(output, "{diagnostic:?}")?; } for diagnostic in erlang_service.iter() { - let diagnostic = convert::ide_to_lsp_diagnostic(&line_index, &url, diagnostic); + let diagnostic = + convert::ide_to_lsp_diagnostic(&line_index, diagnostic, |_file_id| None); writeln!(output, "{diagnostic:?}")?; } } diff --git a/crates/elp/src/convert.rs b/crates/elp/src/convert.rs index 3f68488f7d..3715353c11 100644 --- a/crates/elp/src/convert.rs +++ b/crates/elp/src/convert.rs @@ -26,6 +26,7 @@ use elp_ide::elp_ide_db::assists::AssistContextDiagnostic; use elp_ide::elp_ide_db::assists::AssistContextDiagnosticCode; use elp_ide::elp_ide_db::elp_base_db::AbsPath; use elp_ide::elp_ide_db::elp_base_db::AbsPathBuf; +use elp_ide::elp_ide_db::elp_base_db::FileId; use elp_ide::elp_ide_db::elp_base_db::VfsPath; use lsp_types::DiagnosticRelatedInformation; use lsp_types::Location; @@ -67,11 +68,14 @@ pub fn diagnostic_severity(severity: Severity) -> lsp_types::DiagnosticSeverity } } -pub fn ide_to_lsp_diagnostic( +pub fn ide_to_lsp_diagnostic( line_index: &LineIndex, - url: &Url, d: &Diagnostic, -) -> lsp_types::Diagnostic { + get_file_info: F, +) -> lsp_types::Diagnostic +where + F: Fn(FileId) -> Option<(LineIndex, Url)>, +{ let code_description = match &d.code_doc_uri { Some(uri) => match lsp_types::Url::parse(uri) { Ok(href) => Some(lsp_types::CodeDescription { href }), @@ -90,7 +94,7 @@ pub fn ide_to_lsp_diagnostic( code_description, source, message: d.message.clone(), - related_information: from_related(line_index, url, &d.related_info), + related_information: from_related(get_file_info, &d.related_info), tags: d.tag.as_ref().map(lsp_diagnostic_tags), data: None, } @@ -165,22 +169,26 @@ pub fn eqwalizer_to_arc_diagnostic( ) } -fn from_related( - line_index: &LineIndex, - url: &Url, +fn from_related( + get_file_info: F, r: &Option>, -) -> Option> { +) -> Option> +where + F: Fn(elp_ide::elp_ide_db::elp_base_db::FileId) -> Option<(LineIndex, Url)>, +{ r.as_ref().map(|ri| { ri.iter() - .map(|i| { + .filter_map(|i| { + // Get the line index and URL for the file that contains the related information + let (line_index, uri) = get_file_info(i.file_id)?; let location = Location { - range: range(line_index, i.range), - uri: url.clone(), + range: range(&line_index, i.range), + uri, }; - DiagnosticRelatedInformation { + Some(DiagnosticRelatedInformation { location, message: i.message.clone(), - } + }) }) .collect() }) diff --git a/crates/elp/src/server.rs b/crates/elp/src/server.rs index 3d2cf02252..bec67159cd 100644 --- a/crates/elp/src/server.rs +++ b/crates/elp/src/server.rs @@ -624,7 +624,16 @@ impl Server { .diagnostics .diagnostics_for(*file_id) .iter() - .map(|d| ide_to_lsp_diagnostic(&line_index, &url, d)) + .map(|d| { + let vfs = self.vfs.clone(); + ide_to_lsp_diagnostic(&line_index, d, |related_file_id| { + // Get the line index and URL for the related file + let url = file_id_to_url(&vfs.read(), related_file_id); + let snapshot = self.snapshot(); + let line_index = snapshot.analysis.line_index(related_file_id).ok()?; + Some(((*line_index).clone(), url)) + }) + }) .collect(); self.send_notification::( @@ -828,11 +837,20 @@ impl Server { Arc::make_mut(&mut this.diagnostics) .clear(file_id); if let Ok(line_index) = analysis.line_index(file_id) { + let vfs = this.vfs.clone(); diagnostics = this .diagnostics .diagnostics_for(file_id) .iter() - .map(|d| ide_to_lsp_diagnostic(&line_index, &url, d)) + .map(|d| { + let snapshot = this.snapshot(); + ide_to_lsp_diagnostic(&line_index, d, |related_file_id| { + // Get the line index and URL for the related file + let url = file_id_to_url(&vfs.read(), related_file_id); + let line_index = snapshot.analysis.line_index(related_file_id).ok()?; + Some(((*line_index).clone(), url)) + }) + }) .collect() } } diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index a6684a7144..4f8a9f8732 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -227,8 +227,9 @@ impl Diagnostic { self } - pub(crate) fn as_related(&self) -> RelatedInformation { + pub(crate) fn as_related(&self, file_id: FileId) -> RelatedInformation { RelatedInformation { + file_id, range: self.range, message: self.message.clone(), } @@ -447,6 +448,7 @@ impl Diagnostic { #[derive(Debug, Clone)] pub struct RelatedInformation { + pub file_id: FileId, pub range: TextRange, pub message: String, } @@ -2818,6 +2820,7 @@ fn combine_syntax_errors(native: &Labeled, erlang_service: &Labeled) -> Labeled /// Combine the ELP and erlang_service diagnostics. In particular, /// flatten any cascading diagnostics if possible. pub fn attach_related_diagnostics( + file_id: FileId, native: LabeledDiagnostics, erlang_service: LabeledDiagnostics, ) -> Vec { @@ -2853,7 +2856,7 @@ pub fn attach_related_diagnostics( .flat_map(|(mfa_label, syntax_error_diags)| { if let Some(related) = erlang_service.labeled_undefined_errors.get(mfa_label) { undefineds_to_remove.insert(mfa_label); - let related_info = related.iter().map(|d| d.as_related()).collect_vec(); + let related_info = related.iter().map(|d| d.as_related(file_id)).collect_vec(); syntax_error_diags .iter() .map(|d| d.clone().with_related(Some(related_info.clone()))) diff --git a/crates/ide/src/diagnostics/head_mismatch.rs b/crates/ide/src/diagnostics/head_mismatch.rs index 425ed09e6d..b548b84dff 100644 --- a/crates/ide/src/diagnostics/head_mismatch.rs +++ b/crates/ide/src/diagnostics/head_mismatch.rs @@ -270,6 +270,7 @@ impl Validate for Name { attr_loc, ) .with_related(Some(vec![RelatedInformation { + file_id, range: ref_loc, message: "Mismatched clause name".to_string(), }])) @@ -293,7 +294,7 @@ impl Validate for Arity { fn make_diagnostic( self, - _file_id: FileId, + file_id: FileId, attr: &usize, hattr: &usize, attr_loc: TextRange, @@ -305,6 +306,7 @@ impl Validate for Arity { attr_loc, ) .with_related(Some(vec![RelatedInformation { + file_id, range: ref_loc, message: "Mismatched clause".to_string(), }])) diff --git a/crates/ide/src/diagnostics/misspelled_attribute.rs b/crates/ide/src/diagnostics/misspelled_attribute.rs index b3939bc9cf..9dc5868879 100644 --- a/crates/ide/src/diagnostics/misspelled_attribute.rs +++ b/crates/ide/src/diagnostics/misspelled_attribute.rs @@ -133,6 +133,7 @@ fn make_diagnostic( attr_name_range, ) .with_related(Some(vec![RelatedInformation { + file_id, range: attr_name_range, message: "Misspelled attribute".to_string(), }])) diff --git a/crates/ide/src/diagnostics_collection.rs b/crates/ide/src/diagnostics_collection.rs index d944e223da..f15aed182c 100644 --- a/crates/ide/src/diagnostics_collection.rs +++ b/crates/ide/src/diagnostics_collection.rs @@ -104,7 +104,7 @@ impl DiagnosticCollection { let native = self.native.get(&file_id).unwrap_or(&empty_diags); let erlang_service = self.erlang_service.get(&file_id).unwrap_or(&empty_diags); let mut combined: Vec = - attach_related_diagnostics(native.clone(), erlang_service.clone()); + attach_related_diagnostics(file_id, native.clone(), erlang_service.clone()); let eqwalizer = self.eqwalizer.get(&file_id).into_iter().flatten().cloned(); let eqwalizer_project = self .eqwalizer_project @@ -348,7 +348,7 @@ mod tests { let file_id = *file_id; let diagnostics = diagnostics::native_diagnostics(&db, &config, &vec![], file_id); - let combined = attach_related_diagnostics(diagnostics, extra_diags.clone()); + let combined = attach_related_diagnostics(file_id, diagnostics, extra_diags.clone()); let expected = fixture.annotations_by_file_id(&file_id); let mut actual = combined .into_iter() diff --git a/crates/ide/src/tests.rs b/crates/ide/src/tests.rs index bd607b5d30..71125f5094 100644 --- a/crates/ide/src/tests.rs +++ b/crates/ide/src/tests.rs @@ -513,7 +513,8 @@ pub(crate) fn check_diagnostics_with_config_and_extra( for file_id in &fixture.files { let file_id = *file_id; let diagnostics = diagnostics::native_diagnostics(&analysis.db, &config, &vec![], file_id); - let diagnostics = diagnostics::attach_related_diagnostics(diagnostics, extra_diags.clone()); + let diagnostics = + diagnostics::attach_related_diagnostics(file_id, diagnostics, extra_diags.clone()); let expected = fixture.annotations_by_file_id(&file_id); let actual = convert_diagnostics_to_annotations(diagnostics); From 8efb4fe29ced0a425f743a51a56773ea72bf5a8e Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 19 Nov 2025 03:56:10 -0800 Subject: [PATCH 025/142] BE: track related info in declarative tests Summary: Our declarative tests allow us to add annotations for expected diagnostics, so we can clearly see and check what is generated. But at present there is no way to check any related information for a diagnostic. This diff adds that capability, showing related information together with the FileId and range each refers to. Reviewed By: robertoaloi Differential Revision: D87316965 fbshipit-source-id: ea7db2980b11950c3552befec7b666665ceef84b --- .llms/rules/elp_development.md | 6 ++++++ crates/ide/src/diagnostics.rs | 5 +++++ crates/ide/src/diagnostics/head_mismatch.rs | 6 ++++++ .../src/diagnostics/misspelled_attribute.rs | 3 +++ crates/ide/src/diagnostics/unused_include.rs | 1 + crates/ide/src/tests.rs | 17 +++++++++++++++++ crates/project_model/src/test_fixture.rs | 18 ++++++++++++++++-- 7 files changed, 54 insertions(+), 2 deletions(-) diff --git a/.llms/rules/elp_development.md b/.llms/rules/elp_development.md index 0a024728a1..efd132ac4b 100644 --- a/.llms/rules/elp_development.md +++ b/.llms/rules/elp_development.md @@ -168,6 +168,12 @@ directly in test code: - **Left-margin annotation**: `%%<^^^ text` - Annotation starts at `%%` position instead of first `^` - **Multiline annotations**: Use continuation lines with `%% | next line` + - Continuation lines are particularly useful for diagnostics with related information: + ```erlang + foo() -> syntax error oops. + %% ^^^^^ error: P1711: syntax error before: error + %% | Related info: 0:45-50 function foo/0 undefined + ``` #### Example Test Fixture diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 4f8a9f8732..5e25b9d385 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -3433,6 +3433,9 @@ baz(1)->4. -spec foo() -> ok. foo( -> ok. %% %% ^ error: W0004: Missing ')' + %% | Related info: 0:21-43 function foo/0 undefined + %% | Related info: 0:74-79 function foo/0 undefined + %% | Related info: 0:82-99 spec for undefined function foo/0 "#, ); } @@ -3463,6 +3466,7 @@ baz(1)->4. foo() -> syntax error oops. %% ^^^^^ error: P1711: syntax error before: error + %% | Related info: 0:25-30 function foo/0 undefined "#, ); } @@ -3766,6 +3770,7 @@ baz(1)->4. \~"\"\\µA\"" = \~/"\\µA"/ X = 3. %% ^ error: P1711: syntax error before: X + %% | Related info: 0:32-37 function foo/0 undefined "#, ); } diff --git a/crates/ide/src/diagnostics/head_mismatch.rs b/crates/ide/src/diagnostics/head_mismatch.rs index b548b84dff..3a92d6f931 100644 --- a/crates/ide/src/diagnostics/head_mismatch.rs +++ b/crates/ide/src/diagnostics/head_mismatch.rs @@ -396,6 +396,7 @@ mod tests { foo(0) -> 1; boo(1) -> 2. %% ^^^ 💡 error: P1700: head mismatch 'boo' vs 'foo' + %% | Related info: 0:21-24 Mismatched clause name "#, ); check_fix( @@ -421,6 +422,7 @@ mod tests { ok; fooX(_X) -> %% ^^^^ 💡 error: P1700: head mismatch 'fooX' vs 'food' + %% | Related info: 0:21-25 Mismatched clause name no. bar() -> @@ -450,6 +452,7 @@ mod tests { -module(main). foo(0) -> 1; %% ^^^ 💡 error: P1700: head mismatch 'foo' vs 'boo' + %% | Related info: 0:37-40 Mismatched clause name boo(1) -> 2; boo(2) -> 3. "#, @@ -478,6 +481,7 @@ mod tests { foo(0) -> 1; foo(1,0) -> 2. %% ^^^^^^^^^^^^^ error: P1700: head arity mismatch 2 vs 1 + %% | Related info: 0:21-32 Mismatched clause "#, ); } @@ -490,6 +494,7 @@ mod tests { foo(2,0) -> 3; foo(0) -> 1; %% ^^^^^^^^^^^ error: P1700: head arity mismatch 1 vs 2 + %% | Related info: 0:21-34 Mismatched clause foo(1,0) -> 2. "#, ); @@ -516,6 +521,7 @@ mod tests { (0) -> ok; A(N) -> ok %% ^ 💡 error: P1700: head mismatch 'A' vs '' + %% | Related info: 0:44-53 Mismatched clause name end, F(). "#, diff --git a/crates/ide/src/diagnostics/misspelled_attribute.rs b/crates/ide/src/diagnostics/misspelled_attribute.rs index 9dc5868879..6ba95c8ef9 100644 --- a/crates/ide/src/diagnostics/misspelled_attribute.rs +++ b/crates/ide/src/diagnostics/misspelled_attribute.rs @@ -164,6 +164,7 @@ mod tests { -module(main). -dyalizer({nowarn_function, f/0}). %%% ^^^^^^^^ 💡 error: W0013: misspelled attribute, saw 'dyalizer' but expected 'dialyzer' + %%% | Related info: 0:22-30 Misspelled attribute "#, ); check_fix( @@ -224,6 +225,7 @@ mod tests { -module(main). -module_doc """ %%% ^^^^^^^^^^ 💡 error: W0013: misspelled attribute, saw 'module_doc' but expected 'moduledoc' +%%% | Related info: 0:24-34 Misspelled attribute Hola """. "#, @@ -237,6 +239,7 @@ mod tests { -module(main). -docs """ %%% ^^^^ 💡 error: W0013: misspelled attribute, saw 'docs' but expected 'doc' +%%% | Related info: 0:24-28 Misspelled attribute Hola """. foo() -> ok. diff --git a/crates/ide/src/diagnostics/unused_include.rs b/crates/ide/src/diagnostics/unused_include.rs index 3a91377338..959181a328 100644 --- a/crates/ide/src/diagnostics/unused_include.rs +++ b/crates/ide/src/diagnostics/unused_include.rs @@ -581,6 +581,7 @@ foo() -> ok. %% The following shows up as a wild attribute, which we regard as being used. -defin e(X, 1). %% ^^^^^ 💡 error: W0013: misspelled attribute, saw 'defin' but expected 'define' +%% | Related info: 1:82-87 Misspelled attribute -def ine(Y, 2). "#, diff --git a/crates/ide/src/tests.rs b/crates/ide/src/tests.rs index 71125f5094..8d71b65dad 100644 --- a/crates/ide/src/tests.rs +++ b/crates/ide/src/tests.rs @@ -401,6 +401,23 @@ fn convert_diagnostics_to_annotations(diagnostics: Vec) -> Vec<(Text annotation.push_str(&d.code.as_code()); annotation.push_str(": "); annotation.push_str(&convert_diagnostic_message(&d)); + + // Append related info to the annotation + if let Some(related_info) = &d.related_info { + // Sort related info alphabetically by message for consistent test output + let mut sorted_info = related_info.clone(); + sorted_info.sort_by(|a, b| a.message.cmp(&b.message)); + for info in sorted_info { + annotation.push_str(&format!( + "\nRelated info: {}:{}-{} {}", + info.file_id.index(), + u32::from(info.range.start()), + u32::from(info.range.end()), + info.message + )); + } + } + (d.range, annotation) }) .collect::>(); diff --git a/crates/project_model/src/test_fixture.rs b/crates/project_model/src/test_fixture.rs index 769891190a..ac60d3b7c2 100644 --- a/crates/project_model/src/test_fixture.rs +++ b/crates/project_model/src/test_fixture.rs @@ -472,6 +472,14 @@ pub fn extract_tags(mut text: &str, tag: &str) -> (Vec<(TextRange, Option syntax error oops. +/// %% ^^^^^ error: P1711: syntax error before: error +/// %% | Related info: 0:25-30 function foo/0 undefined +/// ``` +/// /// Annotations point to the last line that actually was long enough for the /// range, not counting annotations themselves. So overlapping annotations are /// possible: @@ -551,10 +559,16 @@ pub fn extract_annotations(text: &str) -> (Vec<(TextRange, String)>, String) { if !res.is_empty() { offset += annotation_offset; this_line_annotations.push((offset, res.len() - 1)); - let &(_, idx) = prev_line_annotations + // Try to find a previous annotation at the same offset + let idx = if let Some(&(_, idx)) = prev_line_annotations .iter() .find(|&&(off, _idx)| off == offset) - .unwrap(); + { + idx + } else { + // If no exact offset match, append to the most recent annotation + res.len() - 1 + }; res[idx].1.push('\n'); res[idx].1.push_str(&content); } From 9a912293111545ec367f596a3e528af5b45e67d9 Mon Sep 17 00:00:00 2001 From: Jon Janzen Date: Wed, 19 Nov 2025 11:28:14 -0800 Subject: [PATCH 026/142] Rollback submodule to enable syncing --- eqwalizer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eqwalizer b/eqwalizer index bed8636fa6..16e6b5d339 160000 --- a/eqwalizer +++ b/eqwalizer @@ -1 +1 @@ -Subproject commit bed8636fa6b926e2292697be35e096db2386a78f +Subproject commit 16e6b5d3398b7c3067a95e577fadafb2f4ccb0a1 From 65de999b3be6a52b412b60405336f2e53cc1f720 Mon Sep 17 00:00:00 2001 From: Open Source Bot Date: Wed, 19 Nov 2025 09:40:39 -0800 Subject: [PATCH 027/142] Updating hashes Summary: GitHub commits: https://github.com/facebook/buck2-prelude/commit/5a717a3930afa31d2d411a985fda3c0c90451156 https://github.com/facebook/facebook-for-woocommerce/commit/b93909fb2a7468218e155d1ab06ec577d2e47160 https://github.com/facebook/fb303/commit/c4ca15fcd7bca6d601ffddfddeea83a0ff4ec353 https://github.com/facebook/fbthrift/commit/2d38838986f88e8c300cda9c89799e540bcaa3d1 https://github.com/facebook/folly/commit/f777219636b7682fd33226862e38ade13e61dac7 https://github.com/facebook/mvfst/commit/2ac8a7c0bb4e555a701088f3f827684642a485ba https://github.com/facebook/proxygen/commit/8010220b213cf39d738b327111649aa222dd3b53 https://github.com/facebook/wangle/commit/c5209193b7e23a60a9ae6da26e5c390cc5030cbe https://github.com/facebookexperimental/edencommon/commit/01b2a4fb6013cbe7f79521070f627156c75a585e https://github.com/facebookexperimental/rust-shed/commit/c22a8d4dd86485103b34fc6f512d922458657566 https://github.com/facebookincubator/fizz/commit/03c425fcf4b8b1e34d74c12f06927c633caaa272 https://github.com/facebookincubator/llm_orchestrator/commit/ce1f68dda55a00071064d97d3b40201643aee7ef https://github.com/whatsapp/eqwalizer/commit/1e0f24c5b8f2b023fdd53d4571c3240430d31f69 Reviewed By: sdwilsh fbshipit-source-id: 8f60cbe0f2feac847240f89707cb2023201f1e54 --- eqwalizer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eqwalizer b/eqwalizer index 16e6b5d339..1e0f24c5b8 160000 --- a/eqwalizer +++ b/eqwalizer @@ -1 +1 @@ -Subproject commit 16e6b5d3398b7c3067a95e577fadafb2f4ccb0a1 +Subproject commit 1e0f24c5b8f2b023fdd53d4571c3240430d31f69 From 479c76ce85a0ab8e141a8c1356931bdb4ea39c20 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Thu, 20 Nov 2025 01:02:30 -0800 Subject: [PATCH 028/142] Add unavailable_type diagnostic Summary: Introduce a new `unavailable_type` diagnostic, which warns about types which are correctly defined, but referenced from a module which does not have the application where the type is defined as a dependency. The diagnostic, which is complementary to eqWAlizer's [unknown_id](https://github.com/WhatsApp/eqwalizer/blob/main/docs/reference/errors.md#unknown_id) (for remote types) and Erlang/OTP's L1295/undefined_type (for local types), can be seen a step towards a ELP-powered XRef. The diagnostic works by scanning the `-spec`, `-type`, `-opaque` and `-callback` attributes. If a type is referenced, we verify whether the OTP application where the type is defined is a (recursive) dependency for the application where the type usage occurs. The diagnostic is currently disabled by default. Reviewed By: alanz Differential Revision: D87341709 fbshipit-source-id: a7729de093f50ae2bdc0c2138019e4a5503e7359 --- crates/elp/src/bin/main.rs | 17 + .../test/xref/unavailable_type.stdout | 5 + crates/hir/src/expr.rs | 20 ++ crates/ide/src/diagnostics.rs | 2 + .../ide/src/diagnostics/unavailable_type.rs | 294 ++++++++++++++++++ crates/ide_db/src/diagnostic_code.rs | 4 + test_projects/xref/.elp.toml | 5 + .../xref/app_a/src/unavailable_type.erl | 6 + test_projects/xref/app_b/src/app_b.erl | 8 + test_projects/xref/app_c/src/app_c.erl | 8 + .../xref/elp_lint_unavailable_type.toml | 2 + website/docs/erlang-error-index/w/W0059.md | 37 +++ 12 files changed, 408 insertions(+) create mode 100644 crates/elp/src/resources/test/xref/unavailable_type.stdout create mode 100644 crates/ide/src/diagnostics/unavailable_type.rs create mode 100644 test_projects/xref/.elp.toml create mode 100644 test_projects/xref/app_a/src/unavailable_type.erl create mode 100644 test_projects/xref/app_b/src/app_b.erl create mode 100644 test_projects/xref/app_c/src/app_c.erl create mode 100644 test_projects/xref/elp_lint_unavailable_type.toml create mode 100644 website/docs/erlang-error-index/w/W0059.md diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index a34cd76100..4cec310e22 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -1971,6 +1971,23 @@ mod tests { ) } + #[test] + fn lint_unavailable_type() { + simple_snapshot( + args_vec![ + "lint", + "--config-file", + "../../test_projects/xref/elp_lint_unavailable_type.toml", + "--module", + "unavailable_type" + ], + "xref", + expect_file!("../resources/test/xref/unavailable_type.stdout"), + true, + None, + ) + } + #[test] fn lint_ssr_from_config() { simple_snapshot( diff --git a/crates/elp/src/resources/test/xref/unavailable_type.stdout b/crates/elp/src/resources/test/xref/unavailable_type.stdout new file mode 100644 index 0000000000..acec636118 --- /dev/null +++ b/crates/elp/src/resources/test/xref/unavailable_type.stdout @@ -0,0 +1,5 @@ +Reporting all diagnostics codes +module specified: unavailable_type +Diagnostics reported in 1 modules: + unavailable_type: 1 + 3:42-3:57::[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'). diff --git a/crates/hir/src/expr.rs b/crates/hir/src/expr.rs index adf3dea349..114930efe3 100644 --- a/crates/hir/src/expr.rs +++ b/crates/hir/src/expr.rs @@ -581,6 +581,26 @@ impl CallTarget { } } } + + pub fn range(&self, sema: &Semantic, body: &Body) -> Option { + match self { + CallTarget::Local { name } => { + let name_any_id = AnyExprId::TypeExpr(*name); + body.range_for_any(sema, name_any_id) + } + CallTarget::Remote { module, name, .. } => { + let module_any_id = AnyExprId::TypeExpr(*module); + let name_any_id = AnyExprId::TypeExpr(*name); + + let name_range = body.range_for_any(sema, name_any_id)?; + if let Some(module_range) = body.range_for_any(sema, module_any_id) { + module_range.cover(name_range) + } else { + Some(name_range) + } + } + } + } } impl CallTarget { diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 5e25b9d385..28e39270f6 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -135,6 +135,7 @@ mod replace_in_spec; mod sets_version_2; mod simplify_negation; mod trivial_match; +mod unavailable_type; mod undefined_function; mod undefined_macro; mod undocumented_function; @@ -1586,6 +1587,7 @@ const GENERIC_LINTERS: &[&dyn GenericDiagnostics] = &[ &undocumented_module::LINTER, &undocumented_function::LINTER, &no_catch::LINTER, + &unavailable_type::LINTER, ]; /// Unified registry for all types of linters diff --git a/crates/ide/src/diagnostics/unavailable_type.rs b/crates/ide/src/diagnostics/unavailable_type.rs new file mode 100644 index 0000000000..6a674f651b --- /dev/null +++ b/crates/ide/src/diagnostics/unavailable_type.rs @@ -0,0 +1,294 @@ +/* + * 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: unavailable-type +// +// Return a warning when referring to a type which is not defined in the module's dependencies. +// This diagnostic checks if a type referenced in specs, type definitions, or opaque types exists in: +// 1. The current module (local types) +// 2. Built-in Erlang types +// 3. Types from the module's application dependencies (including OTP apps) + +use std::borrow::Cow; + +use elp_ide_db::elp_base_db::AppData; +use elp_ide_db::elp_base_db::FileId; +use elp_project_model::AppName; +use hir::AnyExpr; +use hir::Callback; +use hir::InFile; +use hir::Semantic; +use hir::Spec; +use hir::Strategy; +use hir::TypeAlias; +use hir::TypeExpr; +use hir::fold::Fold; +use hir::fold::MacroStrategy; +use hir::fold::ParenStrategy; + +use super::DiagnosticCode; +use crate::diagnostics::GenericLinter; +use crate::diagnostics::GenericLinterMatchContext; +use crate::diagnostics::Linter; + +pub(crate) struct UnavailableTypeLinter; + +impl Linter for UnavailableTypeLinter { + fn id(&self) -> DiagnosticCode { + DiagnosticCode::UnavailableType + } + fn description(&self) -> &'static str { + "Type is not available through dependencies." + } + fn is_enabled(&self) -> bool { + false + } +} + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct Context { + type_label: String, + defining_app: String, + referencing_app: String, + referencing_target: String, +} + +impl GenericLinter for UnavailableTypeLinter { + type Context = Context; + + fn matches( + &self, + sema: &Semantic, + file_id: FileId, + ) -> Option>> { + // Early return if we don't have app data - can't determine type availability + let referencing_app_data = sema.db.file_app_data(file_id)?; + + // Extract target once here - if not available, can't create diagnostics anyway + let referencing_target = referencing_app_data.buck_target_name.as_ref()?; + + let mut res = Vec::new(); + let form_list = sema.form_list(file_id); + + // Check -spec attributes + for (spec_id, _spec) in form_list.specs() { + check_spec( + &mut res, + sema, + file_id, + &referencing_app_data, + referencing_target, + spec_id, + ); + } + + // Check -type and -opaque attributes + for (type_alias_id, _type_alias) in form_list.type_aliases() { + check_type_alias( + &mut res, + sema, + file_id, + &referencing_app_data, + referencing_target, + type_alias_id, + ); + } + + // Check -callback attributes + for (callback_id, _callback) in form_list.callback_attributes() { + check_callback( + &mut res, + sema, + file_id, + &referencing_app_data, + referencing_target, + callback_id, + ); + } + + Some(res) + } + + fn match_description(&self, context: &Self::Context) -> Cow<'_, str> { + Cow::Owned(format!( + "The type '{}' is defined in application '{}', but the application is not a dependency of '{}' (defined in '{}').", + context.type_label, + context.defining_app, + context.referencing_app, + context.referencing_target + )) + } +} + +pub static LINTER: UnavailableTypeLinter = UnavailableTypeLinter; + +fn check_spec( + matches: &mut Vec>, + sema: &Semantic, + file_id: FileId, + referencing_app_data: &AppData, + referencing_target: &String, + spec_id: hir::SpecId, +) { + let spec_id = InFile::new(file_id, spec_id); + let spec_body = sema.db.spec_body(spec_id); + + Spec::fold( + sema, + Strategy { + macros: MacroStrategy::Expand, + parens: ParenStrategy::InvisibleParens, + }, + spec_id, + (), + &mut |_acc, ctx| { + check_type_call( + matches, + sema, + file_id, + referencing_app_data, + referencing_target, + &ctx, + &spec_body.body, + ); + }, + ); +} + +fn check_type_alias( + matches: &mut Vec>, + sema: &Semantic, + file_id: FileId, + referencing_app_data: &AppData, + referencing_target: &String, + type_alias_id: hir::TypeAliasId, +) { + let type_alias_id = InFile::new(file_id, type_alias_id); + let type_body = sema.db.type_body(type_alias_id); + + TypeAlias::fold( + sema, + Strategy { + macros: MacroStrategy::Expand, + parens: ParenStrategy::InvisibleParens, + }, + type_alias_id, + (), + &mut |_acc, ctx| { + check_type_call( + matches, + sema, + file_id, + referencing_app_data, + referencing_target, + &ctx, + &type_body.body, + ); + }, + ); +} + +fn check_callback( + matches: &mut Vec>, + sema: &Semantic, + file_id: FileId, + referencing_app_data: &AppData, + referencing_target: &String, + callback_id: hir::CallbackId, +) { + let callback_id = InFile::new(file_id, callback_id); + let callback_body = sema.db.callback_body(callback_id); + + Callback::fold( + sema, + Strategy { + macros: MacroStrategy::Expand, + parens: ParenStrategy::InvisibleParens, + }, + callback_id, + (), + &mut |_acc, ctx| { + check_type_call( + matches, + sema, + file_id, + referencing_app_data, + referencing_target, + &ctx, + &callback_body.body, + ); + }, + ); +} + +fn is_type_available( + sema: &Semantic, + referencing_app_data: &AppData, + referencing_target: &String, + referencing_app_name: &AppName, + defining_app_name: &AppName, +) -> bool { + // Types from the same app are always available + if referencing_app_name == defining_app_name { + return true; + } + + // Check if type is available through dependencies + if let Some(include_mapping) = &sema + .db + .project_data(referencing_app_data.project_id) + .include_mapping + { + include_mapping.is_dep(referencing_target, defining_app_name) + } else { + // Can't determine - be conservative and allow it + true + } +} + +fn check_type_call( + matches: &mut Vec>, + sema: &Semantic, + file_id: FileId, + referencing_app_data: &AppData, + referencing_target: &String, + ctx: &hir::fold::AnyCallBackCtx<'_>, + body: &hir::Body, +) -> Option<()> { + if let AnyExpr::TypeExpr(TypeExpr::Call { target, args }) = &ctx.item { + let arity = args.len() as u32; + let target_label = target.label(arity, sema, body)?; + let target_range = target.range(sema, body)?; + let type_alias_def = target.resolve_call(arity, sema, file_id, body)?; + let defining_file_id = type_alias_def.file.file_id; + let defining_app_data = sema.db.file_app_data(defining_file_id)?; + let defining_app_name = &defining_app_data.name; + let referencing_app_name = &referencing_app_data.name; + + if !is_type_available( + sema, + referencing_app_data, + referencing_target, + referencing_app_name, + defining_app_name, + ) { + matches.push(GenericLinterMatchContext { + range: target_range.range, + context: Context { + type_label: target_label.to_string(), + defining_app: defining_app_name.to_string(), + referencing_app: referencing_app_name.to_string(), + referencing_target: referencing_target.to_string(), + }, + }); + } + } + Some(()) +} diff --git a/crates/ide_db/src/diagnostic_code.rs b/crates/ide_db/src/diagnostic_code.rs index cee17251e8..957caecb09 100644 --- a/crates/ide_db/src/diagnostic_code.rs +++ b/crates/ide_db/src/diagnostic_code.rs @@ -52,6 +52,7 @@ pub enum DiagnosticCode { DependentHeader, DeprecatedFunction, UndefinedFunction, + UnavailableType, Unexpected(String), ExpressionCanBeSimplified, CannotEvaluateCTCallbacks, @@ -254,6 +255,7 @@ impl DiagnosticCode { DiagnosticCode::ListsReverseAppend => "W0056".to_string(), DiagnosticCode::HirUnresolvedMacro => "W0057".to_string(), DiagnosticCode::HirUnresolvedInclude => "W0058".to_string(), + DiagnosticCode::UnavailableType => "W0059".to_string(), DiagnosticCode::ErlangService(c) => c.to_string(), DiagnosticCode::Eqwalizer(c) => format!("eqwalizer: {c}"), DiagnosticCode::AdHoc(c) => format!("ad-hoc: {c}"), @@ -353,6 +355,7 @@ impl DiagnosticCode { DiagnosticCode::ListsReverseAppend => "lists_reverse_append".to_string(), DiagnosticCode::HirUnresolvedMacro => "hir_unresolved_macro".to_string(), DiagnosticCode::HirUnresolvedInclude => "hir_unresolved_include".to_string(), + DiagnosticCode::UnavailableType => "unavailable_type".to_string(), DiagnosticCode::ErlangService(c) => c.to_string(), DiagnosticCode::Eqwalizer(c) => c.to_string(), @@ -497,6 +500,7 @@ impl DiagnosticCode { DiagnosticCode::DependentHeader => false, DiagnosticCode::DeprecatedFunction => false, DiagnosticCode::UndefinedFunction => false, + DiagnosticCode::UnavailableType => false, DiagnosticCode::Unexpected(_) => false, DiagnosticCode::ExpressionCanBeSimplified => false, DiagnosticCode::UnnecessaryFlatteningToFindFlatLength => false, diff --git a/test_projects/xref/.elp.toml b/test_projects/xref/.elp.toml new file mode 100644 index 0000000000..afda912287 --- /dev/null +++ b/test_projects/xref/.elp.toml @@ -0,0 +1,5 @@ +[buck] +enabled = true +build_deps = false +included_targets = [ "fbcode//whatsapp/elp/test_projects/xref/..." ] +source_root = "whatsapp/elp/test_projects/xref" diff --git a/test_projects/xref/app_a/src/unavailable_type.erl b/test_projects/xref/app_a/src/unavailable_type.erl new file mode 100644 index 0000000000..d6d8f4935b --- /dev/null +++ b/test_projects/xref/app_a/src/unavailable_type.erl @@ -0,0 +1,6 @@ +-module(unavailable_type). +-export([test_types/0]). + +-spec test_types() -> {app_b:my_type_b(), app_c:my_type_c()}. +test_types() -> + {"hello", 42}. diff --git a/test_projects/xref/app_b/src/app_b.erl b/test_projects/xref/app_b/src/app_b.erl new file mode 100644 index 0000000000..d56609b179 --- /dev/null +++ b/test_projects/xref/app_b/src/app_b.erl @@ -0,0 +1,8 @@ +-module(app_b). +-export([my_function/0]). + +-type my_type_b() :: string(). +-export_type([my_type_b/0]). + +-spec my_function() -> my_type_b(). +my_function() -> "hello from b". diff --git a/test_projects/xref/app_c/src/app_c.erl b/test_projects/xref/app_c/src/app_c.erl new file mode 100644 index 0000000000..040f77f26f --- /dev/null +++ b/test_projects/xref/app_c/src/app_c.erl @@ -0,0 +1,8 @@ +-module(app_c). +-export([my_function/0]). + +-type my_type_c() :: integer(). +-export_type([my_type_c/0]). + +-spec my_function() -> my_type_c(). +my_function() -> 42. diff --git a/test_projects/xref/elp_lint_unavailable_type.toml b/test_projects/xref/elp_lint_unavailable_type.toml new file mode 100644 index 0000000000..629a5228bf --- /dev/null +++ b/test_projects/xref/elp_lint_unavailable_type.toml @@ -0,0 +1,2 @@ +[linters.unavailable_type] +enabled = true diff --git a/website/docs/erlang-error-index/w/W0059.md b/website/docs/erlang-error-index/w/W0059.md new file mode 100644 index 0000000000..fd2e990cb3 --- /dev/null +++ b/website/docs/erlang-error-index/w/W0059.md @@ -0,0 +1,37 @@ +--- +sidebar_position: 59 +--- + +# W0059: Unavailable Type + +## Error + +```erlang +-module(main). +-spec foo() -> dep:nonexistent_type(). +%% ^^^^^^^^^^^^^^^^^^^^^^ warning: W0059: Type 'dep:nonexistent_type/0' is not available through dependencies. +foo() -> ok. +``` + +## Explanation + +This diagnostic warns you when your code references a type that either: + +1. Does not exist (is undefined) +2. Is not accessible because it's from a module outside your application's dependencies +3. Exists but is not exported from the defining module + + +### What This Diagnostic Checks + +The diagnostic examines: + +- Type specifications (`-spec` attributes) +- Type definitions (`-type` and `-opaque` attributes) +- Callback specifications (`-callback` attributes) + +## How to Fix + +* Check if the type actually exists +* Verify the name and arity of the type +* Add the application that defines the type to your application's dependencies or move the type definition to an application that is already a dependency From 89ca2807407897d0d2842dcd6b8647a8177546e9 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Thu, 20 Nov 2025 01:02:30 -0800 Subject: [PATCH 029/142] Ensure that linters specified via --diagnostic-filter run, even if disabled by default Summary: It is counterintuitive that, if a linter is disabled, it does not run when `--diagnostic-code` explicitly selects it. This diff solves the issue. Reviewed By: alanz Differential Revision: D87434612 fbshipit-source-id: 7cc19544602f77e086919f0094b64120120fa7d4 --- crates/ide/src/diagnostics.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 28e39270f6..0b78998d62 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -1055,7 +1055,7 @@ impl DiagnosticsConfig { let diagnostic_filter = DiagnosticCode::from(diagnostic_filter.as_str()); // Make sure we do not mask the one we explicitly asked for disabled_diagnostics.remove(&diagnostic_filter); - allowed_diagnostics.insert(diagnostic_filter); + allowed_diagnostics.insert(diagnostic_filter.clone()); } // Make sure the enabled ones win out over disabled if a lint appears in both @@ -1073,6 +1073,13 @@ impl DiagnosticsConfig { } self.lints_from_config = lint_config.ad_hoc_lints.clone(); self.lint_config = Some(lint_config.clone()); + + // If a diagnostic_filter is specified, enable the corresponding linter in lint_config + if let Some(diagnostic_filter) = diagnostic_filter { + let diagnostic_filter_code = DiagnosticCode::from(diagnostic_filter.as_str()); + self.set_linter_enabled(diagnostic_filter_code, true); + } + self.request_erlang_service_diagnostics = self.request_erlang_service_diagnostics(); Ok(self) } @@ -4030,6 +4037,7 @@ baz(1)->4. warning() -> erlang:garbage_collect(). + %% ^^^^^^^^^^^^^^^^^^^^^^ 💡 warning: W0047: Avoid forcing garbage collection. //- /opt/lib/stdlib-3.17/src/erlang.erl otp_app:/opt/lib/stdlib-3.17 -module(erlang). -export([garbage_collect/0]). From cac1c952f9c471ca1bb17ad3d699917a52410997 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Thu, 20 Nov 2025 01:02:30 -0800 Subject: [PATCH 030/142] Extend unavailable_type diagnostic to also scan record definitions Summary: Take into account type references from record definitions when computing the `unavailable_type` diagnostic. Reviewed By: TD5 Differential Revision: D87442698 fbshipit-source-id: 982a5e815877147d17e1d472230f06859263d343 --- .../test/xref/unavailable_type.stdout | 5 +- crates/hir/src/form_list.rs | 5 ++ .../ide/src/diagnostics/unavailable_type.rs | 46 +++++++++++++++++++ .../xref/app_a/src/unavailable_type.erl | 10 +++- 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/crates/elp/src/resources/test/xref/unavailable_type.stdout b/crates/elp/src/resources/test/xref/unavailable_type.stdout index acec636118..5b71b7d2d3 100644 --- a/crates/elp/src/resources/test/xref/unavailable_type.stdout +++ b/crates/elp/src/resources/test/xref/unavailable_type.stdout @@ -1,5 +1,6 @@ Reporting all diagnostics codes module specified: unavailable_type Diagnostics reported in 1 modules: - unavailable_type: 1 - 3:42-3:57::[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'). + unavailable_type: 2 + 9:42-9:57::[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'). + 5:15-5:30::[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'). diff --git a/crates/hir/src/form_list.rs b/crates/hir/src/form_list.rs index c33af25622..a1431d7506 100644 --- a/crates/hir/src/form_list.rs +++ b/crates/hir/src/form_list.rs @@ -188,6 +188,11 @@ impl FormList { self.data.type_aliases.iter() } + /// Returns the -record declarations in the file + pub fn records(&self) -> impl Iterator { + self.data.records.iter() + } + pub fn find_form(&self, form: &ast::Form) -> Option { self.map_back.get(&AstPtr::new(form)).copied() } diff --git a/crates/ide/src/diagnostics/unavailable_type.rs b/crates/ide/src/diagnostics/unavailable_type.rs index 6a674f651b..6f2f52c122 100644 --- a/crates/ide/src/diagnostics/unavailable_type.rs +++ b/crates/ide/src/diagnostics/unavailable_type.rs @@ -24,6 +24,7 @@ use elp_project_model::AppName; use hir::AnyExpr; use hir::Callback; use hir::InFile; +use hir::Record; use hir::Semantic; use hir::Spec; use hir::Strategy; @@ -113,6 +114,18 @@ impl GenericLinter for UnavailableTypeLinter { ); } + // Check -record attributes + for (record_id, _record) in form_list.records() { + check_record( + &mut res, + sema, + file_id, + &referencing_app_data, + referencing_target, + record_id, + ); + } + Some(res) } @@ -228,6 +241,39 @@ fn check_callback( ); } +fn check_record( + matches: &mut Vec>, + sema: &Semantic, + file_id: FileId, + referencing_app_data: &AppData, + referencing_target: &String, + record_id: hir::RecordId, +) { + let record_id = InFile::new(file_id, record_id); + let record_body = sema.db.record_body(record_id); + + Record::fold( + sema, + Strategy { + macros: MacroStrategy::Expand, + parens: ParenStrategy::InvisibleParens, + }, + record_id, + (), + &mut |_acc, ctx| { + check_type_call( + matches, + sema, + file_id, + referencing_app_data, + referencing_target, + &ctx, + &record_body.body, + ); + }, + ); +} + fn is_type_available( sema: &Semantic, referencing_app_data: &AppData, diff --git a/test_projects/xref/app_a/src/unavailable_type.erl b/test_projects/xref/app_a/src/unavailable_type.erl index d6d8f4935b..ddf4fc00d2 100644 --- a/test_projects/xref/app_a/src/unavailable_type.erl +++ b/test_projects/xref/app_a/src/unavailable_type.erl @@ -1,6 +1,12 @@ -module(unavailable_type). -export([test_types/0]). --spec test_types() -> {app_b:my_type_b(), app_c:my_type_c()}. +-record(my_record, { + field_a :: app_b:my_type_b(), + field_b :: app_c:my_type_c(), + field_c :: binary() +}). + +-spec test_types() -> {app_b:my_type_b(), app_c:my_type_c(), #my_record{}}. test_types() -> - {"hello", 42}. + {"hello", 42, #my_record{field_a = "", field_b = 42, field_c = <<>>}}. From 74ed6b2de493365ff5ad1a96f383c25e8f2b16de Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Thu, 20 Nov 2025 01:02:30 -0800 Subject: [PATCH 031/142] Convert no_dialyzer_attribute linter to use a trait Summary: Simple conversion to use the new trait. Reviewed By: TD5 Differential Revision: D87079003 fbshipit-source-id: 7caaf84bf43882045637fe870046537e5eb82a5f --- crates/ide/src/diagnostics.rs | 2 +- .../src/diagnostics/no_dialyzer_attribute.rs | 73 ++++++++++--------- website/docs/erlang-error-index/w/W0048.md | 2 +- 3 files changed, 41 insertions(+), 36 deletions(-) diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 0b78998d62..6630dcf067 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -1511,7 +1511,6 @@ pub fn diagnostics_descriptors<'a>() -> Vec<&'a DiagnosticDescriptor<'a>> { &edoc::DESCRIPTOR, ¯o_precedence_suprise::DESCRIPTOR, &duplicate_module::DESCRIPTOR, - &no_dialyzer_attribute::DESCRIPTOR, &no_nowarn_suppressions::DESCRIPTOR, ] } @@ -1595,6 +1594,7 @@ const GENERIC_LINTERS: &[&dyn GenericDiagnostics] = &[ &undocumented_function::LINTER, &no_catch::LINTER, &unavailable_type::LINTER, + &no_dialyzer_attribute::LINTER, ]; /// Unified registry for all types of linters diff --git a/crates/ide/src/diagnostics/no_dialyzer_attribute.rs b/crates/ide/src/diagnostics/no_dialyzer_attribute.rs index 4c501c5ad5..9a671e6c65 100644 --- a/crates/ide/src/diagnostics/no_dialyzer_attribute.rs +++ b/crates/ide/src/diagnostics/no_dialyzer_attribute.rs @@ -8,50 +8,55 @@ * above-listed licenses. */ -use elp_ide_db::DiagnosticCode; +// Diagnostic: no-dialyzer-attribute use elp_ide_db::elp_base_db::FileId; use hir::Semantic; use hir::known; -use crate::diagnostics::Diagnostic; -use crate::diagnostics::DiagnosticConditions; -use crate::diagnostics::DiagnosticDescriptor; -use crate::diagnostics::Severity; +use crate::diagnostics::DiagnosticCode; +use crate::diagnostics::GenericLinter; +use crate::diagnostics::GenericLinterMatchContext; +use crate::diagnostics::Linter; -const DIAGNOSTIC_CODE: DiagnosticCode = DiagnosticCode::NoDialyzerAttribute; -const DIAGNOSTIC_MESSAGE: &str = "Avoid -dialyzer attribute."; -const DIAGNOSTIC_SEVERITY: Severity = Severity::Warning; +pub(crate) struct NoDialyzerAttributeLinter; -pub(crate) static DESCRIPTOR: DiagnosticDescriptor = DiagnosticDescriptor { - conditions: DiagnosticConditions { - experimental: false, - include_generated: false, - include_tests: false, - default_disabled: false, - }, - checker: &|diags, sema, file_id, _ext| { - no_dialyzer_attribute(diags, sema, file_id); - }, -}; +impl Linter for NoDialyzerAttributeLinter { + fn id(&self) -> DiagnosticCode { + DiagnosticCode::NoDialyzerAttribute + } -fn no_dialyzer_attribute(diagnostics: &mut Vec, sema: &Semantic, file_id: FileId) { - let form_list = sema.db.file_form_list(file_id); - form_list.attributes().for_each(|(_idx, attr)| { - if attr.name == known::dialyzer - && let Some(diagnostic) = make_diagnostic(sema, file_id, attr) - { - diagnostics.push(diagnostic); - } - }); + fn description(&self) -> &'static str { + "Avoid using the -dialyzer attribute." + } + + fn should_process_test_files(&self) -> bool { + false + } } -fn make_diagnostic(sema: &Semantic, file_id: FileId, attr: &hir::Attribute) -> Option { - let range = attr.name_range(sema.db, file_id)?; - let diagnostic = Diagnostic::new(DIAGNOSTIC_CODE, DIAGNOSTIC_MESSAGE, range) - .with_severity(DIAGNOSTIC_SEVERITY); - Some(diagnostic) +impl GenericLinter for NoDialyzerAttributeLinter { + type Context = (); + + fn matches( + &self, + sema: &Semantic, + file_id: FileId, + ) -> Option>> { + let mut res = Vec::new(); + let form_list = sema.db.file_form_list(file_id); + form_list.attributes().for_each(|(_idx, attr)| { + if attr.name == known::dialyzer + && let Some(range) = attr.name_range(sema.db, file_id) + { + res.push(GenericLinterMatchContext { range, context: () }) + } + }); + Some(res) + } } +pub static LINTER: NoDialyzerAttributeLinter = NoDialyzerAttributeLinter; + #[cfg(test)] mod tests { @@ -63,7 +68,7 @@ mod tests { r#" -module(main). -dialyzer({nowarn_function, foo/0}). - %% ^^^^^^^^^ warning: W0048: Avoid -dialyzer attribute. + %% ^^^^^^^^^ 💡 warning: W0048: Avoid using the -dialyzer attribute. "#, ) } diff --git a/website/docs/erlang-error-index/w/W0048.md b/website/docs/erlang-error-index/w/W0048.md index 42d21cec9c..a6e1631148 100644 --- a/website/docs/erlang-error-index/w/W0048.md +++ b/website/docs/erlang-error-index/w/W0048.md @@ -9,7 +9,7 @@ sidebar_position: 48 ```erlang -module(main). -dialyzer({nowarn_function, foo/0}). -%% ^^^^^^^^^ 💡 warning: Avoid -dialyzer attribute. +%% ^^^^^^^^^ 💡 warning: Avoid using the -dialyzer attribute. ``` ## Explanation From 5187bf3d3f367c11fefa6f29ad9041b6d72e7187 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Thu, 20 Nov 2025 01:02:30 -0800 Subject: [PATCH 032/142] Convert duplicate_module linter to use a trait Summary: Simple conversion to use the new trait. Reviewed By: TD5 Differential Revision: D87445553 fbshipit-source-id: 8b45cea1dcefe71b0cd573aa4529433974943d3f --- crates/ide/src/diagnostics.rs | 2 +- .../ide/src/diagnostics/duplicate_module.rs | 81 ++++++++++--------- website/docs/erlang-error-index/w/W0045.md | 2 +- 3 files changed, 46 insertions(+), 39 deletions(-) diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 6630dcf067..59a17db167 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -1510,7 +1510,6 @@ pub fn diagnostics_descriptors<'a>() -> Vec<&'a DiagnosticDescriptor<'a>> { &unspecific_include::DESCRIPTOR, &edoc::DESCRIPTOR, ¯o_precedence_suprise::DESCRIPTOR, - &duplicate_module::DESCRIPTOR, &no_nowarn_suppressions::DESCRIPTOR, ] } @@ -1595,6 +1594,7 @@ const GENERIC_LINTERS: &[&dyn GenericDiagnostics] = &[ &no_catch::LINTER, &unavailable_type::LINTER, &no_dialyzer_attribute::LINTER, + &duplicate_module::LINTER, ]; /// Unified registry for all types of linters diff --git a/crates/ide/src/diagnostics/duplicate_module.rs b/crates/ide/src/diagnostics/duplicate_module.rs index d9e01ecc53..09eb548b31 100644 --- a/crates/ide/src/diagnostics/duplicate_module.rs +++ b/crates/ide/src/diagnostics/duplicate_module.rs @@ -12,51 +12,58 @@ // // Return a warning if more than one module has the same name -use elp_ide_db::DiagnosticCode; use elp_ide_db::elp_base_db::FileId; use elp_ide_db::elp_base_db::ModuleName; use elp_syntax::AstNode; use hir::Semantic; -use super::Diagnostic; -use super::DiagnosticConditions; -use super::DiagnosticDescriptor; -use crate::diagnostics::Severity; +use crate::diagnostics::DiagnosticCode; +use crate::diagnostics::GenericLinter; +use crate::diagnostics::GenericLinterMatchContext; +use crate::diagnostics::Linter; -pub(crate) static DESCRIPTOR: DiagnosticDescriptor = DiagnosticDescriptor { - conditions: DiagnosticConditions { - experimental: false, - include_generated: false, - include_tests: false, // Allow duplication in test fixtures - default_disabled: false, - }, - checker: &|diags, sema, file_id, _file_kind| { - check_file(diags, sema, &file_id); - }, -}; +pub(crate) struct DuplicateModuleLinter; -fn check_file(acc: &mut Vec, sema: &Semantic, file_id: &FileId) -> Option<()> { - // We cannot ask for the module name from the module_index, as - // this file_id may be discarded as a duplicate - let module_name_ast = sema.module_attribute_name(*file_id)?; - let module_name = ModuleName::new(module_name_ast.text()?.as_str()); - - let app_data = sema.db.file_app_data(*file_id)?; - let module_index = sema.db.module_index(app_data.project_id); - if let Some(_dups) = module_index.duplicates(&module_name) { - let range = module_name_ast.syntax().text_range(); - acc.push( - Diagnostic::new( - DiagnosticCode::DuplicateModule, - "Duplicate module name", - range, - ) - .with_severity(Severity::Warning), - ); +impl Linter for DuplicateModuleLinter { + fn id(&self) -> DiagnosticCode { + DiagnosticCode::DuplicateModule + } + + fn description(&self) -> &'static str { + "A module with this name exists elsewhere" + } + + fn should_process_test_files(&self) -> bool { + false // Allow duplication in test fixtures } - Some(()) } +impl GenericLinter for DuplicateModuleLinter { + type Context = (); + + fn matches( + &self, + sema: &Semantic, + file_id: FileId, + ) -> Option>> { + let mut res = Vec::new(); + // We cannot ask for the module name from the module_index, as + // this file_id may be discarded as a duplicate + let module_name_ast = sema.module_attribute_name(file_id)?; + let module_name = ModuleName::new(module_name_ast.text()?.as_str()); + + let app_data = sema.db.file_app_data(file_id)?; + let module_index = sema.db.module_index(app_data.project_id); + if let Some(_dups) = module_index.duplicates(&module_name) { + let range = module_name_ast.syntax().text_range(); + res.push(GenericLinterMatchContext { range, context: () }); + } + Some(res) + } +} + +pub static LINTER: DuplicateModuleLinter = DuplicateModuleLinter; + #[cfg(test)] mod test { use crate::tests::check_diagnostics; @@ -67,11 +74,11 @@ mod test { r#" //- /src/dup_mod.erl -module(dup_mod). - %% ^^^^^^^ warning: W0045: Duplicate module name + %% ^^^^^^^ 💡 warning: W0045: A module with this name exists elsewhere //- /src/sub/dup_mod.erl -module(dup_mod). - %% ^^^^^^^ warning: W0045: Duplicate module name + %% ^^^^^^^ 💡 warning: W0045: A module with this name exists elsewhere "#, ) } diff --git a/website/docs/erlang-error-index/w/W0045.md b/website/docs/erlang-error-index/w/W0045.md index 13c908d8a0..c68f83ef4d 100644 --- a/website/docs/erlang-error-index/w/W0045.md +++ b/website/docs/erlang-error-index/w/W0045.md @@ -8,7 +8,7 @@ sidebar_position: 45 ```erlang -module(my_module). - %% ^^^^^^^^^ warning: Duplicate module name + %% ^^^^^^^^^ warning: A module with this name exists elsewhere ``` ## Explanation From e9a9035e5ad8379e59167283d4aa8ae897bc5364 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Thu, 20 Nov 2025 04:38:57 -0800 Subject: [PATCH 033/142] Convert no_warn_suppressions linter to use a trait Summary: Simple conversion to use the new trait. Reviewed By: jcpetruzza Differential Revision: D87447644 fbshipit-source-id: 74e0caf1fc8fd289013af2fc69839b25f0b9bdc4 --- crates/ide/src/diagnostics.rs | 2 +- .../src/diagnostics/no_nowarn_suppressions.rs | 89 ++++++++++--------- 2 files changed, 46 insertions(+), 45 deletions(-) diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 59a17db167..68197dfa9b 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -1510,7 +1510,6 @@ pub fn diagnostics_descriptors<'a>() -> Vec<&'a DiagnosticDescriptor<'a>> { &unspecific_include::DESCRIPTOR, &edoc::DESCRIPTOR, ¯o_precedence_suprise::DESCRIPTOR, - &no_nowarn_suppressions::DESCRIPTOR, ] } @@ -1595,6 +1594,7 @@ const GENERIC_LINTERS: &[&dyn GenericDiagnostics] = &[ &unavailable_type::LINTER, &no_dialyzer_attribute::LINTER, &duplicate_module::LINTER, + &no_nowarn_suppressions::LINTER, ]; /// Unified registry for all types of linters diff --git a/crates/ide/src/diagnostics/no_nowarn_suppressions.rs b/crates/ide/src/diagnostics/no_nowarn_suppressions.rs index ef258779cd..a0b36f87b8 100644 --- a/crates/ide/src/diagnostics/no_nowarn_suppressions.rs +++ b/crates/ide/src/diagnostics/no_nowarn_suppressions.rs @@ -16,62 +16,63 @@ use hir::Semantic; use lazy_static::lazy_static; use regex::Regex; -use crate::diagnostics::Diagnostic; use crate::diagnostics::DiagnosticCode; -use crate::diagnostics::DiagnosticConditions; -use crate::diagnostics::DiagnosticDescriptor; -use crate::diagnostics::Severity; +use crate::diagnostics::GenericLinter; +use crate::diagnostics::GenericLinterMatchContext; +use crate::diagnostics::Linter; -const DIAGNOSTIC_CODE: DiagnosticCode = DiagnosticCode::NoNoWarnSuppressions; -const DIAGNOSTIC_MESSAGE: &str = "Do not suppress compiler warnings at module level."; -const DIAGNOSTIC_SEVERITY: Severity = Severity::Warning; +pub(crate) struct NoNoWarnSuppressionsLinter; -pub(crate) static DESCRIPTOR: DiagnosticDescriptor = DiagnosticDescriptor { - conditions: DiagnosticConditions { - experimental: false, - include_generated: false, - include_tests: true, - default_disabled: false, - }, - checker: &|diags, sema, file_id, _ext| { - no_warn_suppression(diags, sema, file_id); - }, -}; - -fn no_warn_suppression(diagnostics: &mut Vec, sema: &Semantic, file_id: FileId) { - lazy_static! { - static ref NOWARN_REGEX: Regex = Regex::new(r"^nowarn_").unwrap(); +impl Linter for NoNoWarnSuppressionsLinter { + fn id(&self) -> DiagnosticCode { + DiagnosticCode::NoNoWarnSuppressions } - let form_list = sema.db.file_form_list(file_id); - for (_compile_option_idx, compile_option) in form_list.compile_attributes() { - let attr = compile_option.form_id.get_ast(sema.db, file_id); - if let Some(expr) = attr.options() { - // Blindly search for any atom matching the nowarn_ prefix - for n in expr.syntax().descendants() { - match_ast! { - match n { - ast::Atom(atom) => { - if let Some(atom_text) = atom.text() - && NOWARN_REGEX.is_match(&atom_text) { - let diagnostic = Diagnostic::new( - DIAGNOSTIC_CODE, - DIAGNOSTIC_MESSAGE, - atom.syntax().text_range(), - ) - .with_ignore_fix(sema, file_id) - .with_severity(DIAGNOSTIC_SEVERITY); - diagnostics.push(diagnostic); - } - }, - _ => {} + fn description(&self) -> &'static str { + "Do not suppress compiler warnings at module level." + } +} + +impl GenericLinter for NoNoWarnSuppressionsLinter { + type Context = (); + + fn matches( + &self, + sema: &Semantic, + file_id: FileId, + ) -> Option>> { + lazy_static! { + static ref NOWARN_REGEX: Regex = Regex::new(r"^nowarn_").unwrap(); + } + + let mut res = Vec::new(); + let form_list = sema.db.file_form_list(file_id); + for (_compile_option_idx, compile_option) in form_list.compile_attributes() { + let attr = compile_option.form_id.get_ast(sema.db, file_id); + if let Some(expr) = attr.options() { + // Blindly search for any atom matching the nowarn_ prefix + for n in expr.syntax().descendants() { + match_ast! { + match n { + ast::Atom(atom) => { + if let Some(atom_text) = atom.text() + && NOWARN_REGEX.is_match(&atom_text) { + let range = atom.syntax().text_range(); + res.push(GenericLinterMatchContext { range, context: () }); + } + }, + _ => {} + } } } } } + Some(res) } } +pub static LINTER: NoNoWarnSuppressionsLinter = NoNoWarnSuppressionsLinter; + #[cfg(test)] mod tests { use crate::tests; From 77ff228aae33bea6ba4ccb9b83c4d83c273a8529 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Thu, 20 Nov 2025 04:38:57 -0800 Subject: [PATCH 034/142] Convert macro_precedence_surprise linter to use a trait Summary: Simple conversion to use the new trait. Reviewed By: jcpetruzza Differential Revision: D87516327 fbshipit-source-id: 8a6669c4c337a54ecf0a65c864df8be40d82e720 --- crates/ide/src/diagnostics.rs | 2 +- .../diagnostics/macro_precedence_suprise.rs | 172 ++++++++++-------- 2 files changed, 94 insertions(+), 80 deletions(-) diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 68197dfa9b..6522455018 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -1509,7 +1509,6 @@ pub fn diagnostics_descriptors<'a>() -> Vec<&'a DiagnosticDescriptor<'a>> { &record_tuple_match::DESCRIPTOR, &unspecific_include::DESCRIPTOR, &edoc::DESCRIPTOR, - ¯o_precedence_suprise::DESCRIPTOR, ] } @@ -1595,6 +1594,7 @@ const GENERIC_LINTERS: &[&dyn GenericDiagnostics] = &[ &no_dialyzer_attribute::LINTER, &duplicate_module::LINTER, &no_nowarn_suppressions::LINTER, + ¯o_precedence_suprise::LINTER, ]; /// Unified registry for all types of linters diff --git a/crates/ide/src/diagnostics/macro_precedence_suprise.rs b/crates/ide/src/diagnostics/macro_precedence_suprise.rs index 49c627da28..700f54441e 100644 --- a/crates/ide/src/diagnostics/macro_precedence_suprise.rs +++ b/crates/ide/src/diagnostics/macro_precedence_suprise.rs @@ -15,14 +15,12 @@ use elp_ide_assists::Assist; use elp_ide_assists::helpers::add_parens_edit; -use elp_ide_db::DiagnosticCode; use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChange; use elp_text_edit::TextRange; use hir::AnyExpr; use hir::AnyExprRef; use hir::Expr; -use hir::ExprSource; use hir::Semantic; use hir::Strategy; use hir::fold::MacroStrategy; @@ -30,92 +28,108 @@ use hir::fold::ParenStrategy; use hir::fold::ParentId; use hir::fold::fold_file_functions; -use super::Diagnostic; -use super::DiagnosticConditions; -use super::DiagnosticDescriptor; -use super::Severity; +use crate::diagnostics::DiagnosticCode; +use crate::diagnostics::GenericLinter; +use crate::diagnostics::GenericLinterMatchContext; +use crate::diagnostics::Linter; use crate::fix; -pub(crate) static DESCRIPTOR: DiagnosticDescriptor = DiagnosticDescriptor { - conditions: DiagnosticConditions { - experimental: false, - include_generated: true, - include_tests: true, - default_disabled: false, - }, - checker: &|diags, sema, file_id, _file_kind| { - check_file(diags, sema, &file_id); - }, -}; - -/// Look for macro expansions. If we find one, check what the top-level expansion gives us. -fn check_file(acc: &mut Vec, sema: &Semantic, file_id: &FileId) { - let fold_strategy = Strategy { - macros: MacroStrategy::ExpandButIncludeMacroCall, - parens: ParenStrategy::VisibleParens, - }; - let index_strategy = Strategy { - macros: MacroStrategy::Expand, - parens: ParenStrategy::VisibleParens, - }; - - fold_file_functions(sema, fold_strategy, *file_id, (), &mut |_acc, ctx| { - if let AnyExpr::Expr(Expr::MacroCall { expansion, .. }) = &ctx.item - && let Some((body, _body_map, ast)) = ctx.body_with_expr_source(sema) - { - let visible_parens_body = body.index_with_strategy(index_strategy); - if let Expr::BinaryOp { .. } = &visible_parens_body[*expansion] - && let ParentId::HirIdx(hir_idx) = ctx.parent() - && hir_idx.body_origin == ctx.body_origin - { - // We can have nested macro - // calls, which are not - // visible in the - // visible_parens_body. Report - // on the top-level one only. - let fold_body = body.index_with_strategy(fold_strategy); - match &fold_body.get_any(hir_idx.idx) { - AnyExprRef::Expr(Expr::MacroCall { .. }) => {} - _ => { - if let AnyExprRef::Expr(Expr::BinaryOp { .. }) = - &visible_parens_body.get_any(hir_idx.idx) - { - make_diagnostic(acc, file_id, ast); - } - } - }; - }; - } - }); +#[derive(Debug, Default, Clone, PartialEq)] +pub(crate) struct MacroPrecedenceContext { + range: TextRange, } -fn make_diagnostic(acc: &mut Vec, file_id: &FileId, ast: ExprSource) { - let range = ast.range(); - if range.file_id == *file_id { - let fix = add_parens_fix(*file_id, &range.range); - acc.push( - Diagnostic::new( - DiagnosticCode::MacroPrecedenceEscape, - "The macro expansion can have unexpected precedence here", - range.range, - ) - .with_severity(Severity::Warning) - .with_fixes(Some(vec![fix])), - ); +pub(crate) struct MacroPrecedenceSupriseLinter; + +impl Linter for MacroPrecedenceSupriseLinter { + fn id(&self) -> DiagnosticCode { + DiagnosticCode::MacroPrecedenceEscape + } + + fn description(&self) -> &'static str { + "The macro expansion can have unexpected precedence here" + } + + fn should_process_generated_files(&self) -> bool { + true } } -fn add_parens_fix(file_id: FileId, range: &TextRange) -> Assist { - let assist_message = "Add parens to macro call".to_string(); - let edit = add_parens_edit(range); - fix( - "macro_precedence_add_parens", - &assist_message, - SourceChange::from_text_edit(file_id, edit.clone()), - *range, - ) +impl GenericLinter for MacroPrecedenceSupriseLinter { + type Context = MacroPrecedenceContext; + + fn matches( + &self, + sema: &Semantic, + file_id: FileId, + ) -> Option>> { + let fold_strategy = Strategy { + macros: MacroStrategy::ExpandButIncludeMacroCall, + parens: ParenStrategy::VisibleParens, + }; + let index_strategy = Strategy { + macros: MacroStrategy::Expand, + parens: ParenStrategy::VisibleParens, + }; + + let mut res = Vec::new(); + fold_file_functions(sema, fold_strategy, file_id, (), &mut |_acc, ctx| { + if let AnyExpr::Expr(Expr::MacroCall { expansion, .. }) = &ctx.item + && let Some((body, _body_map, ast)) = ctx.body_with_expr_source(sema) + { + let visible_parens_body = body.index_with_strategy(index_strategy); + if let Expr::BinaryOp { .. } = &visible_parens_body[*expansion] + && let ParentId::HirIdx(hir_idx) = ctx.parent() + && hir_idx.body_origin == ctx.body_origin + { + // We can have nested macro + // calls, which are not + // visible in the + // visible_parens_body. Report + // on the top-level one only. + let fold_body = body.index_with_strategy(fold_strategy); + match &fold_body.get_any(hir_idx.idx) { + AnyExprRef::Expr(Expr::MacroCall { .. }) => {} + _ => { + if let AnyExprRef::Expr(Expr::BinaryOp { .. }) = + &visible_parens_body.get_any(hir_idx.idx) + { + let range = ast.range(); + if range.file_id == file_id { + let context = MacroPrecedenceContext { range: range.range }; + res.push(GenericLinterMatchContext { + range: range.range, + context, + }); + } + } + } + }; + }; + } + }); + Some(res) + } + + fn fixes( + &self, + context: &Self::Context, + _sema: &Semantic, + file_id: FileId, + ) -> Option> { + let edit = add_parens_edit(&context.range); + let fix = fix( + "macro_precedence_add_parens", + "Add parens to macro call", + SourceChange::from_text_edit(file_id, edit), + context.range, + ); + Some(vec![fix]) + } } +pub static LINTER: MacroPrecedenceSupriseLinter = MacroPrecedenceSupriseLinter; + #[cfg(test)] mod tests { From 7571e571553e0ef40fabcf034ab123ec3f7da876 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Thu, 20 Nov 2025 07:30:38 -0800 Subject: [PATCH 035/142] BE: fix off-by-one in `elp lint` diagnostic output Summary: When running any elp CLI command that produces diagnostics, the locations are incorrect, as they use the internal coordinate frame rather than the usual one for IDEs and other tools. This means each line and col location is one less than it should be. This diff fixes that, so the locations make sense to users. Note: it does not affect output using `--format json`, which already does this. Reviewed By: TD5 Differential Revision: D87539540 fbshipit-source-id: 27abd4ac5fb9e5056fc9e3397257d9e5cb9e678d --- crates/elp/src/bin/lint_cli.rs | 12 +++---- .../resolves_generated_includes.stdout | 14 ++++---- .../diagnostics/lint_report_suppressed.stdout | 2 +- .../diagnostics/parse_elp_lint_fix.stdout | 4 +-- .../parse_elp_lint_recursive.stdout | 32 +++++++++---------- .../linter/custom_function_matches.stdout | 2 +- .../resources/test/linter/elp_lint_ct.stdout | 2 +- .../test/linter/elp_lint_edoc.stdout | 2 +- .../test/linter/parse_elp_lint2.stdout | 2 +- .../linter/parse_elp_lint_adhoc_output.stdout | 8 ++--- .../test/linter/parse_elp_lint_app.stdout | 2 +- .../parse_elp_lint_config_output.stdout | 16 +++++----- ...parse_elp_lint_custom_config_output.stdout | 8 ++--- .../parse_elp_lint_erlang_service.stdout | 2 +- ...rse_elp_lint_explicit_enable_output.stdout | 22 ++++++------- .../linter/parse_elp_lint_fix_ignore.stdout | 4 +-- .../parse_elp_lint_fixme_spelling.stdout | 4 +-- .../test/linter/parse_elp_lint_ignore.stdout | 10 +++--- .../linter/parse_elp_lint_ignore_apps.stdout | 2 +- .../parse_elp_lint_ignore_apps_b.stdout | 4 +-- .../parse_elp_no_lint_specified_output.stdout | 30 ++++++++--------- .../resources/test/linter/ssr_ad_hoc.stdout | 2 +- .../test/linter/ssr_ad_hoc_cli.stdout | 2 +- .../ssr_ad_hoc_cli_macros_expand.stdout | 2 +- .../ssr_ad_hoc_cli_macros_no_expand.stdout | 2 +- ...sr_ad_hoc_cli_macros_visible_expand.stdout | 4 +-- .../linter/ssr_ad_hoc_cli_multiple.stdout | 4 +-- .../ssr_ad_hoc_cli_parens_invisible.stdout | 2 +- .../ssr_ad_hoc_cli_parens_visible.stdout | 6 ++-- .../test/linter/warnings_as_errors.stdout | 30 ++++++++--------- .../test/xref/unavailable_type.stdout | 4 +-- crates/ide/src/diagnostics.rs | 8 ++--- 32 files changed, 125 insertions(+), 125 deletions(-) diff --git a/crates/elp/src/bin/lint_cli.rs b/crates/elp/src/bin/lint_cli.rs index 4ddf80b5c2..3bc2271072 100644 --- a/crates/elp/src/bin/lint_cli.rs +++ b/crates/elp/src/bin/lint_cli.rs @@ -1088,11 +1088,11 @@ mod tests { head_mismatcX(0) -> 0. "#, expect![[r#" - module specified: lints - Diagnostics reported in 1 modules: - lints: 1 - 4:2-4:15::[Error] [P1700] head mismatch 'head_mismatcX' vs 'head_mismatch' - "#]], + module specified: lints + Diagnostics reported in 1 modules: + lints: 1 + 5:3-5:16::[Error] [P1700] head mismatch 'head_mismatcX' vs 'head_mismatch' + "#]], expect![""], ); } @@ -1111,7 +1111,7 @@ mod tests { module specified: lints Diagnostics reported in 1 modules: lints: 1 - 2:2-2:5::[Warning] [L1230] function foo/0 is unused + 3:3-3:6::[Warning] [L1230] function foo/0 is unused "#]], expect![""], ); diff --git a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout index 83f167f14a..378f86c01e 100644 --- a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout +++ b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout @@ -1,12 +1,12 @@ Reporting all diagnostics codes Diagnostics reported in 3 modules: app_a: 1 - 5:9-5:31::[WeakWarning] [W0037] Unspecific include. + 6:10-6:32::[WeakWarning] [W0037] Unspecific include. top_includer: 2 - 0:1-0:1::[Error] [L0000] Issue in included file - 12:4-12:10::[Error] [E1508] undefined macro 'THIRD/2' + 1:2-1:2::[Error] [L0000] Issue in included file + 13:5-13:11::[Error] [E1508] undefined macro 'THIRD/2' wa_buck2_module_search: 4 - 54:18-54:31::[WeakWarning] [W0051] Binary string can be written using sigil syntax. - 56:18-56:28::[WeakWarning] [W0051] Binary string can be written using sigil syntax. - 56:38-56:51::[WeakWarning] [W0051] Binary string can be written using sigil syntax. - 60:30-60:40::[WeakWarning] [W0051] Binary string can be written using sigil syntax. + 55:19-55:32::[WeakWarning] [W0051] Binary string can be written using sigil syntax. + 57:19-57:29::[WeakWarning] [W0051] Binary string can be written using sigil syntax. + 57:39-57:52::[WeakWarning] [W0051] Binary string can be written using sigil syntax. + 61:31-61:41::[WeakWarning] [W0051] Binary string can be written using sigil syntax. diff --git a/crates/elp/src/resources/test/diagnostics/lint_report_suppressed.stdout b/crates/elp/src/resources/test/diagnostics/lint_report_suppressed.stdout index c8cdedb3ea..cda6044675 100644 --- a/crates/elp/src/resources/test/diagnostics/lint_report_suppressed.stdout +++ b/crates/elp/src/resources/test/diagnostics/lint_report_suppressed.stdout @@ -1,4 +1,4 @@ module specified: suppressed Diagnostics reported in 1 modules: suppressed: 1 - 7:4-7:8::[Warning] [W0007] match is redundant + 8:5-8:9::[Warning] [W0007] match is redundant diff --git a/crates/elp/src/resources/test/diagnostics/parse_elp_lint_fix.stdout b/crates/elp/src/resources/test/diagnostics/parse_elp_lint_fix.stdout index b3a5f365b7..5cee41edd5 100644 --- a/crates/elp/src/resources/test/diagnostics/parse_elp_lint_fix.stdout +++ b/crates/elp/src/resources/test/diagnostics/parse_elp_lint_fix.stdout @@ -1,11 +1,11 @@ module specified: lints Diagnostics reported in 1 modules: lints: 1 - 4:0-4:13::[Error] [P1700] head mismatch 'head_mismatcX' vs 'head_mismatch' + 5:1-5:14::[Error] [P1700] head mismatch 'head_mismatcX' vs 'head_mismatch' --------------------------------------------- Applying fix in module 'lints' for - 4:0-4:13::[Error] [P1700] head mismatch 'head_mismatcX' vs 'head_mismatch' + 5:1-5:14::[Error] [P1700] head mismatch 'head_mismatcX' vs 'head_mismatch' @@ -1,6 +1,6 @@ -module(lints). -export([head_mismatch/1]). diff --git a/crates/elp/src/resources/test/diagnostics/parse_elp_lint_recursive.stdout b/crates/elp/src/resources/test/diagnostics/parse_elp_lint_recursive.stdout index 6bcfecb8a6..d947376488 100644 --- a/crates/elp/src/resources/test/diagnostics/parse_elp_lint_recursive.stdout +++ b/crates/elp/src/resources/test/diagnostics/parse_elp_lint_recursive.stdout @@ -1,12 +1,12 @@ module specified: lint_recursive Diagnostics reported in 1 modules: lint_recursive: 2 - 18:4-18:11::[Warning] [W0007] match is redundant - 13:4-13:11::[Warning] [W0007] match is redundant + 19:5-19:12::[Warning] [W0007] match is redundant + 14:5-14:12::[Warning] [W0007] match is redundant --------------------------------------------- Applying fix in module 'lint_recursive' for - 18:4-18:11::[Warning] [W0007] match is redundant + 19:5-19:12::[Warning] [W0007] match is redundant @@ -16,7 +16,7 @@ test_foo2(Config) -> @@ -21,12 +21,12 @@ Applying fix in module 'lint_recursive' for New filtered diagnostics lint_recursive: 2 - 13:4-13:11::[Warning] [W0007] match is redundant - 18:4-18:10::[Warning] [W0006] this statement has no effect + 14:5-14:12::[Warning] [W0007] match is redundant + 19:5-19:11::[Warning] [W0006] this statement has no effect --------------------------------------------- Applying fix in module 'lint_recursive' for - 13:4-13:11::[Warning] [W0007] match is redundant + 14:5-14:12::[Warning] [W0007] match is redundant @@ -11,7 +11,7 @@ %% something/0. test_foo(Config) -> @@ -41,12 +41,12 @@ Applying fix in module 'lint_recursive' for New filtered diagnostics lint_recursive: 2 - 18:4-18:10::[Warning] [W0006] this statement has no effect - 13:4-13:10::[Warning] [W0006] this statement has no effect + 19:5-19:11::[Warning] [W0006] this statement has no effect + 14:5-14:11::[Warning] [W0006] this statement has no effect --------------------------------------------- Applying fix in module 'lint_recursive' for - 18:4-18:10::[Warning] [W0006] this statement has no effect + 19:5-19:11::[Warning] [W0006] this statement has no effect @@ -16,7 +16,6 @@ test_foo2(Config) -> @@ -60,12 +60,12 @@ Applying fix in module 'lint_recursive' for New filtered diagnostics lint_recursive: 2 - 16:10-16:16::[Warning] [W0010] this variable is unused - 13:4-13:10::[Warning] [W0006] this statement has no effect + 17:11-17:17::[Warning] [W0010] this variable is unused + 14:5-14:11::[Warning] [W0006] this statement has no effect --------------------------------------------- Applying fix in module 'lint_recursive' for - 16:10-16:16::[Warning] [W0010] this variable is unused + 17:11-17:17::[Warning] [W0010] this variable is unused @@ -14,7 +14,7 @@ Config, clean_mocks(). @@ -80,11 +80,11 @@ Applying fix in module 'lint_recursive' for New filtered diagnostics lint_recursive: 1 - 13:4-13:10::[Warning] [W0006] this statement has no effect + 14:5-14:11::[Warning] [W0006] this statement has no effect --------------------------------------------- Applying fix in module 'lint_recursive' for - 13:4-13:10::[Warning] [W0006] this statement has no effect + 14:5-14:11::[Warning] [W0006] this statement has no effect @@ -11,7 +11,6 @@ %% something/0. test_foo(Config) -> @@ -98,11 +98,11 @@ Applying fix in module 'lint_recursive' for New filtered diagnostics lint_recursive: 1 - 11:9-11:15::[Warning] [W0010] this variable is unused + 12:10-12:16::[Warning] [W0010] this variable is unused --------------------------------------------- Applying fix in module 'lint_recursive' for - 11:9-11:15::[Warning] [W0010] this variable is unused + 12:10-12:16::[Warning] [W0010] this variable is unused @@ -9,7 +9,7 @@ %% We want to check that the "no effect" statements in test_foo/1 and %% test_foo2/1 are removed, but not the ones in clean_mocks/0 and diff --git a/crates/elp/src/resources/test/linter/custom_function_matches.stdout b/crates/elp/src/resources/test/linter/custom_function_matches.stdout index 08c0321771..961154e550 100644 --- a/crates/elp/src/resources/test/linter/custom_function_matches.stdout +++ b/crates/elp/src/resources/test/linter/custom_function_matches.stdout @@ -2,4 +2,4 @@ Reporting all diagnostics codes module specified: custom_function_matches Diagnostics reported in 1 modules: custom_function_matches: 1 - 13:4-13:25::[Warning] [W0017] Function 'not_excluded:function/0' is undefined. + 14:5-14:26::[Warning] [W0017] Function 'not_excluded:function/0' is undefined. diff --git a/crates/elp/src/resources/test/linter/elp_lint_ct.stdout b/crates/elp/src/resources/test/linter/elp_lint_ct.stdout index db2b6e3afa..773b1f4d9c 100644 --- a/crates/elp/src/resources/test/linter/elp_lint_ct.stdout +++ b/crates/elp/src/resources/test/linter/elp_lint_ct.stdout @@ -1,3 +1,3 @@ Diagnostics reported in 1 modules: app_a_unreachable_test_SUITE: 1 - 7:0-7:1::[Error] [W0008] Unreachable test (b/1) + 8:1-8:2::[Error] [W0008] Unreachable test (b/1) diff --git a/crates/elp/src/resources/test/linter/elp_lint_edoc.stdout b/crates/elp/src/resources/test/linter/elp_lint_edoc.stdout index 022b3b8972..8b60108476 100644 --- a/crates/elp/src/resources/test/linter/elp_lint_edoc.stdout +++ b/crates/elp/src/resources/test/linter/elp_lint_edoc.stdout @@ -1,3 +1,3 @@ Diagnostics reported in 1 modules: app_a_edoc: 1 - 4:0-5:0::[Warning] [O0039] tag @docc not recognized. + 5:1-6:1::[Warning] [O0039] tag @docc not recognized. diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint2.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint2.stdout index b0cc8a1337..00ffa07782 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint2.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint2.stdout @@ -1,4 +1,4 @@ module specified: app_a Diagnostics reported in 1 modules: app_a: 1 - 8:0-8:4::[Error] [P1700] head mismatch 'fooX' vs 'food' + 9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food' diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_adhoc_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_adhoc_output.stdout index 5adeaace66..97e3ef307f 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_adhoc_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_adhoc_output.stdout @@ -1,13 +1,13 @@ module specified: app_b Diagnostics reported in 1 modules: app_b: 2 - 7:4-7:35::[WeakWarning] [ad-hoc: application:get_env/2] 'application:get_env/2' called - 4:4-4:34::[WeakWarning] [ad-hoc: application:get_env/2] 'application:get_env/2' called + 8:5-8:36::[WeakWarning] [ad-hoc: application:get_env/2] 'application:get_env/2' called + 5:5-5:35::[WeakWarning] [ad-hoc: application:get_env/2] 'application:get_env/2' called --------------------------------------------- Applying fixes in module 'app_b' for - 7:4-7:35::[WeakWarning] [ad-hoc: application:get_env/2] 'application:get_env/2' called - 4:4-4:34::[WeakWarning] [ad-hoc: application:get_env/2] 'application:get_env/2' called + 8:5-8:36::[WeakWarning] [ad-hoc: application:get_env/2] 'application:get_env/2' called + 5:5-5:35::[WeakWarning] [ad-hoc: application:get_env/2] 'application:get_env/2' called @@ -1,8 +1,8 @@ -module(app_b). -export([application_env_error/0, application_env_no_error/0]). diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_app.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_app.stdout index a5c27f4367..f1938756fc 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_app.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_app.stdout @@ -1,3 +1,3 @@ Diagnostics reported in 1 modules: app_a: 1 - 8:0-8:4::[Error] [P1700] head mismatch 'fooX' vs 'food' + 9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food' diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_config_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_config_output.stdout index 8b6d166b16..d4ef132ea6 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_config_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_config_output.stdout @@ -1,13 +1,13 @@ Diagnostics reported in 4 modules: app_a: 3 - 8:5-8:6::[Warning] [W0010] this variable is unused - 4:4-4:34::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` - 8:0-8:4::[Error] [P1700] head mismatch 'fooX' vs 'food' + 9:6-9:7::[Warning] [W0010] this variable is unused + 5:5-5:35::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` + 9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food' app_a_unused_param: 2 - 4:4-4:5::[Warning] [W0010] this variable is unused - 4:4-4:5::[Warning] [L1268] variable 'X' is unused + 5:5-5:6::[Warning] [W0010] this variable is unused + 5:5-5:6::[Warning] [L1268] variable 'X' is unused app_b: 1 - 4:4-4:34::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` + 5:5-5:35::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` app_b_unused_param: 2 - 4:4-4:5::[Warning] [W0010] this variable is unused - 4:4-4:5::[Warning] [L1268] variable 'X' is unused + 5:5-5:6::[Warning] [W0010] this variable is unused + 5:5-5:6::[Warning] [L1268] variable 'X' is unused diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_custom_config_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_custom_config_output.stdout index 50166d0687..3927f2c196 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_custom_config_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_custom_config_output.stdout @@ -1,9 +1,9 @@ Diagnostics reported in 4 modules: app_a: 1 - 4:4-4:34::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` + 5:5-5:35::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` app_a_unused_param: 1 - 4:4-4:5::[Warning] [L1268] variable 'X' is unused + 5:5-5:6::[Warning] [L1268] variable 'X' is unused app_b: 1 - 4:4-4:34::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` + 5:5-5:35::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` app_b_unused_param: 1 - 4:4-4:5::[Warning] [L1268] variable 'X' is unused + 5:5-5:6::[Warning] [L1268] variable 'X' is unused diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_erlang_service.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_erlang_service.stdout index 071903fae8..74b091100b 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_erlang_service.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_erlang_service.stdout @@ -1,4 +1,4 @@ module specified: expression_updates_literal Diagnostics reported in 1 modules: expression_updates_literal: 1 - 7:6-8:15::[Warning] [L1318] expression updates a literal + 8:7-9:16::[Warning] [L1318] expression updates a literal diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_explicit_enable_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_explicit_enable_output.stdout index cf937cab11..d051fc19fc 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_explicit_enable_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_explicit_enable_output.stdout @@ -1,19 +1,19 @@ Diagnostics reported in 7 modules: app_a: 2 - 4:4-4:34::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. + 5:5-5:35::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` + 1:1-1:1::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. app_a_edoc: 1 - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. + 1:1-1:1::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. app_a_ssr: 1 - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. + 1:1-1:1::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. app_a_unused_param: 2 - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. - 4:4-4:5::[Warning] [L1268] variable 'X' is unused + 1:1-1:1::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. + 5:5-5:6::[Warning] [L1268] variable 'X' is unused app_b: 2 - 4:4-4:34::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. + 5:5-5:35::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` + 1:1-1:1::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. app_b_unused_param: 2 - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. - 4:4-4:5::[Warning] [L1268] variable 'X' is unused + 1:1-1:1::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. + 5:5-5:6::[Warning] [L1268] variable 'X' is unused spelling: 1 - 0:0-0:0::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. + 1:1-1:1::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_fix_ignore.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_fix_ignore.stdout index 4c42e88eec..c573704321 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_fix_ignore.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_fix_ignore.stdout @@ -1,11 +1,11 @@ module specified: app_b Diagnostics reported in 1 modules: app_b: 1 - 4:4-4:34::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` + 5:5-5:35::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` --------------------------------------------- Applying fix in module 'app_b' for - 4:4-4:34::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` + 5:5-5:35::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` @@ -1,7 +1,8 @@ -module(app_b). -export([application_env_error/0, application_env_no_error/0]). diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_fixme_spelling.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_fixme_spelling.stdout index fcba7f3aee..b8c9d1896e 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_fixme_spelling.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_fixme_spelling.stdout @@ -1,11 +1,11 @@ module specified: spelling Diagnostics reported in 1 modules: spelling: 1 - 1:1-1:9::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' + 2:2-2:10::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' --------------------------------------------- Applying fix in module 'spelling' for - 1:1-1:9::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' + 2:2-2:10::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' @@ -1,4 +1,5 @@ -module(spelling). +% elp:fixme W0013 (misspelled_attribute) diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_ignore.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_ignore.stdout index 07a81b80e2..dbdb7db051 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_ignore.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_ignore.stdout @@ -1,9 +1,9 @@ Diagnostics reported in 3 modules: app_a: 1 - 8:5-8:6::[Warning] [W0010] this variable is unused + 9:6-9:7::[Warning] [W0010] this variable is unused app_a_unused_param: 2 - 4:4-4:5::[Warning] [W0010] this variable is unused - 4:4-4:5::[Warning] [L1268] variable 'X' is unused + 5:5-5:6::[Warning] [W0010] this variable is unused + 5:5-5:6::[Warning] [L1268] variable 'X' is unused app_b_unused_param: 2 - 4:4-4:5::[Warning] [W0010] this variable is unused - 4:4-4:5::[Warning] [L1268] variable 'X' is unused + 5:5-5:6::[Warning] [W0010] this variable is unused + 5:5-5:6::[Warning] [L1268] variable 'X' is unused diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_ignore_apps.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_ignore_apps.stdout index e7a35d0262..58caba3339 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_ignore_apps.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_ignore_apps.stdout @@ -1,3 +1,3 @@ Diagnostics reported in 1 modules: app_b_unused_param: 1 - 4:4-4:5::[Warning] [W0010] this variable is unused + 5:5-5:6::[Warning] [W0010] this variable is unused diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_ignore_apps_b.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_ignore_apps_b.stdout index 00d4e2355e..963181a031 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_ignore_apps_b.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_ignore_apps_b.stdout @@ -1,5 +1,5 @@ Diagnostics reported in 2 modules: app_a: 1 - 8:5-8:6::[Warning] [W0010] this variable is unused + 9:6-9:7::[Warning] [W0010] this variable is unused app_a_unused_param: 1 - 4:4-4:5::[Warning] [W0010] this variable is unused + 5:5-5:6::[Warning] [W0010] this variable is unused diff --git a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout index 2efe7fa2de..b2adfefad5 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout @@ -1,24 +1,24 @@ Reporting all diagnostics codes Diagnostics reported in 7 modules: app_a: 6 - 4:4-4:34::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` - 8:0-8:4::[Error] [P1700] head mismatch 'fooX' vs 'food' - 7:6-7:7::[Warning] [W0018] Unexpected ';' - 12:4-12:13::[Warning] [W0026] Function 'app_a:baz/2' is not exported. - 11:0-11:3::[Warning] [L1230] function bar/0 is unused - 15:0-15:3::[Warning] [L1230] function baz/2 is unused + 5:5-5:35::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` + 9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food' + 8:7-8:8::[Warning] [W0018] Unexpected ';' + 13:5-13:14::[Warning] [W0026] Function 'app_a:baz/2' is not exported. + 12:1-12:4::[Warning] [L1230] function bar/0 is unused + 16:1-16:4::[Warning] [L1230] function baz/2 is unused app_a_unused_param: 1 - 4:4-4:5::[Warning] [L1268] variable 'X' is unused + 5:5-5:6::[Warning] [L1268] variable 'X' is unused app_b: 1 - 4:4-4:34::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` + 5:5-5:35::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` app_b_unused_param: 1 - 4:4-4:5::[Warning] [L1268] variable 'X' is unused + 5:5-5:6::[Warning] [L1268] variable 'X' is unused custom_function_matches: 3 - 12:4-12:21::[Warning] [W0017] Function 'excluded:function/0' is undefined. - 13:4-13:25::[Warning] [W0017] Function 'not_excluded:function/0' is undefined. - 14:4-14:14::[Warning] [W0017] Function 'cross:call/0' is undefined. + 13:5-13:22::[Warning] [W0017] Function 'excluded:function/0' is undefined. + 14:5-14:26::[Warning] [W0017] Function 'not_excluded:function/0' is undefined. + 15:5-15:15::[Warning] [W0017] Function 'cross:call/0' is undefined. expression_updates_literal: 2 - 6:0-6:5::[Warning] [L1309] missing specification for function a_fun/0 - 7:6-8:15::[Warning] [L1318] expression updates a literal + 7:1-7:6::[Warning] [L1309] missing specification for function a_fun/0 + 8:7-9:16::[Warning] [L1318] expression updates a literal spelling: 1 - 1:1-1:9::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' + 2:2-2:10::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' diff --git a/crates/elp/src/resources/test/linter/ssr_ad_hoc.stdout b/crates/elp/src/resources/test/linter/ssr_ad_hoc.stdout index 55650830ba..9d331b77c3 100644 --- a/crates/elp/src/resources/test/linter/ssr_ad_hoc.stdout +++ b/crates/elp/src/resources/test/linter/ssr_ad_hoc.stdout @@ -1,3 +1,3 @@ Diagnostics reported in 1 modules: app_a: 1 - 15:12-15:17::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {_@A, _@B}. + 16:13-16:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {_@A, _@B}. diff --git a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli.stdout b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli.stdout index 2e8c69b6a4..4560a933ae 100644 --- a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli.stdout +++ b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli.stdout @@ -1,3 +1,3 @@ Matches found in 1 modules: app_a: 1 - 15:12-15:17::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {_@A, _@B}. + 16:13-16:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {_@A, _@B}. diff --git a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_expand.stdout b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_expand.stdout index 16ad74ebac..5f34373ba6 100644 --- a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_expand.stdout +++ b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_expand.stdout @@ -1,3 +1,3 @@ Matches found in 1 modules: app_a_ssr: 1 - 10:10-10:17::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {4}. + 11:11-11:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {4}. diff --git a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_no_expand.stdout b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_no_expand.stdout index 0e246ef6f5..17017a5b70 100644 --- a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_no_expand.stdout +++ b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_no_expand.stdout @@ -1,3 +1,3 @@ Matches found in 1 modules: app_a_ssr: 1 - 10:10-10:17::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: ?BAR(_@AA). + 11:11-11:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: ?BAR(_@AA). diff --git a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_visible_expand.stdout b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_visible_expand.stdout index 351b8ffee2..7ea212c37d 100644 --- a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_visible_expand.stdout +++ b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_visible_expand.stdout @@ -1,4 +1,4 @@ Matches found in 1 modules: app_a_ssr: 2 - 10:10-10:17::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: ?BAR(_@AA). - 10:10-10:17::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {4}. + 11:11-11:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: ?BAR(_@AA). + 11:11-11:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {4}. diff --git a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_multiple.stdout b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_multiple.stdout index afffa751be..f47bfe428f 100644 --- a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_multiple.stdout +++ b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_multiple.stdout @@ -1,4 +1,4 @@ Matches found in 1 modules: app_a_ssr: 2 - 6:9-6:14::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: 3. - 10:10-10:17::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {4}. + 7:10-7:15::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: 3. + 11:11-11:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {4}. diff --git a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_parens_invisible.stdout b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_parens_invisible.stdout index cefa90c758..653173722f 100644 --- a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_parens_invisible.stdout +++ b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_parens_invisible.stdout @@ -1,3 +1,3 @@ Matches found in 1 modules: app_a_ssr: 1 - 6:9-6:14::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: (((3))). + 7:10-7:15::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: (((3))). diff --git a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_parens_visible.stdout b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_parens_visible.stdout index f830e74c63..7b84e1f651 100644 --- a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_parens_visible.stdout +++ b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_parens_visible.stdout @@ -1,5 +1,5 @@ Matches found in 1 modules: app_a_ssr: 3 - 6:9-6:14::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: (_@A). - 6:10-6:13::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: (_@A). - 7:9-7:12::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: (_@A). + 7:10-7:15::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: (_@A). + 7:11-7:14::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: (_@A). + 8:10-8:13::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: (_@A). diff --git a/crates/elp/src/resources/test/linter/warnings_as_errors.stdout b/crates/elp/src/resources/test/linter/warnings_as_errors.stdout index 831df4f97a..78880f4472 100644 --- a/crates/elp/src/resources/test/linter/warnings_as_errors.stdout +++ b/crates/elp/src/resources/test/linter/warnings_as_errors.stdout @@ -1,24 +1,24 @@ Reporting all diagnostics codes Diagnostics reported in 7 modules: app_a: 6 - 4:4-4:34::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` - 8:0-8:4::[Error] [P1700] head mismatch 'fooX' vs 'food' - 7:6-7:7::[Warning] [W0018] Unexpected ';' - 12:4-12:13::[Warning] [W0026] Function 'app_a:baz/2' is not exported. - 11:0-11:3::[Error] [L1230] function bar/0 is unused - 15:0-15:3::[Error] [L1230] function baz/2 is unused + 5:5-5:35::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` + 9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food' + 8:7-8:8::[Warning] [W0018] Unexpected ';' + 13:5-13:14::[Warning] [W0026] Function 'app_a:baz/2' is not exported. + 12:1-12:4::[Error] [L1230] function bar/0 is unused + 16:1-16:4::[Error] [L1230] function baz/2 is unused app_a_unused_param: 1 - 4:4-4:5::[Error] [L1268] variable 'X' is unused + 5:5-5:6::[Error] [L1268] variable 'X' is unused app_b: 1 - 4:4-4:34::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` + 5:5-5:35::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` app_b_unused_param: 1 - 4:4-4:5::[Error] [L1268] variable 'X' is unused + 5:5-5:6::[Error] [L1268] variable 'X' is unused custom_function_matches: 3 - 12:4-12:21::[Warning] [W0017] Function 'excluded:function/0' is undefined. - 13:4-13:25::[Warning] [W0017] Function 'not_excluded:function/0' is undefined. - 14:4-14:14::[Warning] [W0017] Function 'cross:call/0' is undefined. + 13:5-13:22::[Warning] [W0017] Function 'excluded:function/0' is undefined. + 14:5-14:26::[Warning] [W0017] Function 'not_excluded:function/0' is undefined. + 15:5-15:15::[Warning] [W0017] Function 'cross:call/0' is undefined. expression_updates_literal: 2 - 6:0-6:5::[Error] [L1309] missing specification for function a_fun/0 - 7:6-8:15::[Error] [L1318] expression updates a literal + 7:1-7:6::[Error] [L1309] missing specification for function a_fun/0 + 8:7-9:16::[Error] [L1318] expression updates a literal spelling: 1 - 1:1-1:9::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' + 2:2-2:10::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' diff --git a/crates/elp/src/resources/test/xref/unavailable_type.stdout b/crates/elp/src/resources/test/xref/unavailable_type.stdout index 5b71b7d2d3..1521d13194 100644 --- a/crates/elp/src/resources/test/xref/unavailable_type.stdout +++ b/crates/elp/src/resources/test/xref/unavailable_type.stdout @@ -2,5 +2,5 @@ Reporting all diagnostics codes module specified: unavailable_type Diagnostics reported in 1 modules: unavailable_type: 2 - 9:42-9:57::[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'). - 5:15-5:30::[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'). + 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'). + 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'). diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 6522455018..4aae3078c5 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -381,10 +381,10 @@ impl Diagnostic { format!( "{}:{}-{}:{}::[{:?}] [{}] {}", - start.line, - start.col_utf16, - end.line, - end.col_utf16, + start.line + 1, + start.col_utf16 + 1, + end.line + 1, + end.col_utf16 + 1, self.severity(use_cli_severity), self.code, self.message From 44409a4346b5538730b48064ae04a8822b958b4b Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Fri, 21 Nov 2025 02:03:27 -0800 Subject: [PATCH 036/142] Remove bench runner draft Reviewed By: alanz Differential Revision: D87516838 fbshipit-source-id: ab8bc396f005e8c3cf843583b61bd0e06e8ee2ca --- bench_runner/example_bench/benches/main.rs | 60 ---------------------- bench_runner/runner/main.rs | 16 ------ 2 files changed, 76 deletions(-) delete mode 100644 bench_runner/example_bench/benches/main.rs delete mode 100644 bench_runner/runner/main.rs diff --git a/bench_runner/example_bench/benches/main.rs b/bench_runner/example_bench/benches/main.rs deleted file mode 100644 index 6b2733b5b9..0000000000 --- a/bench_runner/example_bench/benches/main.rs +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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. - */ - -use std::thread; -use std::time; - -use criterion::BenchmarkId; -use criterion::Criterion; -use criterion::criterion_group; -use criterion::criterion_main; - -fn fibonacci_slow(n: u64) -> u64 { - match n { - 0 => 1, - 1 => 1, - n => fibonacci_slow(n - 1) + fibonacci_slow(n - 2), - } -} - -fn fibonacci_fast(n: u64) -> u64 { - let mut a = 0; - let mut b = 1; - let millis = time::Duration::from_millis(12); - thread::sleep(millis); - - match n { - 0 => b, - _ => { - for _ in 0..n { - let c = a + b; - a = b; - b = c; - } - b - } - } -} - -fn bench_fibs(c: &mut Criterion) { - let mut group = c.benchmark_group("Fibonacci"); - for i in [20u64, 21u64].iter() { - group.bench_with_input(BenchmarkId::new("Recursive", i), i, |b, i| { - b.iter(|| fibonacci_slow(*i)) - }); - group.bench_with_input(BenchmarkId::new("Iterative", i), i, |b, i| { - b.iter(|| fibonacci_fast(*i)) - }); - } - group.finish(); -} - -criterion_group!(benches, bench_fibs); -criterion_main!(benches); diff --git a/bench_runner/runner/main.rs b/bench_runner/runner/main.rs deleted file mode 100644 index f895c08c52..0000000000 --- a/bench_runner/runner/main.rs +++ /dev/null @@ -1,16 +0,0 @@ -/* - * 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. - */ - -use std::env; - -fn main() { - let args: Vec = env::args().collect(); - println!("ARGS: {:?}", args); -} From 1788ac77bde55dd98f2257961721a69cd63af51e Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Fri, 21 Nov 2025 02:03:27 -0800 Subject: [PATCH 037/142] Move Emacs configuration under editors Summary: We already have a `editors` directory with editor-specific packages and configurations. Move the Emacs sample there. Also reference it from the Emacs installation docs, to make it simpler to find. Reviewed By: alanz Differential Revision: D87517087 fbshipit-source-id: 754324de280ec094d6756d8702738d16fbdf268b --- {elisp => editors/emacs}/dotemacs.el | 0 website/docs/get-started/editors/emacs.md | 2 ++ 2 files changed, 2 insertions(+) rename {elisp => editors/emacs}/dotemacs.el (100%) diff --git a/elisp/dotemacs.el b/editors/emacs/dotemacs.el similarity index 100% rename from elisp/dotemacs.el rename to editors/emacs/dotemacs.el diff --git a/website/docs/get-started/editors/emacs.md b/website/docs/get-started/editors/emacs.md index 5111167df6..a444e87721 100644 --- a/website/docs/get-started/editors/emacs.md +++ b/website/docs/get-started/editors/emacs.md @@ -54,6 +54,8 @@ Add the following to your emacs `.emacs` file or equivalent. For a list of available configuration option, please refer to [this page](https://emacs-lsp.github.io/lsp-mode/page/lsp-erlang-elp/) and to the [`lsp-mode` settings documentation](https://emacs-lsp.github.io/lsp-mode/page/settings/mode/). +There is also a [`.dotemacs`](https://github.com/WhatsApp/erlang-language-platform/blob/main/editors/emacs/dotemacs.el) file in the ELP repository that you can use as a reference. + ## Troubleshooting #### The following servers support current file but do not have automatic installation From b25b590a278bd03f609b0a42136f85c16ed165c5 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Fri, 21 Nov 2025 02:03:27 -0800 Subject: [PATCH 038/142] Move tree-diff functionality from syntax to ide_db crate Summary: # Context The `text_edit` crate contains a single file, `lib.rs`. We can remove the entire crate by moving the functionality into a `text_edit.rs` in the `ide_db` crate. This is in line with Rust Analyzer. # Problem The move would eventually create a circular dependency between `syntax` and `ide_db`. # Solution To untangle the circular dependency, we can move the `tree-diff` functionality to the `ide_db` crate. Again, this is in line with Rust Analyzer. Reviewed By: alanz Differential Revision: D87524148 fbshipit-source-id: 90b1df3d4191a63c0d74c49af9fafec6578f530c --- Cargo.lock | 3 +- crates/ide_db/Cargo.toml | 2 + crates/ide_db/src/lib.rs | 1 + crates/ide_db/src/source_change.rs | 4 +- crates/ide_db/src/tree_diff.rs | 505 +++++++++++++++++++++++++++++ crates/syntax/Cargo.toml | 1 - crates/syntax/src/algo.rs | 489 ---------------------------- 7 files changed, 512 insertions(+), 493 deletions(-) create mode 100644 crates/ide_db/src/tree_diff.rs diff --git a/Cargo.lock b/Cargo.lock index 03f23763eb..bb400e89c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -637,6 +637,7 @@ name = "elp_ide_db" version = "1.1.0" dependencies = [ "anyhow", + "cov-mark", "eetf", "either", "elp_base_db", @@ -650,6 +651,7 @@ dependencies = [ "fxhash", "hir", "indexmap 2.9.0", + "itertools 0.10.5", "lazy_static", "log", "memchr", @@ -734,7 +736,6 @@ dependencies = [ name = "elp_syntax" version = "1.1.0" dependencies = [ - "cov-mark", "eetf", "elp_ide_db", "elp_text_edit", diff --git a/crates/ide_db/Cargo.toml b/crates/ide_db/Cargo.toml index babeee31b9..52cee9b67c 100644 --- a/crates/ide_db/Cargo.toml +++ b/crates/ide_db/Cargo.toml @@ -17,10 +17,12 @@ elp_types_db.workspace = true hir.workspace = true anyhow.workspace = true +cov-mark.workspace = true eetf.workspace = true either.workspace = true fxhash.workspace = true indexmap.workspace = true +itertools.workspace = true lazy_static.workspace = true log.workspace = true memchr.workspace = true diff --git a/crates/ide_db/src/lib.rs b/crates/ide_db/src/lib.rs index 0240d8498d..1ef15d91ae 100644 --- a/crates/ide_db/src/lib.rs +++ b/crates/ide_db/src/lib.rs @@ -66,6 +66,7 @@ pub mod assists; pub mod helpers; pub mod rename; pub mod source_change; +pub mod tree_diff; pub use defs::ReferenceClass; pub use defs::ReferenceType; diff --git a/crates/ide_db/src/source_change.rs b/crates/ide_db/src/source_change.rs index bb64431772..e387682aba 100644 --- a/crates/ide_db/src/source_change.rs +++ b/crates/ide_db/src/source_change.rs @@ -20,7 +20,6 @@ use std::mem; use elp_base_db::AnchoredPathBuf; use elp_base_db::FileId; use elp_syntax::SyntaxNode; -use elp_syntax::algo; use elp_text_edit::TextEdit; use elp_text_edit::TextEditBuilder; use elp_text_edit::TextRange; @@ -29,6 +28,7 @@ use fxhash::FxHashMap; use stdx::never; use crate::helpers::SnippetCap; +use crate::tree_diff::diff; #[derive(Default, Debug, Clone)] pub struct SourceChange { @@ -171,7 +171,7 @@ impl SourceChangeBuilder { fn commit(&mut self) { if let Some(tm) = self.mutated_tree.take() { - algo::diff(&tm.immutable, &tm.mutable_clone).into_text_edit(&mut self.edit) + diff(&tm.immutable, &tm.mutable_clone).into_text_edit(&mut self.edit) } let edit = mem::take(&mut self.edit).finish(); diff --git a/crates/ide_db/src/tree_diff.rs b/crates/ide_db/src/tree_diff.rs new file mode 100644 index 0000000000..70b29f4d35 --- /dev/null +++ b/crates/ide_db/src/tree_diff.rs @@ -0,0 +1,505 @@ +/* + * 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. + */ + +//! Basic tree diffing functionality. + +use std::hash::BuildHasherDefault; + +use elp_syntax::NodeOrToken; +use elp_syntax::SyntaxElement; +use elp_syntax::SyntaxNode; +use elp_text_edit::TextEditBuilder; +use fxhash::FxHashMap; +use indexmap::IndexMap; + +type FxIndexMap = IndexMap>; + +#[derive(Debug, Hash, PartialEq, Eq)] +enum TreeDiffInsertPos { + After(SyntaxElement), + AsFirstChild(SyntaxElement), +} + +#[derive(Debug)] +pub struct TreeDiff { + replacements: FxHashMap, + deletions: Vec, + // the vec as well as the indexmap are both here to preserve order + insertions: FxIndexMap>, +} + +impl TreeDiff { + pub fn into_text_edit(&self, builder: &mut TextEditBuilder) { + let _p = tracing::info_span!("into_text_edit").entered(); + + for (anchor, to) in self.insertions.iter() { + let offset = match anchor { + TreeDiffInsertPos::After(it) => it.text_range().end(), + TreeDiffInsertPos::AsFirstChild(it) => it.text_range().start(), + }; + to.iter() + .for_each(|to| builder.insert(offset, to.to_string())); + } + for (from, to) in self.replacements.iter() { + builder.replace(from.text_range(), to.to_string()) + } + for text_range in self.deletions.iter().map(SyntaxElement::text_range) { + builder.delete(text_range); + } + } + + pub fn is_empty(&self) -> bool { + self.replacements.is_empty() && self.deletions.is_empty() && self.insertions.is_empty() + } +} + +/// Finds a (potentially minimal) diff, which, applied to `from`, will result in `to`. +/// +/// Specifically, returns a structure that consists of a replacements, insertions and deletions +/// such that applying this map on `from` will result in `to`. +/// +/// This function tries to find a fine-grained diff. +pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff { + let _p = tracing::info_span!("diff").entered(); + + let mut diff = TreeDiff { + replacements: FxHashMap::default(), + insertions: FxIndexMap::default(), + deletions: Vec::new(), + }; + let (from, to) = (from.clone().into(), to.clone().into()); + + if !syntax_element_eq(&from, &to) { + go(&mut diff, from, to); + } + return diff; + + fn syntax_element_eq(lhs: &SyntaxElement, rhs: &SyntaxElement) -> bool { + lhs.kind() == rhs.kind() + && lhs.text_range().len() == rhs.text_range().len() + && match (&lhs, &rhs) { + (NodeOrToken::Node(lhs), NodeOrToken::Node(rhs)) => { + lhs == rhs || lhs.text() == rhs.text() + } + (NodeOrToken::Token(lhs), NodeOrToken::Token(rhs)) => lhs.text() == rhs.text(), + _ => false, + } + } + + // FIXME: this is horribly inefficient. I bet there's a cool algorithm to diff trees properly. + fn go(diff: &mut TreeDiff, lhs: SyntaxElement, rhs: SyntaxElement) { + let (lhs, rhs) = match lhs.as_node().zip(rhs.as_node()) { + Some((lhs, rhs)) => (lhs, rhs), + _ => { + cov_mark::hit!(diff_node_token_replace); + diff.replacements.insert(lhs, rhs); + return; + } + }; + + let mut look_ahead_scratch = Vec::default(); + + let mut rhs_children = rhs.children_with_tokens(); + let mut lhs_children = lhs.children_with_tokens(); + let mut last_lhs = None; + loop { + let lhs_child = lhs_children.next(); + match (lhs_child.clone(), rhs_children.next()) { + (None, None) => break, + (None, Some(element)) => { + let insert_pos = match last_lhs.clone() { + Some(prev) => { + cov_mark::hit!(diff_insert); + TreeDiffInsertPos::After(prev) + } + // first iteration, insert into out parent as the first child + None => { + cov_mark::hit!(diff_insert_as_first_child); + TreeDiffInsertPos::AsFirstChild(lhs.clone().into()) + } + }; + diff.insertions.entry(insert_pos).or_default().push(element); + } + (Some(element), None) => { + cov_mark::hit!(diff_delete); + diff.deletions.push(element); + } + (Some(ref lhs_ele), Some(ref rhs_ele)) if syntax_element_eq(lhs_ele, rhs_ele) => {} + (Some(lhs_ele), Some(rhs_ele)) => { + // nodes differ, look for lhs_ele in rhs, if its found we can mark everything up + // until that element as insertions. This is important to keep the diff minimal + // in regards to insertions that have been actually done, this is important for + // use insertions as we do not want to replace the entire module node. + look_ahead_scratch.push(rhs_ele.clone()); + let mut rhs_children_clone = rhs_children.clone(); + let mut insert = false; + for rhs_child in rhs_children_clone.by_ref() { + if syntax_element_eq(&lhs_ele, &rhs_child) { + cov_mark::hit!(diff_insertions); + insert = true; + break; + } else { + look_ahead_scratch.push(rhs_child); + } + } + let drain = look_ahead_scratch.drain(..); + if insert { + let insert_pos = if let Some(prev) = last_lhs.clone().filter(|_| insert) { + TreeDiffInsertPos::After(prev) + } else { + cov_mark::hit!(insert_first_child); + TreeDiffInsertPos::AsFirstChild(lhs.clone().into()) + }; + + diff.insertions.entry(insert_pos).or_default().extend(drain); + rhs_children = rhs_children_clone; + } else { + go(diff, lhs_ele, rhs_ele) + } + } + } + last_lhs = lhs_child.or(last_lhs); + } + } +} + +#[cfg(test)] +mod tests { + use elp_syntax::AstNode; + use elp_syntax::SourceFile; + use elp_syntax::SyntaxElement; + use elp_syntax::SyntaxKind; + use elp_text_edit::TextEdit; + use expect_test::Expect; + use expect_test::expect; + use itertools::Itertools; + + #[test] + fn replace_node_token() { + cov_mark::check!(diff_node_token_replace); + check_diff( + r#"-module(foo)."#, + r#"ident"#, + expect![[r#" + insertions: + + + + replacements: + + Line 0: Token(ANON_DASH@0..1 "-") -> ident + + deletions: + + Line 1: module + Line 1: ( + Line 1: foo + Line 1: ) + Line 1: . + "#]], + ); + } + + #[test] + fn replace_parent() { + cov_mark::check!(diff_node_token_replace); + check_diff( + r#""#, + r#"-module(foo)."#, + expect![[r#" + insertions: + + + + replacements: + + Line 0: Token(SOURCE_FILE@0..0 "") -> -module(foo). + + deletions: + + + "#]], + ); + } + + #[test] + fn insert_last() { + cov_mark::check!(diff_insert); + check_diff( + r#" +-module(foo). +-use(foo). +-use(bar)."#, + r#" +-module(foo). +-use(foo). +-use(bar). +-use(baz)."#, + expect![[r#" + insertions: + + Line 3: After(Node(WILD_ATTRIBUTE@26..36)) + -> "\n" + -> -use(baz). + + replacements: + + + + deletions: + + + "#]], + ); + } + + #[test] + fn insert_middle() { + check_diff( + r#" +-module(foo). +-use(foo). +-use(bar)."#, + r#" +-module(foo). +-use(foo). +-use(baz). +-use(bar)."#, + expect![[r#" + insertions: + + Line 3: After(Token(WHITESPACE@25..26 "\n")) + -> -use(baz). + -> "\n" + + replacements: + + + + deletions: + + + "#]], + ) + } + + #[test] + fn insert_first() { + check_diff( + r#" +-use(bar). +-use(baz)."#, + r#" +-export([foo/0]). +-use(bar). +-use(baz)."#, + expect![[r#" + insertions: + + Line 0: After(Token(WHITESPACE@0..1 "\n")) + -> -export([foo/0]). + -> "\n" + + replacements: + + + + deletions: + + + "#]], + ) + } + + #[test] + fn first_child_insertion() { + // cov_mark::check!(insert_first_child); + check_diff( + r#" +main() -> + ok."#, + r#" +-module(foo). + +main() -> + ok."#, + expect![[r#" + insertions: + + Line 0: After(Token(WHITESPACE@0..1 "\n")) + -> -module(foo). + -> "\n\n" + + replacements: + + + + deletions: + + + "#]], + ); + } + + #[test] + fn delete_last() { + cov_mark::check!(diff_delete); + check_diff( + r#"-module(foo). + -bar([baz])."#, + r#"-module(foo)."#, + expect![[r#" + insertions: + + + + replacements: + + + + deletions: + + Line 1: "\n " + Line 2: -bar([baz]). + "#]], + ); + } + + #[test] + fn delete_middle() { + // cov_mark::check!(diff_insertions); + check_diff( + r#" +-export([foo/0,bar/1]). +-bar(aaa). + +-foo(bbb). +"#, + r#" +-export([foo/0,bar/1]). + +-foo(bbb). +"#, + expect![[r#" + insertions: + + Line 1: After(Node(EXPORT_ATTRIBUTE@1..24)) + -> "\n\n" + -> -foo(bbb). + + replacements: + + + + deletions: + + Line 2: -bar(aaa). + Line 3: "\n\n" + Line 4: -foo(bbb). + Line 5: "\n" + "#]], + ) + } + + #[test] + fn delete_first() { + check_diff( + r#" +-export([foo/0,bar/1]). + +-foo(bbb). +"#, + r#" +-foo(bbb). +"#, + expect![[r#" + insertions: + + + + replacements: + + Line 1: Token(ANON_DASH@1..2 "-") -> -foo + Line 2: Token(ANON_EXPORT@2..8 "export") -> (bbb) + Line 2: Token(ANON_LPAREN@8..9 "(") -> . + Line 2: Token(WHITESPACE@24..26 "\n\n") -> "\n" + + deletions: + + Line 2: [ + Line 2: foo/0 + Line 2: , + Line 2: bar/1 + Line 2: ] + Line 2: ) + Line 2: . + Line 3: -foo(bbb). + Line 4: "\n" + "#]], + ) + } + + fn check_diff(from: &str, to: &str, expected_diff: Expect) { + let from_node = SourceFile::parse_text(from).tree().syntax().clone(); + let to_node = SourceFile::parse_text(to).tree().syntax().clone(); + let diff = super::diff(&from_node, &to_node); + + let line_number = + |syn: &SyntaxElement| from[..syn.text_range().start().into()].lines().count(); + + let fmt_syntax = |syn: &SyntaxElement| match syn.kind() { + SyntaxKind::WHITESPACE => format!("{:?}", syn.to_string()), + _ => format!("{syn}"), + }; + + let insertions = + diff.insertions + .iter() + .format_with("\n", |(k, v), f| -> Result<(), std::fmt::Error> { + f(&format!( + "Line {}: {:?}\n-> {}", + line_number(match k { + super::TreeDiffInsertPos::After(syn) => syn, + super::TreeDiffInsertPos::AsFirstChild(syn) => syn, + }), + k, + v.iter().format_with("\n-> ", |v, f| f(&fmt_syntax(v))) + )) + }); + + let replacements = diff + .replacements + .iter() + .sorted_by_key(|(syntax, _)| syntax.text_range().start()) + .format_with("\n", |(k, v), f| { + f(&format!( + "Line {}: {:?} -> {}", + line_number(k), + k, + fmt_syntax(v) + )) + }); + + let deletions = diff.deletions.iter().format_with("\n", |v, f| { + f(&format!("Line {}: {}", line_number(v), &fmt_syntax(v))) + }); + + let actual = format!( + "insertions:\n\n{insertions}\n\nreplacements:\n\n{replacements}\n\ndeletions:\n\n{deletions}\n" + ); + expected_diff.assert_eq(&actual); + + let mut from = from.to_owned(); + let mut text_edit = TextEdit::builder(); + diff.into_text_edit(&mut text_edit); + text_edit.finish().apply(&mut from); + assert_eq!(&*from, to, "diff did not turn `from` to `to`"); + } +} diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml index 081efa08d4..80ed01f28c 100644 --- a/crates/syntax/Cargo.toml +++ b/crates/syntax/Cargo.toml @@ -9,7 +9,6 @@ workspace = true [dependencies] elp_text_edit.workspace = true -cov-mark.workspace = true eetf.workspace = true fxhash.workspace = true indexmap.workspace = true diff --git a/crates/syntax/src/algo.rs b/crates/syntax/src/algo.rs index af72d7de98..08b7ed9a1f 100644 --- a/crates/syntax/src/algo.rs +++ b/crates/syntax/src/algo.rs @@ -10,12 +10,8 @@ //! Collection of assorted algorithms for syntax trees. -use std::hash::BuildHasherDefault; use std::ops::RangeInclusive; -use elp_text_edit::TextEditBuilder; -use fxhash::FxHashMap; -use indexmap::IndexMap; use itertools::Itertools; use rowan::NodeOrToken; @@ -178,157 +174,6 @@ pub enum InsertPosition { After(T), } -type FxIndexMap = IndexMap>; - -#[derive(Debug, Hash, PartialEq, Eq)] -enum TreeDiffInsertPos { - After(SyntaxElement), - AsFirstChild(SyntaxElement), -} - -#[derive(Debug)] -pub struct TreeDiff { - replacements: FxHashMap, - deletions: Vec, - // the vec as well as the indexmap are both here to preserve order - insertions: FxIndexMap>, -} - -impl TreeDiff { - pub fn into_text_edit(&self, builder: &mut TextEditBuilder) { - let _p = tracing::info_span!("into_text_edit").entered(); - - for (anchor, to) in self.insertions.iter() { - let offset = match anchor { - TreeDiffInsertPos::After(it) => it.text_range().end(), - TreeDiffInsertPos::AsFirstChild(it) => it.text_range().start(), - }; - to.iter() - .for_each(|to| builder.insert(offset, to.to_string())); - } - for (from, to) in self.replacements.iter() { - builder.replace(from.text_range(), to.to_string()) - } - for text_range in self.deletions.iter().map(SyntaxElement::text_range) { - builder.delete(text_range); - } - } - - pub fn is_empty(&self) -> bool { - self.replacements.is_empty() && self.deletions.is_empty() && self.insertions.is_empty() - } -} - -/// Finds a (potentially minimal) diff, which, applied to `from`, will result in `to`. -/// -/// Specifically, returns a structure that consists of a replacements, insertions and deletions -/// such that applying this map on `from` will result in `to`. -/// -/// This function tries to find a fine-grained diff. -pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff { - let _p = tracing::info_span!("diff").entered(); - - let mut diff = TreeDiff { - replacements: FxHashMap::default(), - insertions: FxIndexMap::default(), - deletions: Vec::new(), - }; - let (from, to) = (from.clone().into(), to.clone().into()); - - if !syntax_element_eq(&from, &to) { - go(&mut diff, from, to); - } - return diff; - - fn syntax_element_eq(lhs: &SyntaxElement, rhs: &SyntaxElement) -> bool { - lhs.kind() == rhs.kind() - && lhs.text_range().len() == rhs.text_range().len() - && match (&lhs, &rhs) { - (NodeOrToken::Node(lhs), NodeOrToken::Node(rhs)) => { - lhs == rhs || lhs.text() == rhs.text() - } - (NodeOrToken::Token(lhs), NodeOrToken::Token(rhs)) => lhs.text() == rhs.text(), - _ => false, - } - } - - // FIXME: this is horribly inefficient. I bet there's a cool algorithm to diff trees properly. - fn go(diff: &mut TreeDiff, lhs: SyntaxElement, rhs: SyntaxElement) { - let (lhs, rhs) = match lhs.as_node().zip(rhs.as_node()) { - Some((lhs, rhs)) => (lhs, rhs), - _ => { - cov_mark::hit!(diff_node_token_replace); - diff.replacements.insert(lhs, rhs); - return; - } - }; - - let mut look_ahead_scratch = Vec::default(); - - let mut rhs_children = rhs.children_with_tokens(); - let mut lhs_children = lhs.children_with_tokens(); - let mut last_lhs = None; - loop { - let lhs_child = lhs_children.next(); - match (lhs_child.clone(), rhs_children.next()) { - (None, None) => break, - (None, Some(element)) => { - let insert_pos = match last_lhs.clone() { - Some(prev) => { - cov_mark::hit!(diff_insert); - TreeDiffInsertPos::After(prev) - } - // first iteration, insert into out parent as the first child - None => { - cov_mark::hit!(diff_insert_as_first_child); - TreeDiffInsertPos::AsFirstChild(lhs.clone().into()) - } - }; - diff.insertions.entry(insert_pos).or_default().push(element); - } - (Some(element), None) => { - cov_mark::hit!(diff_delete); - diff.deletions.push(element); - } - (Some(ref lhs_ele), Some(ref rhs_ele)) if syntax_element_eq(lhs_ele, rhs_ele) => {} - (Some(lhs_ele), Some(rhs_ele)) => { - // nodes differ, look for lhs_ele in rhs, if its found we can mark everything up - // until that element as insertions. This is important to keep the diff minimal - // in regards to insertions that have been actually done, this is important for - // use insertions as we do not want to replace the entire module node. - look_ahead_scratch.push(rhs_ele.clone()); - let mut rhs_children_clone = rhs_children.clone(); - let mut insert = false; - for rhs_child in rhs_children_clone.by_ref() { - if syntax_element_eq(&lhs_ele, &rhs_child) { - cov_mark::hit!(diff_insertions); - insert = true; - break; - } else { - look_ahead_scratch.push(rhs_child); - } - } - let drain = look_ahead_scratch.drain(..); - if insert { - let insert_pos = if let Some(prev) = last_lhs.clone().filter(|_| insert) { - TreeDiffInsertPos::After(prev) - } else { - cov_mark::hit!(insert_first_child); - TreeDiffInsertPos::AsFirstChild(lhs.clone().into()) - }; - - diff.insertions.entry(insert_pos).or_default().extend(drain); - rhs_children = rhs_children_clone; - } else { - go(diff, lhs_ele, rhs_ele) - } - } - } - last_lhs = lhs_child.or(last_lhs); - } - } -} - /// Adds specified children (tokens or nodes) to the current node at the /// specific position. /// @@ -452,337 +297,3 @@ fn to_green_element(element: SyntaxElement) -> NodeOrToken it.green().to_owned().into(), } } - -#[cfg(test)] -mod tests { - use elp_text_edit::TextEdit; - use expect_test::Expect; - use expect_test::expect; - use itertools::Itertools; - - use crate::AstNode; - use crate::SyntaxElement; - use crate::SyntaxKind; - - #[test] - fn replace_node_token() { - cov_mark::check!(diff_node_token_replace); - check_diff( - r#"-module(foo)."#, - r#"ident"#, - expect![[r#" - insertions: - - - - replacements: - - Line 0: Token(ANON_DASH@0..1 "-") -> ident - - deletions: - - Line 1: module - Line 1: ( - Line 1: foo - Line 1: ) - Line 1: . - "#]], - ); - } - - #[test] - fn replace_parent() { - cov_mark::check!(diff_node_token_replace); - check_diff( - r#""#, - r#"-module(foo)."#, - expect![[r#" - insertions: - - - - replacements: - - Line 0: Token(SOURCE_FILE@0..0 "") -> -module(foo). - - deletions: - - - "#]], - ); - } - - #[test] - fn insert_last() { - cov_mark::check!(diff_insert); - check_diff( - r#" --module(foo). --use(foo). --use(bar)."#, - r#" --module(foo). --use(foo). --use(bar). --use(baz)."#, - expect![[r#" - insertions: - - Line 3: After(Node(WILD_ATTRIBUTE@26..36)) - -> "\n" - -> -use(baz). - - replacements: - - - - deletions: - - - "#]], - ); - } - - #[test] - fn insert_middle() { - check_diff( - r#" --module(foo). --use(foo). --use(bar)."#, - r#" --module(foo). --use(foo). --use(baz). --use(bar)."#, - expect![[r#" - insertions: - - Line 3: After(Token(WHITESPACE@25..26 "\n")) - -> -use(baz). - -> "\n" - - replacements: - - - - deletions: - - - "#]], - ) - } - - #[test] - fn insert_first() { - check_diff( - r#" --use(bar). --use(baz)."#, - r#" --export([foo/0]). --use(bar). --use(baz)."#, - expect![[r#" - insertions: - - Line 0: After(Token(WHITESPACE@0..1 "\n")) - -> -export([foo/0]). - -> "\n" - - replacements: - - - - deletions: - - - "#]], - ) - } - - #[test] - fn first_child_insertion() { - // cov_mark::check!(insert_first_child); - check_diff( - r#" -main() -> - ok."#, - r#" --module(foo). - -main() -> - ok."#, - expect![[r#" - insertions: - - Line 0: After(Token(WHITESPACE@0..1 "\n")) - -> -module(foo). - -> "\n\n" - - replacements: - - - - deletions: - - - "#]], - ); - } - - #[test] - fn delete_last() { - cov_mark::check!(diff_delete); - check_diff( - r#"-module(foo). - -bar([baz])."#, - r#"-module(foo)."#, - expect![[r#" - insertions: - - - - replacements: - - - - deletions: - - Line 1: "\n " - Line 2: -bar([baz]). - "#]], - ); - } - - #[test] - fn delete_middle() { - // cov_mark::check!(diff_insertions); - check_diff( - r#" --export([foo/0,bar/1]). --bar(aaa). - --foo(bbb). -"#, - r#" --export([foo/0,bar/1]). - --foo(bbb). -"#, - expect![[r#" - insertions: - - Line 1: After(Node(EXPORT_ATTRIBUTE@1..24)) - -> "\n\n" - -> -foo(bbb). - - replacements: - - - - deletions: - - Line 2: -bar(aaa). - Line 3: "\n\n" - Line 4: -foo(bbb). - Line 5: "\n" - "#]], - ) - } - - #[test] - fn delete_first() { - check_diff( - r#" --export([foo/0,bar/1]). - --foo(bbb). -"#, - r#" --foo(bbb). -"#, - expect![[r#" - insertions: - - - - replacements: - - Line 1: Token(ANON_DASH@1..2 "-") -> -foo - Line 2: Token(ANON_EXPORT@2..8 "export") -> (bbb) - Line 2: Token(ANON_LPAREN@8..9 "(") -> . - Line 2: Token(WHITESPACE@24..26 "\n\n") -> "\n" - - deletions: - - Line 2: [ - Line 2: foo/0 - Line 2: , - Line 2: bar/1 - Line 2: ] - Line 2: ) - Line 2: . - Line 3: -foo(bbb). - Line 4: "\n" - "#]], - ) - } - - fn check_diff(from: &str, to: &str, expected_diff: Expect) { - let from_node = crate::SourceFile::parse_text(from).tree().syntax().clone(); - let to_node = crate::SourceFile::parse_text(to).tree().syntax().clone(); - let diff = super::diff(&from_node, &to_node); - - let line_number = - |syn: &SyntaxElement| from[..syn.text_range().start().into()].lines().count(); - - let fmt_syntax = |syn: &SyntaxElement| match syn.kind() { - SyntaxKind::WHITESPACE => format!("{:?}", syn.to_string()), - _ => format!("{syn}"), - }; - - let insertions = - diff.insertions - .iter() - .format_with("\n", |(k, v), f| -> Result<(), std::fmt::Error> { - f(&format!( - "Line {}: {:?}\n-> {}", - line_number(match k { - super::TreeDiffInsertPos::After(syn) => syn, - super::TreeDiffInsertPos::AsFirstChild(syn) => syn, - }), - k, - v.iter().format_with("\n-> ", |v, f| f(&fmt_syntax(v))) - )) - }); - - let replacements = diff - .replacements - .iter() - .sorted_by_key(|(syntax, _)| syntax.text_range().start()) - .format_with("\n", |(k, v), f| { - f(&format!( - "Line {}: {:?} -> {}", - line_number(k), - k, - fmt_syntax(v) - )) - }); - - let deletions = diff.deletions.iter().format_with("\n", |v, f| { - f(&format!("Line {}: {}", line_number(v), &fmt_syntax(v))) - }); - - let actual = format!( - "insertions:\n\n{insertions}\n\nreplacements:\n\n{replacements}\n\ndeletions:\n\n{deletions}\n" - ); - expected_diff.assert_eq(&actual); - - let mut from = from.to_owned(); - let mut text_edit = TextEdit::builder(); - diff.into_text_edit(&mut text_edit); - text_edit.finish().apply(&mut from); - assert_eq!(&*from, to, "diff did not turn `from` to `to`"); - } -} From 99501811a569595654e735e334db107c50e3cbf0 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Fri, 21 Nov 2025 02:03:27 -0800 Subject: [PATCH 039/142] Remove text_edit crate Summary: The `text_edit` crate contains a single file. Move it to the `ide_db` crate, and remove the whole `text_edit` crate. This is in line with Rust Analyzer. Reviewed By: alanz Differential Revision: D87539326 fbshipit-source-id: cdc8f71902bc084eca79ae54ca71584ac48ed637 --- Cargo.lock | 15 ++------------- Cargo.toml | 1 - crates/elp/Cargo.toml | 2 +- crates/elp/src/bin/lint_cli.rs | 2 +- crates/elp/src/to_proto.rs | 4 ++-- crates/ide/Cargo.toml | 3 +-- crates/ide/src/diagnostics.rs | 2 +- crates/ide/src/diagnostics/boolean_precedence.rs | 4 ++-- crates/ide/src/diagnostics/debugging_function.rs | 2 +- crates/ide/src/diagnostics/dependent_header.rs | 2 +- crates/ide/src/diagnostics/deprecated_function.rs | 6 +++--- crates/ide/src/diagnostics/edoc.rs | 4 ++-- .../ide/src/diagnostics/effect_free_statement.rs | 2 +- .../eqwalizer_assists/expected_type.rs | 4 ++-- crates/ide/src/diagnostics/head_mismatch.rs | 2 +- .../src/diagnostics/macro_precedence_suprise.rs | 2 +- crates/ide/src/diagnostics/meck.rs | 4 ++-- .../missing_compile_warn_missing_spec.rs | 2 +- .../ide/src/diagnostics/misspelled_attribute.rs | 2 +- crates/ide/src/diagnostics/module_mismatch.rs | 2 +- .../diagnostics/nonstandard_integer_formatting.rs | 2 +- crates/ide/src/diagnostics/record_tuple_match.rs | 2 +- crates/ide/src/diagnostics/replace_call.rs | 2 +- crates/ide/src/diagnostics/replace_in_spec.rs | 2 +- crates/ide/src/diagnostics/trivial_match.rs | 2 +- crates/ide/src/diagnostics/undefined_macro.rs | 2 +- .../ide/src/diagnostics/undocumented_function.rs | 2 +- crates/ide/src/diagnostics/undocumented_module.rs | 2 +- crates/ide/src/diagnostics/unspecific_include.rs | 4 ++-- .../ide/src/diagnostics/unused_function_args.rs | 4 ++-- crates/ide/src/diagnostics/unused_include.rs | 2 +- crates/ide/src/diagnostics/unused_macro.rs | 2 +- crates/ide/src/diagnostics_collection.rs | 2 +- crates/ide/src/rename.rs | 2 +- crates/ide/src/tests.rs | 2 +- crates/ide_assists/Cargo.toml | 1 - crates/ide_assists/src/handlers/add_doc.rs | 2 +- crates/ide_assists/src/handlers/flip_sep.rs | 2 +- .../src/handlers/implement_behaviour.rs | 4 ++-- .../ide_assists/src/handlers/inline_function.rs | 2 +- crates/ide_assists/src/helpers.rs | 4 ++-- crates/ide_db/Cargo.toml | 2 +- crates/ide_db/src/helpers.rs | 2 +- crates/ide_db/src/lib.rs | 3 ++- crates/ide_db/src/rename.rs | 2 +- crates/ide_db/src/source_change.rs | 8 ++++---- .../src/lib.rs => ide_db/src/text_edit.rs} | 1 - crates/ide_db/src/tree_diff.rs | 6 ++++-- crates/syntax/Cargo.toml | 2 -- crates/text_edit/Cargo.toml | 11 ----------- 50 files changed, 64 insertions(+), 89 deletions(-) rename crates/{text_edit/src/lib.rs => ide_db/src/text_edit.rs} (99%) delete mode 100644 crates/text_edit/Cargo.toml diff --git a/Cargo.lock b/Cargo.lock index bb400e89c3..2da9907eca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -446,10 +446,10 @@ dependencies = [ "crossbeam-channel", "elp_eqwalizer", "elp_ide", + "elp_ide_db", "elp_log", "elp_project_model", "elp_syntax", - "elp_text_edit", "env_logger", "expect-test", "fs_extra", @@ -572,7 +572,6 @@ dependencies = [ "elp_ide_ssr", "elp_project_model", "elp_syntax", - "elp_text_edit", "elp_types_db", "env_logger", "expect-test", @@ -604,7 +603,6 @@ dependencies = [ "cov-mark", "elp_ide_db", "elp_syntax", - "elp_text_edit", "expect-test", "fxhash", "hir", @@ -645,7 +643,6 @@ dependencies = [ "elp_erlang_service", "elp_project_model", "elp_syntax", - "elp_text_edit", "elp_types_db", "expect-test", "fxhash", @@ -666,6 +663,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", + "text-size", "toml", "tracing", ] @@ -738,7 +736,6 @@ version = "1.1.0" dependencies = [ "eetf", "elp_ide_db", - "elp_text_edit", "expect-test", "fxhash", "indexmap 2.9.0", @@ -758,14 +755,6 @@ dependencies = [ "tree-sitter-erlang", ] -[[package]] -name = "elp_text_edit" -version = "1.1.0" -dependencies = [ - "itertools 0.10.5", - "text-size", -] - [[package]] name = "elp_types_db" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index b3b8ae074c..825530fe4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,6 @@ elp_ide_ssr = { path = "./crates/ide_ssr" } elp_log = { path = "./crates/elp_log" } elp_project_model = { path = "./crates/project_model" } elp_syntax = { path = "./crates/syntax" } -elp_text_edit = { path = "./crates/text_edit" } elp_types_db = { path = "./crates/types_db" } hir = { path = "./crates/hir" } diff --git a/crates/elp/Cargo.toml b/crates/elp/Cargo.toml index 66552ada1d..2a1b6e8b65 100644 --- a/crates/elp/Cargo.toml +++ b/crates/elp/Cargo.toml @@ -18,10 +18,10 @@ workspace = true [dependencies] elp_eqwalizer.workspace = true elp_ide.workspace = true +elp_ide_db.workspace = true elp_log.workspace = true elp_project_model.workspace = true elp_syntax.workspace = true -elp_text_edit.workspace = true hir.workspace = true always-assert.workspace = true diff --git a/crates/elp/src/bin/lint_cli.rs b/crates/elp/src/bin/lint_cli.rs index 3bc2271072..fcce5cac98 100644 --- a/crates/elp/src/bin/lint_cli.rs +++ b/crates/elp/src/bin/lint_cli.rs @@ -52,12 +52,12 @@ use elp_ide::elp_ide_db::elp_base_db::ProjectId; use elp_ide::elp_ide_db::elp_base_db::Vfs; use elp_ide::elp_ide_db::elp_base_db::VfsPath; use elp_ide::elp_ide_db::source_change::SourceChange; +use elp_ide_db::text_edit::TextSize; use elp_log::telemetry; use elp_project_model::AppName; use elp_project_model::AppType; use elp_project_model::DiscoverConfig; use elp_project_model::buck::BuckQueryConfig; -use elp_text_edit::TextSize; use fxhash::FxHashMap; use fxhash::FxHashSet; use hir::FormIdx; diff --git a/crates/elp/src/to_proto.rs b/crates/elp/src/to_proto.rs index f9263cfcab..70d316d5c6 100644 --- a/crates/elp/src/to_proto.rs +++ b/crates/elp/src/to_proto.rs @@ -48,9 +48,9 @@ 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::SourceChange; +use elp_ide_db::text_edit::Indel; +use elp_ide_db::text_edit::TextEdit; use elp_project_model::ProjectBuildData; -use elp_text_edit::Indel; -use elp_text_edit::TextEdit; use lsp_types::CompletionItemTag; use lsp_types::Hover; use lsp_types::HoverContents; diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml index aba9731379..acba8ef642 100644 --- a/crates/ide/Cargo.toml +++ b/crates/ide/Cargo.toml @@ -10,12 +10,11 @@ workspace = true elp_eqwalizer.workspace = true elp_erlang_service.workspace = true elp_ide_assists.workspace = true -elp_ide_completion.workspace = true elp_ide_db.workspace = true +elp_ide_completion.workspace = true elp_ide_ssr.workspace = true elp_project_model.workspace = true elp_syntax.workspace = true -elp_text_edit.workspace = true elp_types_db.workspace = true hir.workspace = true diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 4aae3078c5..f8932db145 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -44,6 +44,7 @@ use elp_ide_db::metadata::Kind; use elp_ide_db::metadata::Metadata; use elp_ide_db::metadata::Source; use elp_ide_db::source_change::SourceChange; +use elp_ide_db::text_edit::TextEdit; use elp_ide_ssr::Match; use elp_ide_ssr::SsrSearchScope; use elp_ide_ssr::match_pattern; @@ -63,7 +64,6 @@ use elp_syntax::ast::edit::IndentLevel; use elp_syntax::ast::edit::start_of_line; use elp_syntax::label::Label; use elp_syntax::ted::Element; -use elp_text_edit::TextEdit; use elp_types_db::TypedSemantic; use fxhash::FxHashMap; use fxhash::FxHashSet; diff --git a/crates/ide/src/diagnostics/boolean_precedence.rs b/crates/ide/src/diagnostics/boolean_precedence.rs index affbdf9b1d..f2ba0fe542 100644 --- a/crates/ide/src/diagnostics/boolean_precedence.rs +++ b/crates/ide/src/diagnostics/boolean_precedence.rs @@ -24,11 +24,11 @@ use elp_ide_assists::helpers::unwrap_parens; use elp_ide_db::DiagnosticCode; use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChange; +use elp_ide_db::text_edit::TextEdit; +use elp_ide_db::text_edit::TextRange; use elp_syntax::ast; use elp_syntax::ast::BinaryOp; use elp_syntax::ast::LogicOp; -use elp_text_edit::TextEdit; -use elp_text_edit::TextRange; use hir::AnyExpr; use hir::AnyExprId; use hir::AnyExprRef; diff --git a/crates/ide/src/diagnostics/debugging_function.rs b/crates/ide/src/diagnostics/debugging_function.rs index 904ebe3098..185736d207 100644 --- a/crates/ide/src/diagnostics/debugging_function.rs +++ b/crates/ide/src/diagnostics/debugging_function.rs @@ -11,7 +11,7 @@ use elp_ide_assists::Assist; use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChangeBuilder; -use elp_text_edit::TextRange; +use elp_ide_db::text_edit::TextRange; use hir::Semantic; use crate::FunctionMatch; diff --git a/crates/ide/src/diagnostics/dependent_header.rs b/crates/ide/src/diagnostics/dependent_header.rs index e0b4d11652..4729f05872 100644 --- a/crates/ide/src/diagnostics/dependent_header.rs +++ b/crates/ide/src/diagnostics/dependent_header.rs @@ -14,10 +14,10 @@ use elp_ide_db::elp_base_db::FileId; use elp_ide_db::elp_base_db::FileKind; +use elp_ide_db::text_edit::TextRange; use elp_syntax::AstNode; use elp_syntax::ast; use elp_syntax::ast::RecordName; -use elp_text_edit::TextRange; use hir::AnyExpr; use hir::InFile; use hir::Name; diff --git a/crates/ide/src/diagnostics/deprecated_function.rs b/crates/ide/src/diagnostics/deprecated_function.rs index 3956282c45..540edfcca3 100644 --- a/crates/ide/src/diagnostics/deprecated_function.rs +++ b/crates/ide/src/diagnostics/deprecated_function.rs @@ -21,10 +21,10 @@ use elp_ide_assists::Assist; use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChange; +use elp_ide_db::text_edit::TextEdit; +use elp_ide_db::text_edit::TextRange; +use elp_ide_db::text_edit::TextSize; use elp_syntax::AstNode; -use elp_text_edit::TextEdit; -use elp_text_edit::TextRange; -use elp_text_edit::TextSize; use hir::AnyExpr; use hir::Expr; use hir::FunctionDef; diff --git a/crates/ide/src/diagnostics/edoc.rs b/crates/ide/src/diagnostics/edoc.rs index 53858e7cca..0daae4f7f6 100644 --- a/crates/ide/src/diagnostics/edoc.rs +++ b/crates/ide/src/diagnostics/edoc.rs @@ -14,9 +14,9 @@ use elp_ide_assists::helpers; use elp_ide_assists::helpers::extend_range; use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChangeBuilder; +use elp_ide_db::text_edit::TextRange; +use elp_ide_db::text_edit::TextSize; use elp_syntax::AstNode; -use elp_text_edit::TextRange; -use elp_text_edit::TextSize; use fxhash::FxHashSet; use hir::Attribute; use hir::Semantic; diff --git a/crates/ide/src/diagnostics/effect_free_statement.rs b/crates/ide/src/diagnostics/effect_free_statement.rs index 72d6c8eb8d..9aec815fbb 100644 --- a/crates/ide/src/diagnostics/effect_free_statement.rs +++ b/crates/ide/src/diagnostics/effect_free_statement.rs @@ -17,11 +17,11 @@ use std::iter; use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChange; +use elp_ide_db::text_edit::TextEdit; use elp_syntax::AstNode; use elp_syntax::SyntaxElement; use elp_syntax::SyntaxKind; use elp_syntax::ast; -use elp_text_edit::TextEdit; use hir::AnyExprId; use hir::Expr; use hir::ExprId; diff --git a/crates/ide/src/diagnostics/eqwalizer_assists/expected_type.rs b/crates/ide/src/diagnostics/eqwalizer_assists/expected_type.rs index ee655d2f8c..081f6e43ae 100644 --- a/crates/ide/src/diagnostics/eqwalizer_assists/expected_type.rs +++ b/crates/ide/src/diagnostics/eqwalizer_assists/expected_type.rs @@ -15,8 +15,8 @@ use elp_ide_db::elp_base_db::FileId; use elp_ide_db::elp_base_db::FilePosition; use elp_ide_db::find_best_token; use elp_ide_db::source_change::SourceChange; -use elp_text_edit::TextEdit; -use elp_text_edit::TextSize; +use elp_ide_db::text_edit::TextEdit; +use elp_ide_db::text_edit::TextSize; use elp_types_db::eqwalizer::StructuredDiagnostic; use elp_types_db::eqwalizer::tc_diagnostics::ExpectedSubtype; use elp_types_db::eqwalizer::tc_diagnostics::TypeError; diff --git a/crates/ide/src/diagnostics/head_mismatch.rs b/crates/ide/src/diagnostics/head_mismatch.rs index 3a92d6f931..edfb1d8518 100644 --- a/crates/ide/src/diagnostics/head_mismatch.rs +++ b/crates/ide/src/diagnostics/head_mismatch.rs @@ -13,13 +13,13 @@ use std::hash::Hash; use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChange; +use elp_ide_db::text_edit::TextEdit; use elp_syntax::SyntaxToken; use elp_syntax::TextRange; use elp_syntax::ast; use elp_syntax::ast::AstNode; use elp_syntax::ast::ClauseSeparator; use elp_syntax::syntax_node::SyntaxNode; -use elp_text_edit::TextEdit; use fxhash::FxHashMap; use hir::Semantic; diff --git a/crates/ide/src/diagnostics/macro_precedence_suprise.rs b/crates/ide/src/diagnostics/macro_precedence_suprise.rs index 700f54441e..838ddb3d9c 100644 --- a/crates/ide/src/diagnostics/macro_precedence_suprise.rs +++ b/crates/ide/src/diagnostics/macro_precedence_suprise.rs @@ -17,7 +17,7 @@ use elp_ide_assists::Assist; use elp_ide_assists::helpers::add_parens_edit; use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChange; -use elp_text_edit::TextRange; +use elp_ide_db::text_edit::TextRange; use hir::AnyExpr; use hir::AnyExprRef; use hir::Expr; diff --git a/crates/ide/src/diagnostics/meck.rs b/crates/ide/src/diagnostics/meck.rs index 1fc115638d..8919ddb770 100644 --- a/crates/ide/src/diagnostics/meck.rs +++ b/crates/ide/src/diagnostics/meck.rs @@ -10,8 +10,8 @@ use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChangeBuilder; -use elp_text_edit::TextRange; -use elp_text_edit::TextSize; +use elp_ide_db::text_edit::TextRange; +use elp_ide_db::text_edit::TextSize; use hir::AnyExprId; use hir::Expr; use hir::FunctionDef; diff --git a/crates/ide/src/diagnostics/missing_compile_warn_missing_spec.rs b/crates/ide/src/diagnostics/missing_compile_warn_missing_spec.rs index 681672ba8e..4f61ea5190 100644 --- a/crates/ide/src/diagnostics/missing_compile_warn_missing_spec.rs +++ b/crates/ide/src/diagnostics/missing_compile_warn_missing_spec.rs @@ -20,8 +20,8 @@ use elp_ide_db::DiagnosticCode; use elp_ide_db::elp_base_db::FileId; use elp_ide_db::elp_base_db::FileKind; use elp_ide_db::source_change::SourceChangeBuilder; +use elp_ide_db::text_edit::TextRange; use elp_syntax::AstNode; -use elp_text_edit::TextRange; use fxhash::FxHashSet; use hir::AnyExpr; use hir::CompileOptionId; diff --git a/crates/ide/src/diagnostics/misspelled_attribute.rs b/crates/ide/src/diagnostics/misspelled_attribute.rs index 6ba95c8ef9..90695f52d1 100644 --- a/crates/ide/src/diagnostics/misspelled_attribute.rs +++ b/crates/ide/src/diagnostics/misspelled_attribute.rs @@ -10,9 +10,9 @@ use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChange; +use elp_ide_db::text_edit::TextEdit; use elp_syntax::ast::AstNode; use elp_syntax::ast::WildAttribute; -use elp_text_edit::TextEdit; use hir::Attribute; use hir::Semantic; diff --git a/crates/ide/src/diagnostics/module_mismatch.rs b/crates/ide/src/diagnostics/module_mismatch.rs index c1a57dfbd9..77181b55ef 100644 --- a/crates/ide/src/diagnostics/module_mismatch.rs +++ b/crates/ide/src/diagnostics/module_mismatch.rs @@ -15,11 +15,11 @@ use elp_ide_assists::Assist; use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChange; +use elp_ide_db::text_edit::TextEdit; use elp_syntax::AstNode; use elp_syntax::SyntaxNode; use elp_syntax::TextRange; use elp_syntax::ast; -use elp_text_edit::TextEdit; use hir::Semantic; use crate::Diagnostic; diff --git a/crates/ide/src/diagnostics/nonstandard_integer_formatting.rs b/crates/ide/src/diagnostics/nonstandard_integer_formatting.rs index d80751c946..63b0a450cb 100644 --- a/crates/ide/src/diagnostics/nonstandard_integer_formatting.rs +++ b/crates/ide/src/diagnostics/nonstandard_integer_formatting.rs @@ -15,8 +15,8 @@ use elp_ide_db::DiagnosticCode; use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChangeBuilder; +use elp_ide_db::text_edit::TextRange; use elp_syntax::AstNode; -use elp_text_edit::TextRange; use hir::AnyExpr; use hir::BasedInteger; use hir::Expr; diff --git a/crates/ide/src/diagnostics/record_tuple_match.rs b/crates/ide/src/diagnostics/record_tuple_match.rs index 5606dce89a..9756bfeb73 100644 --- a/crates/ide/src/diagnostics/record_tuple_match.rs +++ b/crates/ide/src/diagnostics/record_tuple_match.rs @@ -15,7 +15,7 @@ use elp_ide_db::DiagnosticCode; use elp_ide_db::elp_base_db::FileId; use elp_ide_db::elp_base_db::FileRange; -use elp_text_edit::TextRange; +use elp_ide_db::text_edit::TextRange; use hir::AnyExpr; use hir::FunctionDef; use hir::Literal; diff --git a/crates/ide/src/diagnostics/replace_call.rs b/crates/ide/src/diagnostics/replace_call.rs index e83afcb985..fa16acf356 100644 --- a/crates/ide/src/diagnostics/replace_call.rs +++ b/crates/ide/src/diagnostics/replace_call.rs @@ -15,9 +15,9 @@ use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChange; +use elp_ide_db::text_edit::TextEdit; use elp_syntax::TextRange; use elp_syntax::ast; -use elp_text_edit::TextEdit; use hir::AnyExprId; use hir::CallTarget; use hir::Expr; diff --git a/crates/ide/src/diagnostics/replace_in_spec.rs b/crates/ide/src/diagnostics/replace_in_spec.rs index 12d8aa3f64..a5df5217e2 100644 --- a/crates/ide/src/diagnostics/replace_in_spec.rs +++ b/crates/ide/src/diagnostics/replace_in_spec.rs @@ -16,8 +16,8 @@ use elp_ide_db::DiagnosticCode; use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChange; +use elp_ide_db::text_edit::TextEdit; use elp_syntax::SmolStr; -use elp_text_edit::TextEdit; use fxhash::FxHashSet; use hir::AnyExpr; use hir::InFile; diff --git a/crates/ide/src/diagnostics/trivial_match.rs b/crates/ide/src/diagnostics/trivial_match.rs index 56d3881a9a..705cc22d7e 100644 --- a/crates/ide/src/diagnostics/trivial_match.rs +++ b/crates/ide/src/diagnostics/trivial_match.rs @@ -17,9 +17,9 @@ use std::collections::HashMap; use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChange; +use elp_ide_db::text_edit::TextEdit; use elp_syntax::TextRange; use elp_syntax::ast; -use elp_text_edit::TextEdit; use hir::AnyExpr; use hir::AnyExprId; use hir::BinarySeg; diff --git a/crates/ide/src/diagnostics/undefined_macro.rs b/crates/ide/src/diagnostics/undefined_macro.rs index c90e2d1285..51ee0ceaff 100644 --- a/crates/ide/src/diagnostics/undefined_macro.rs +++ b/crates/ide/src/diagnostics/undefined_macro.rs @@ -15,7 +15,7 @@ use elp_ide_db::DiagnosticCode; use elp_ide_db::elp_base_db::FileId; use elp_ide_db::elp_base_db::path_for_file; use elp_ide_db::source_change::SourceChange; -use elp_text_edit::TextEdit; +use elp_ide_db::text_edit::TextEdit; use fxhash::FxHashSet; use hir::Semantic; use lazy_static::lazy_static; diff --git a/crates/ide/src/diagnostics/undocumented_function.rs b/crates/ide/src/diagnostics/undocumented_function.rs index 41fdeb0229..ab193cdcd5 100644 --- a/crates/ide/src/diagnostics/undocumented_function.rs +++ b/crates/ide/src/diagnostics/undocumented_function.rs @@ -11,9 +11,9 @@ // Diagnostic: undocumented-function use elp_ide_assists::helpers::unwrap_parens; use elp_ide_db::elp_base_db::FileId; +use elp_ide_db::text_edit::TextRange; use elp_syntax::ast; use elp_syntax::ast::Atom; -use elp_text_edit::TextRange; use fxhash::FxHashSet; use hir::AsName; use hir::FunctionDef; diff --git a/crates/ide/src/diagnostics/undocumented_module.rs b/crates/ide/src/diagnostics/undocumented_module.rs index 96a3a6c13a..f93b04d1bf 100644 --- a/crates/ide/src/diagnostics/undocumented_module.rs +++ b/crates/ide/src/diagnostics/undocumented_module.rs @@ -12,8 +12,8 @@ use elp_ide_assists::Assist; use elp_ide_assists::helpers; use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChangeBuilder; +use elp_ide_db::text_edit::TextRange; use elp_syntax::AstNode; -use elp_text_edit::TextRange; use hir::Semantic; use crate::diagnostics::DiagnosticCode; diff --git a/crates/ide/src/diagnostics/unspecific_include.rs b/crates/ide/src/diagnostics/unspecific_include.rs index 339874f6ee..c9a9644ae3 100644 --- a/crates/ide/src/diagnostics/unspecific_include.rs +++ b/crates/ide/src/diagnostics/unspecific_include.rs @@ -14,9 +14,9 @@ use elp_ide_db::elp_base_db::FileId; use elp_ide_db::elp_base_db::generated_file_include_lib; use elp_ide_db::elp_base_db::path_for_file; use elp_ide_db::source_change::SourceChange; +use elp_ide_db::text_edit::TextEdit; +use elp_ide_db::text_edit::TextRange; use elp_syntax::ast; -use elp_text_edit::TextEdit; -use elp_text_edit::TextRange; use hir::InFile; use hir::Semantic; diff --git a/crates/ide/src/diagnostics/unused_function_args.rs b/crates/ide/src/diagnostics/unused_function_args.rs index 53306052ce..ec4825e522 100644 --- a/crates/ide/src/diagnostics/unused_function_args.rs +++ b/crates/ide/src/diagnostics/unused_function_args.rs @@ -18,9 +18,9 @@ use std::collections::HashSet; use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChange; +use elp_ide_db::text_edit::TextEdit; +use elp_ide_db::text_edit::TextRange; use elp_syntax::ast; -use elp_text_edit::TextEdit; -use elp_text_edit::TextRange; use hir::AnyExpr; use hir::AnyExprId; use hir::FunctionClauseDef; diff --git a/crates/ide/src/diagnostics/unused_include.rs b/crates/ide/src/diagnostics/unused_include.rs index 959181a328..04a1a8490e 100644 --- a/crates/ide/src/diagnostics/unused_include.rs +++ b/crates/ide/src/diagnostics/unused_include.rs @@ -17,9 +17,9 @@ use elp_ide_db::SearchScope; use elp_ide_db::SymbolDefinition; use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChange; +use elp_ide_db::text_edit::TextEdit; use elp_syntax::SmolStr; use elp_syntax::ast::AstNode; -use elp_text_edit::TextEdit; use fxhash::FxHashMap; use fxhash::FxHashSet; use hir::FormIdx; diff --git a/crates/ide/src/diagnostics/unused_macro.rs b/crates/ide/src/diagnostics/unused_macro.rs index c25f5990a4..089371f85b 100644 --- a/crates/ide/src/diagnostics/unused_macro.rs +++ b/crates/ide/src/diagnostics/unused_macro.rs @@ -19,9 +19,9 @@ use elp_ide_assists::helpers::extend_range; use elp_ide_db::SymbolDefinition; use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChange; +use elp_ide_db::text_edit::TextEdit; use elp_syntax::AstNode; use elp_syntax::TextRange; -use elp_text_edit::TextEdit; use hir::Semantic; use crate::diagnostics::DiagnosticCode; diff --git a/crates/ide/src/diagnostics_collection.rs b/crates/ide/src/diagnostics_collection.rs index f15aed182c..4abe143dd9 100644 --- a/crates/ide/src/diagnostics_collection.rs +++ b/crates/ide/src/diagnostics_collection.rs @@ -266,8 +266,8 @@ mod tests { use std::iter::once; use elp_ide_db::elp_base_db::FileId; + use elp_ide_db::text_edit::TextRange; use elp_syntax::label::Label; - use elp_text_edit::TextRange; use fxhash::FxHashMap; use fxhash::FxHashSet; diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index 2642aed015..ab7731f619 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -175,11 +175,11 @@ pub(crate) mod tests { use elp_ide_db::RootDatabase; use elp_ide_db::elp_base_db::assert_eq_text; use elp_ide_db::elp_base_db::fixture::WithFixture as _; + 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 elp_text_edit::TextEdit; use hir::AnyExprId; use hir::InFile; use hir::Semantic; diff --git a/crates/ide/src/tests.rs b/crates/ide/src/tests.rs index 8d71b65dad..233a7a33f8 100644 --- a/crates/ide/src/tests.rs +++ b/crates/ide/src/tests.rs @@ -20,8 +20,8 @@ use elp_ide_db::elp_base_db::SourceDatabaseExt; use elp_ide_db::elp_base_db::assert_eq_text; use elp_ide_db::elp_base_db::fixture::WithFixture; use elp_ide_db::elp_base_db::remove_annotations; +use elp_ide_db::text_edit::TextRange; use elp_project_model::test_fixture::trim_indent; -use elp_text_edit::TextRange; use expect_test::Expect; use itertools::Itertools; diff --git a/crates/ide_assists/Cargo.toml b/crates/ide_assists/Cargo.toml index 167e1f4b11..b2277cd12d 100644 --- a/crates/ide_assists/Cargo.toml +++ b/crates/ide_assists/Cargo.toml @@ -9,7 +9,6 @@ workspace = true [dependencies] elp_ide_db.workspace = true elp_syntax.workspace = true -elp_text_edit.workspace = true hir.workspace = true cov-mark.workspace = true diff --git a/crates/ide_assists/src/handlers/add_doc.rs b/crates/ide_assists/src/handlers/add_doc.rs index b1f51a7b90..87a511fbff 100644 --- a/crates/ide_assists/src/handlers/add_doc.rs +++ b/crates/ide_assists/src/handlers/add_doc.rs @@ -12,9 +12,9 @@ use std::fmt::Write; use elp_ide_db::assists::AssistId; use elp_ide_db::assists::AssistKind; +use elp_ide_db::text_edit::TextSize; use elp_syntax::AstNode; use elp_syntax::ast; -use elp_text_edit::TextSize; use hir::FunctionDef; use crate::AssistContext; diff --git a/crates/ide_assists/src/handlers/flip_sep.rs b/crates/ide_assists/src/handlers/flip_sep.rs index 662e31b3e9..65ae663518 100644 --- a/crates/ide_assists/src/handlers/flip_sep.rs +++ b/crates/ide_assists/src/handlers/flip_sep.rs @@ -10,12 +10,12 @@ use elp_ide_db::assists::AssistId; use elp_ide_db::assists::AssistKind; +use elp_ide_db::text_edit::TextRange; use elp_syntax::AstNode; use elp_syntax::Direction; use elp_syntax::SyntaxKind; use elp_syntax::SyntaxToken; use elp_syntax::algo::non_trivia_sibling; -use elp_text_edit::TextRange; use fxhash::FxHashSet; use hir::InFile; diff --git a/crates/ide_assists/src/handlers/implement_behaviour.rs b/crates/ide_assists/src/handlers/implement_behaviour.rs index 384ac8a65b..2821e9214e 100644 --- a/crates/ide_assists/src/handlers/implement_behaviour.rs +++ b/crates/ide_assists/src/handlers/implement_behaviour.rs @@ -12,10 +12,10 @@ use std::cmp::max; use elp_ide_db::assists::AssistId; use elp_ide_db::assists::AssistKind; +use elp_ide_db::text_edit::TextRange; +use elp_ide_db::text_edit::TextSize; use elp_syntax::AstNode; use elp_syntax::ast::BehaviourAttribute; -use elp_text_edit::TextRange; -use elp_text_edit::TextSize; use hir::Callback; use hir::CallbackId; use hir::InFile; diff --git a/crates/ide_assists/src/handlers/inline_function.rs b/crates/ide_assists/src/handlers/inline_function.rs index 69dbb1c55d..055395af52 100644 --- a/crates/ide_assists/src/handlers/inline_function.rs +++ b/crates/ide_assists/src/handlers/inline_function.rs @@ -22,6 +22,7 @@ use elp_ide_db::elp_base_db::FileRange; use elp_ide_db::find_best_token; use elp_ide_db::helpers::get_call; use elp_ide_db::rename::SafetyChecks; +use elp_ide_db::text_edit::TextEdit; use elp_syntax::AstNode; use elp_syntax::NodeOrToken; use elp_syntax::SyntaxKind; @@ -32,7 +33,6 @@ use elp_syntax::TextSize; use elp_syntax::ast; use elp_syntax::ast::HasArity; use elp_syntax::ast::edit::IndentLevel; -use elp_text_edit::TextEdit; use fxhash::FxHashSet; use hir::FunctionDef; use hir::InFile; diff --git a/crates/ide_assists/src/helpers.rs b/crates/ide_assists/src/helpers.rs index bb2a38cf1b..121b1dda41 100644 --- a/crates/ide_assists/src/helpers.rs +++ b/crates/ide_assists/src/helpers.rs @@ -20,6 +20,8 @@ use elp_ide_db::elp_base_db::FileId; use elp_ide_db::helpers::top_insert_position; use elp_ide_db::rename::is_safe_function; use elp_ide_db::source_change::SourceChangeBuilder; +use elp_ide_db::text_edit::TextEdit; +use elp_ide_db::text_edit::TextSize; use elp_syntax::AstNode; use elp_syntax::AstPtr; use elp_syntax::Direction; @@ -34,8 +36,6 @@ use elp_syntax::algo; use elp_syntax::algo::skip_inline_comment; use elp_syntax::ast; use elp_syntax::match_ast; -use elp_text_edit::TextEdit; -use elp_text_edit::TextSize; use fxhash::FxHashSet; use hir::Attribute; use hir::Body; diff --git a/crates/ide_db/Cargo.toml b/crates/ide_db/Cargo.toml index 52cee9b67c..927391ba93 100644 --- a/crates/ide_db/Cargo.toml +++ b/crates/ide_db/Cargo.toml @@ -12,7 +12,6 @@ elp_eqwalizer.workspace = true elp_erlang_service.workspace = true elp_project_model.workspace = true elp_syntax.workspace = true -elp_text_edit.workspace = true elp_types_db.workspace = true hir.workspace = true @@ -37,6 +36,7 @@ stdx.workspace = true strum.workspace = true strum_macros.workspace = true tempfile.workspace = true +text-size.workspace = true toml.workspace = true tracing.workspace = true diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs index cad66d9e48..c92be86f5f 100644 --- a/crates/ide_db/src/helpers.rs +++ b/crates/ide_db/src/helpers.rs @@ -15,9 +15,9 @@ use elp_syntax::SourceFile; use elp_syntax::SyntaxKind; use elp_syntax::SyntaxNode; use elp_syntax::SyntaxToken; +use elp_syntax::TextSize; use elp_syntax::TokenAtOffset; use elp_syntax::ast; -use elp_text_edit::TextSize; use hir::FormList; #[derive(Clone, Copy, Debug, PartialEq, Eq)] diff --git a/crates/ide_db/src/lib.rs b/crates/ide_db/src/lib.rs index 1ef15d91ae..a00ea1f0d9 100644 --- a/crates/ide_db/src/lib.rs +++ b/crates/ide_db/src/lib.rs @@ -60,6 +60,7 @@ mod line_index; // @fb-only pub mod metadata; mod search; +pub mod text_edit; // --------------------------------------------------------------------- pub mod assists; @@ -420,8 +421,8 @@ impl TypedSemantic for RootDatabase { mod tests { use elp_base_db::SourceDatabase; use elp_base_db::fixture::WithFixture; - use elp_text_edit::TextRange; + use super::text_edit::TextRange; use crate::RootDatabase; #[test] diff --git a/crates/ide_db/src/rename.rs b/crates/ide_db/src/rename.rs index c25ee41e2a..3ea610554e 100644 --- a/crates/ide_db/src/rename.rs +++ b/crates/ide_db/src/rename.rs @@ -20,7 +20,6 @@ use elp_base_db::FileRange; use elp_syntax::AstNode; use elp_syntax::ast; use elp_syntax::ast::in_erlang_module; -use elp_text_edit::TextEdit; use hir::InFile; use hir::Semantic; @@ -28,6 +27,7 @@ use crate::SymbolDefinition; use crate::helpers::get_call; use crate::search::NameLike; use crate::source_change::SourceChange; +use crate::text_edit::TextEdit; pub type RenameResult = Result; diff --git a/crates/ide_db/src/source_change.rs b/crates/ide_db/src/source_change.rs index e387682aba..e4afbd8742 100644 --- a/crates/ide_db/src/source_change.rs +++ b/crates/ide_db/src/source_change.rs @@ -20,14 +20,14 @@ use std::mem; use elp_base_db::AnchoredPathBuf; use elp_base_db::FileId; use elp_syntax::SyntaxNode; -use elp_text_edit::TextEdit; -use elp_text_edit::TextEditBuilder; -use elp_text_edit::TextRange; -use elp_text_edit::TextSize; +use elp_syntax::TextRange; +use elp_syntax::TextSize; use fxhash::FxHashMap; use stdx::never; use crate::helpers::SnippetCap; +use crate::text_edit::TextEdit; +use crate::text_edit::TextEditBuilder; use crate::tree_diff::diff; #[derive(Default, Debug, Clone)] diff --git a/crates/text_edit/src/lib.rs b/crates/ide_db/src/text_edit.rs similarity index 99% rename from crates/text_edit/src/lib.rs rename to crates/ide_db/src/text_edit.rs index 42dac0f3ac..0410102569 100644 --- a/crates/text_edit/src/lib.rs +++ b/crates/ide_db/src/text_edit.rs @@ -15,7 +15,6 @@ //! `rust-analyzer` never mutates text itself and only sends diffs to clients, //! so `TextEdit` is the ultimate representation of the work done by //! rust-analyzer. - use std::cmp::max; use itertools::Itertools; diff --git a/crates/ide_db/src/tree_diff.rs b/crates/ide_db/src/tree_diff.rs index 70b29f4d35..5bca5c2ec2 100644 --- a/crates/ide_db/src/tree_diff.rs +++ b/crates/ide_db/src/tree_diff.rs @@ -15,10 +15,11 @@ use std::hash::BuildHasherDefault; use elp_syntax::NodeOrToken; use elp_syntax::SyntaxElement; use elp_syntax::SyntaxNode; -use elp_text_edit::TextEditBuilder; use fxhash::FxHashMap; use indexmap::IndexMap; +use crate::text_edit::TextEditBuilder; + type FxIndexMap = IndexMap>; #[derive(Debug, Hash, PartialEq, Eq)] @@ -176,11 +177,12 @@ mod tests { use elp_syntax::SourceFile; use elp_syntax::SyntaxElement; use elp_syntax::SyntaxKind; - use elp_text_edit::TextEdit; use expect_test::Expect; use expect_test::expect; use itertools::Itertools; + use crate::text_edit::TextEdit; + #[test] fn replace_node_token() { cov_mark::check!(diff_node_token_replace); diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml index 80ed01f28c..1f8d1aaa63 100644 --- a/crates/syntax/Cargo.toml +++ b/crates/syntax/Cargo.toml @@ -7,8 +7,6 @@ version.workspace = true workspace = true [dependencies] -elp_text_edit.workspace = true - eetf.workspace = true fxhash.workspace = true indexmap.workspace = true diff --git a/crates/text_edit/Cargo.toml b/crates/text_edit/Cargo.toml deleted file mode 100644 index 59d333adc7..0000000000 --- a/crates/text_edit/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "elp_text_edit" -edition.workspace = true -version.workspace = true - -[lints] -workspace = true - -[dependencies] -itertools.workspace = true -text-size.workspace = true From 5820634ce2c1e85ceac813430b04520c0cb544c8 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Fri, 21 Nov 2025 02:03:27 -0800 Subject: [PATCH 040/142] Allow severity method to receive &Semantic and FileId as arguments Summary: # Context We want to convert the `edoc` linter to use a trait. # Problem The `edoc` linter has a different `Severity` for source code and test files. # Solution We can extend the `severity` and `cli_severity` methods to accept `sema` and `file_id`. This way, we can customize the severity based on the file. The new arguments will be used by the edoc linter in a subsequent diff. Reviewed By: alanz Differential Revision: D87545262 fbshipit-source-id: d7546d7d77ec019c2a2edc846e69134e93a6fe90 --- crates/ide/src/diagnostics.rs | 14 +++++++------- .../ide/src/diagnostics/binary_string_to_sigil.rs | 2 +- .../src/diagnostics/could_be_a_string_literal.rs | 2 +- crates/ide/src/diagnostics/cross_node_eval.rs | 5 ++++- crates/ide/src/diagnostics/debugging_function.rs | 4 ++-- .../missing_compile_warn_missing_spec.rs | 2 +- crates/ide/src/diagnostics/no_error_logger.rs | 5 ++++- .../ide/src/diagnostics/undocumented_function.rs | 2 +- crates/ide/src/diagnostics/undocumented_module.rs | 2 +- .../diagnostics/unnecessary_fold_to_build_map.rs | 2 +- .../unnecessary_map_to_list_in_comprehension.rs | 2 +- 11 files changed, 24 insertions(+), 18 deletions(-) diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index f8932db145..de828e5086 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -511,13 +511,13 @@ pub(crate) trait Linter { fn description(&self) -> &'static str; // The severity for the lint issue. It defaults to `Warning`. - fn severity(&self) -> Severity { + fn severity(&self, _sema: &Semantic, _file_id: FileId) -> Severity { Severity::Warning } // For CLI, when using the --use-cli-severity flag. It defaults to `severity()` - fn cli_severity(&self) -> Severity { - self.severity() + fn cli_severity(&self, sema: &Semantic, file_id: FileId) -> Severity { + self.severity(sema, file_id) } // Specify if the linter issues can be suppressed via a `% elp:ignore` comment. @@ -1649,16 +1649,16 @@ fn diagnostics_from_linters( let severity = if let Some(lint_config) = config.lint_config.as_ref() { lint_config .get_severity_override(&linter.id()) - .unwrap_or_else(|| linter.severity()) + .unwrap_or_else(|| linter.severity(sema, file_id)) } else { - linter.severity() + linter.severity(sema, file_id) }; let cli_severity = if let Some(lint_config) = config.lint_config.as_ref() { lint_config .get_severity_override(&linter.id()) - .unwrap_or_else(|| linter.cli_severity()) + .unwrap_or_else(|| linter.cli_severity(sema, file_id)) } else { - linter.cli_severity() + linter.cli_severity(sema, file_id) }; match l { DiagnosticLinter::FunctionCall(function_linter) => { diff --git a/crates/ide/src/diagnostics/binary_string_to_sigil.rs b/crates/ide/src/diagnostics/binary_string_to_sigil.rs index 60a18aa7e1..91b08f9d1d 100644 --- a/crates/ide/src/diagnostics/binary_string_to_sigil.rs +++ b/crates/ide/src/diagnostics/binary_string_to_sigil.rs @@ -38,7 +38,7 @@ impl Linter for BinaryStringToSigilLinter { "Binary string can be written using sigil syntax." } - fn severity(&self) -> Severity { + fn severity(&self, _sema: &Semantic, _file_id: FileId) -> Severity { Severity::WeakWarning } diff --git a/crates/ide/src/diagnostics/could_be_a_string_literal.rs b/crates/ide/src/diagnostics/could_be_a_string_literal.rs index d939163222..03ea0e6274 100644 --- a/crates/ide/src/diagnostics/could_be_a_string_literal.rs +++ b/crates/ide/src/diagnostics/could_be_a_string_literal.rs @@ -39,7 +39,7 @@ impl Linter for CouldBeAStringLiteralLinter { "Could be rewritten as a literal." } - fn severity(&self) -> Severity { + fn severity(&self, _sema: &Semantic, _file_id: FileId) -> Severity { Severity::Information } } diff --git a/crates/ide/src/diagnostics/cross_node_eval.rs b/crates/ide/src/diagnostics/cross_node_eval.rs index 56cf2e1299..f50ca663e3 100644 --- a/crates/ide/src/diagnostics/cross_node_eval.rs +++ b/crates/ide/src/diagnostics/cross_node_eval.rs @@ -12,6 +12,9 @@ //! //! Return a diagnostic for rpc calls to remote nodes. +use elp_ide_db::elp_base_db::FileId; +use hir::Semantic; + use crate::codemod_helpers::FunctionMatch; use crate::diagnostics::DiagnosticCode; use crate::diagnostics::FunctionCallLinter; @@ -28,7 +31,7 @@ impl Linter for CrossNodeEvalLinter { fn description(&self) -> &'static str { "Production code must not use cross node eval (e.g. `rpc:call()`)" } - fn severity(&self) -> Severity { + fn severity(&self, _sema: &Semantic, _file_id: FileId) -> Severity { Severity::Error } fn should_process_test_files(&self) -> bool { diff --git a/crates/ide/src/diagnostics/debugging_function.rs b/crates/ide/src/diagnostics/debugging_function.rs index 185736d207..94c3d11364 100644 --- a/crates/ide/src/diagnostics/debugging_function.rs +++ b/crates/ide/src/diagnostics/debugging_function.rs @@ -34,10 +34,10 @@ impl Linter for NoDebuggingFunctionLinter { fn description(&self) -> &'static str { "Debugging functions should only be used during local debugging and usages should not be checked in." } - fn severity(&self) -> Severity { + fn severity(&self, _sema: &Semantic, _file_id: FileId) -> Severity { Severity::WeakWarning } - fn cli_severity(&self) -> Severity { + fn cli_severity(&self, _sema: &Semantic, _file_id: FileId) -> Severity { Severity::Error } fn should_process_generated_files(&self) -> bool { diff --git a/crates/ide/src/diagnostics/missing_compile_warn_missing_spec.rs b/crates/ide/src/diagnostics/missing_compile_warn_missing_spec.rs index 4f61ea5190..b1f24eb60f 100644 --- a/crates/ide/src/diagnostics/missing_compile_warn_missing_spec.rs +++ b/crates/ide/src/diagnostics/missing_compile_warn_missing_spec.rs @@ -53,7 +53,7 @@ impl Linter for MissingCompileWarnMissingSpec { fn description(&self) -> &'static str { "Please add \"-compile(warn_missing_spec_all).\" to the module. If exported functions are not all specced, they need to be specced." } - fn severity(&self) -> Severity { + fn severity(&self, _sema: &Semantic, _file_id: FileId) -> Severity { Severity::Error } fn should_process_test_files(&self) -> bool { diff --git a/crates/ide/src/diagnostics/no_error_logger.rs b/crates/ide/src/diagnostics/no_error_logger.rs index 08e94133d0..d587d3f3c7 100644 --- a/crates/ide/src/diagnostics/no_error_logger.rs +++ b/crates/ide/src/diagnostics/no_error_logger.rs @@ -8,6 +8,9 @@ * above-listed licenses. */ +use elp_ide_db::elp_base_db::FileId; +use hir::Semantic; + use crate::codemod_helpers::FunctionMatch; use crate::diagnostics::DiagnosticCode; use crate::diagnostics::FunctionCallLinter; @@ -23,7 +26,7 @@ impl Linter for NoErrorLoggerLinter { fn description(&self) -> &'static str { "The `error_logger` module is deprecated." } - fn severity(&self) -> Severity { + fn severity(&self, _sema: &Semantic, _file_id: FileId) -> Severity { Severity::Error } } diff --git a/crates/ide/src/diagnostics/undocumented_function.rs b/crates/ide/src/diagnostics/undocumented_function.rs index ab193cdcd5..f1d0aa06b5 100644 --- a/crates/ide/src/diagnostics/undocumented_function.rs +++ b/crates/ide/src/diagnostics/undocumented_function.rs @@ -39,7 +39,7 @@ impl Linter for UndocumentedFunctionLinter { "The function is non-trivial, exported, but not documented." } - fn severity(&self) -> Severity { + fn severity(&self, _sema: &Semantic, _file_id: FileId) -> Severity { Severity::WeakWarning } diff --git a/crates/ide/src/diagnostics/undocumented_module.rs b/crates/ide/src/diagnostics/undocumented_module.rs index f93b04d1bf..eeb4caf7b1 100644 --- a/crates/ide/src/diagnostics/undocumented_module.rs +++ b/crates/ide/src/diagnostics/undocumented_module.rs @@ -34,7 +34,7 @@ impl Linter for UndocumentedModuleLinter { "The module is not documented." } - fn severity(&self) -> Severity { + fn severity(&self, _sema: &Semantic, _file_id: FileId) -> Severity { Severity::WeakWarning } diff --git a/crates/ide/src/diagnostics/unnecessary_fold_to_build_map.rs b/crates/ide/src/diagnostics/unnecessary_fold_to_build_map.rs index 5fea930a9a..40f16da4d8 100644 --- a/crates/ide/src/diagnostics/unnecessary_fold_to_build_map.rs +++ b/crates/ide/src/diagnostics/unnecessary_fold_to_build_map.rs @@ -49,7 +49,7 @@ impl Linter for UnnecessaryFoldToBuildMapLinter { "Unnecessary explicit fold to construct map." } - fn severity(&self) -> Severity { + fn severity(&self, _sema: &Semantic, _file_id: FileId) -> Severity { Severity::WeakWarning } } diff --git a/crates/ide/src/diagnostics/unnecessary_map_to_list_in_comprehension.rs b/crates/ide/src/diagnostics/unnecessary_map_to_list_in_comprehension.rs index 6dbf3cd4c7..0c2ed5e140 100644 --- a/crates/ide/src/diagnostics/unnecessary_map_to_list_in_comprehension.rs +++ b/crates/ide/src/diagnostics/unnecessary_map_to_list_in_comprehension.rs @@ -33,7 +33,7 @@ impl Linter for UnnecessaryMapToListInComprehensionLinter { "Unnecessary intermediate list allocated." } - fn severity(&self) -> Severity { + fn severity(&self, _sema: &Semantic, _file_id: FileId) -> Severity { Severity::WeakWarning } } From 9860cc85f11a0c8681e6108e4e8ad8e170024ef5 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Fri, 21 Nov 2025 02:44:09 -0800 Subject: [PATCH 041/142] Publish OSS extension to OpenVSX marketplace Summary: As requested by several users: https://github.com/WhatsApp/erlang-language-platform/issues/127 We can publish the extension to the open-vsx.org marketplace, in addition to the Microsoft one. This enables using ELP in VSCodium. Reviewed By: alanz Differential Revision: D87625612 fbshipit-source-id: 8ee143cc3853b7ded04193802d32e164602be8d0 --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 471b9d2418..0a2bb2508f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -200,6 +200,8 @@ jobs: node-version: 20 - name: Install VSCE run: npm install -g vsce + - name: Install OVSX + run: npm install -g ovsx - name: Prepare VS Code Extension to host binaries (No Windows) if: matrix.os != 'windows' run: mkdir -p editors/code/bin @@ -285,3 +287,7 @@ jobs: working-directory: editors/code if: ${{ github.event_name == 'release' && matrix.vscode-publish && matrix.os != 'windows' }} run: vsce publish -p ${{ secrets.VSCE_PAT }} --packagePath erlang-language-platform.vsix + - name: Publish extension to OpenVSX marketplace + working-directory: editors/code + if: ${{ github.event_name == 'release' && matrix.vscode-publish && matrix.os != 'windows' }} + run: ovsx publish -p ${{ secrets.OVSX_PAT }} --packagePath erlang-language-platform.vsix From 5470044b61d95e02db0cabb0987b0a456bc5a858 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Fri, 21 Nov 2025 05:49:13 -0800 Subject: [PATCH 042/142] Bump js-yaml from 4.1.0 to 4.1.1 and glob from 10.4.5 to 10.5.0 Summary: Fix https://github.com/nodeca/js-yaml/security/advisories/GHSA-mh29-5h37-fv8m and https://github.com/isaacs/node-glob/security/advisories/GHSA-5j98-mcp5-4vw2 Reviewed By: jcpetruzza Differential Revision: D87634207 fbshipit-source-id: 50ef19e9b138252d43a631b2c9501ca9a764b74a --- editors/code/package-lock.json | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json index 1e62d62162..5e28184214 100644 --- a/editors/code/package-lock.json +++ b/editors/code/package-lock.json @@ -1448,10 +1448,11 @@ "dev": true }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -1624,9 +1625,9 @@ } }, "node_modules/mocha/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -3399,9 +3400,9 @@ "dev": true }, "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "requires": { "argparse": "^2.0.1" @@ -3532,9 +3533,9 @@ } }, "glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "requires": { "foreground-child": "^3.1.0", From fbdb3e1baf1e2c8ce7c9ee4bda979a3b8bf71c07 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Fri, 21 Nov 2025 08:12:17 -0800 Subject: [PATCH 043/142] ssr: Improve rendering of result Summary: Inside ELP an SSR match is exposed as a diagnostic. So the way it is output to the command line is as a diagnostic, giving location and the pattern that matched. This is not very useful when exploring a code base from the command line. This diff brings the ability to show the source for a matching diagnostic. The command line interface is modeled on the one for grep, so it should be familiar to users. It will automatically use colour if in a TTY context, but this can be controlled via CLI or environment variable. Reviewed By: TD5 Differential Revision: D87634862 fbshipit-source-id: 52604d21100274b2abba9e364acbb972914aa4ac --- crates/elp/src/bin/args.rs | 42 +++ crates/elp/src/bin/main.rs | 17 + crates/elp/src/bin/ssr_cli.rs | 355 +++++++++++++++++- ...e_elp_no_lint_specified_json_output.stdout | 1 + .../parse_elp_no_lint_specified_output.stdout | 3 +- .../resources/test/linter/ssr_ad_hoc.stdout | 3 +- .../test/linter/ssr_ad_hoc_cli.stdout | 3 +- .../test/linter/ssr_context_separator.stdout | 17 + .../test/linter/warnings_as_errors.stdout | 3 +- crates/elp/src/resources/test/ssr_help.stdout | 37 +- test_projects/linter/app_a/src/app_a.erl | 6 + 11 files changed, 463 insertions(+), 24 deletions(-) create mode 100644 crates/elp/src/resources/test/linter/ssr_context_separator.stdout diff --git a/crates/elp/src/bin/args.rs b/crates/elp/src/bin/args.rs index b80bae0ff6..93356a202b 100644 --- a/crates/elp/src/bin/args.rs +++ b/crates/elp/src/bin/args.rs @@ -389,6 +389,40 @@ pub struct Ssr { /// Dump a configuration snippet that can be put in .elp_lint.toml to match the given SSR patterns pub dump_config: bool, + /// Show source code context for matches + #[bpaf(long("show-source"))] + pub show_source: bool, + + /// Print NUM lines of leading context, enables --show-source + #[bpaf(short('B'), long("before-context"), argument("NUM"))] + pub before_context: Option, + + /// Print NUM lines of trailing context, enables --show-source + #[bpaf(short('A'), long("after-context"), argument("NUM"))] + pub after_context: Option, + + /// Print NUM lines of output context, enables --show-source + #[bpaf(short('C'), long("context"), argument("NUM"))] + pub context: Option, + + /// Print SEP on line between matches with context, enables --show-source + #[bpaf(long("group-separator"), argument("SEP"))] + pub group_separator: Option, + + /// Do not print separator for matches with context, enables --show-source + #[bpaf(long("no-group-separator"))] + pub no_group_separator: bool, + + /// Use markers to highlight the matching strings; WHEN is 'always', 'never', or 'auto' + #[bpaf( + long("color"), + long("colour"), + argument("WHEN"), + fallback(Some("auto".to_string())), + guard(color_guard, "Please use always, never, or auto") + )] + pub color: Option, + /// Report system memory usage and other statistics #[bpaf(long("report-system-stats"))] pub report_system_stats: bool, @@ -743,6 +777,14 @@ fn macros_guard(format: &Option) -> bool { } } +fn color_guard(color: &Option) -> bool { + match color { + None => true, + Some(c) if c == "always" || c == "never" || c == "auto" => true, + _ => false, + } +} + #[allow(clippy::ptr_arg)] // This is needed in the BPAF macros fn at_least_1(data: &Vec) -> bool { !data.is_empty() diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index 4cec310e22..fb551f4aee 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -2041,6 +2041,23 @@ mod tests { ) } + #[test] + fn lint_ssr_with_context_and_separator() { + simple_snapshot( + args_vec![ + "ssr", + "--context", "2", + "--group-separator", "====", + "--colour", "never" + "{_@A, _@B}", + ], + "linter", + expect_file!("../resources/test/linter/ssr_context_separator.stdout"), + true, + None, + ) + } + #[test] fn lint_ssr_as_cli_arg_multiple_patterns() { simple_snapshot( diff --git a/crates/elp/src/bin/ssr_cli.rs b/crates/elp/src/bin/ssr_cli.rs index f3b4c4339f..46143d1616 100644 --- a/crates/elp/src/bin/ssr_cli.rs +++ b/crates/elp/src/bin/ssr_cli.rs @@ -9,6 +9,7 @@ */ use std::fs; +use std::io::IsTerminal; use std::path::Path; use std::str; use std::time::SystemTime; @@ -30,6 +31,7 @@ use elp_ide::diagnostics::FallBackToAll; use elp_ide::diagnostics::LintConfig; use elp_ide::diagnostics::LintsFromConfig; use elp_ide::diagnostics::MatchSsr; +use elp_ide::elp_ide_db::LineCol; use elp_ide::elp_ide_db::elp_base_db::AbsPath; use elp_ide::elp_ide_db::elp_base_db::FileId; use elp_ide::elp_ide_db::elp_base_db::IncludeOtp; @@ -218,11 +220,84 @@ pub fn run_ssr( } } } else { - writeln!(cli, "Matches found in {} modules:", diags.len())?; + // Determine if we should show source context + // If any context flags are set, automatically enable source display + let show_source = args.show_source + || args.before_context.is_some() + || args.after_context.is_some() + || args.context.is_some() + || args.group_separator.is_some() + || args.no_group_separator; + let (before_lines, after_lines) = calculate_context_lines(args); + let has_context = before_lines > 0 || after_lines > 0; + let group_separator = should_show_group_separator(args, has_context && show_source); + let mut first_match = true; + + if !show_source { + writeln!(cli, "Matches found in {} modules:", diags.len())?; + } + for (name, file_id, diags) in &diags { - writeln!(cli, " {}: {}", name, diags.len())?; + if !show_source { + writeln!(cli, " {}: {}", name, diags.len())?; + } for diag in diags { - print_diagnostic(diag, &loaded.analysis(), *file_id, false, cli)?; + // Print group separator before each match (except the first) if showing source with context + if show_source + && !first_match + && let Some(ref sep) = group_separator + { + writeln!(cli, "{}", sep)?; + } + first_match = false; + + // Get relative path for diagnostic output + let vfs_path = loaded.vfs.file_path(*file_id); + let analysis = loaded.analysis(); + let root_path = &analysis + .project_data(*file_id) + .unwrap_or_else(|_err| panic!("could not find project data")) + .unwrap_or_else(|| panic!("could not find project data")) + .root_dir; + let relative_path = reporting::get_relative_path(root_path, vfs_path); + + // Only show path when showing source context + let path_to_show = if show_source { + Some(relative_path) + } else { + None + }; + print_diagnostic(diag, &loaded.analysis(), *file_id, path_to_show, false, cli)?; + + // Only show source context if --show-source or --show-source-markers is set + if show_source { + // Determine if color should be used + let should_show_color = should_use_color(args); + + // When not using color, always use markers to indicate the match + if should_show_color { + print_source_with_context( + diag, + &loaded.analysis(), + *file_id, + before_lines, + after_lines, + true, // use_color + cli, + )?; + } else { + // No color: use markers to indicate the match + print_source_with_context_markers( + diag, + &loaded.analysis(), + *file_id, + before_lines, + after_lines, + cli, + )?; + } + writeln!(cli)?; // Add blank line after source context + } } } } @@ -299,7 +374,6 @@ fn do_parse_one( config .lints_from_config .get_diagnostics(&mut diags, &sema, file_id); - sema.parse(file_id); diags })?; @@ -315,11 +389,17 @@ fn print_diagnostic( diag: &diagnostics::Diagnostic, analysis: &Analysis, file_id: FileId, + path: Option<&Path>, use_cli_severity: bool, cli: &mut dyn Cli, ) -> Result<(), anyhow::Error> { let line_index = analysis.line_index(file_id)?; - writeln!(cli, " {}", diag.print(&line_index, use_cli_severity))?; + let diag_str = diag.print(&line_index, use_cli_severity); + if let Some(path) = path { + writeln!(cli, "{}:{}", path.display(), diag_str)?; + } else { + writeln!(cli, " {}", diag_str)?; + } Ok(()) } @@ -343,3 +423,268 @@ fn print_diagnostic_json( )?; Ok(()) } + +/// Print a line with color highlighting +fn print_line_with_color( + line_num: usize, + line_content: &str, + is_match_line: bool, + start: &LineCol, + end: &LineCol, + current_line: u32, + cli: &mut dyn Cli, +) -> Result<(), anyhow::Error> { + // Line number in gray + write!(cli, "\x1b[90m{:4} |\x1b[0m ", line_num)?; + + if !is_match_line { + // Non-match line: print normally + writeln!(cli, "{}", line_content)?; + } else { + // Match line: highlight the matched portion + if current_line == start.line && current_line == end.line { + // Single-line match + let start_col = start.col_utf16 as usize; + let end_col = end.col_utf16 as usize; + + let before = &line_content[..start_col.min(line_content.len())]; + let matched = + &line_content[start_col.min(line_content.len())..end_col.min(line_content.len())]; + let after = &line_content[end_col.min(line_content.len())..]; + + write!(cli, "{}", before)?; + write!(cli, "\x1b[91;1m{}\x1b[0m", matched)?; // Red bold + writeln!(cli, "{}", after)?; + } else if current_line == start.line { + // First line of multi-line match + let start_col = start.col_utf16 as usize; + let before = &line_content[..start_col.min(line_content.len())]; + let matched = &line_content[start_col.min(line_content.len())..]; + + write!(cli, "{}", before)?; + writeln!(cli, "\x1b[91;1m{}\x1b[0m", matched)?; // Red bold + } else if current_line == end.line { + // Last line of multi-line match + let end_col = end.col_utf16 as usize; + let matched = &line_content[..end_col.min(line_content.len())]; + let after = &line_content[end_col.min(line_content.len())..]; + + write!(cli, "\x1b[91;1m{}\x1b[0m", matched)?; // Red bold + writeln!(cli, "{}", after)?; + } else { + // Middle line of multi-line match + writeln!(cli, "\x1b[91;1m{}\x1b[0m", line_content)?; // Red bold + } + } + + Ok(()) +} + +/// Calculate context lines from the new grep-style arguments +fn calculate_context_lines(args: &Ssr) -> (usize, usize) { + // -C/--context takes precedence and sets both before and after + if let Some(context) = args.context { + return (context, context); + } + + // Otherwise use individual before/after values, defaulting to 0 + let before = args.before_context.unwrap_or(0); + let after = args.after_context.unwrap_or(0); + (before, after) +} + +/// Determine if a group separator should be shown +fn should_show_group_separator(args: &Ssr, has_context: bool) -> Option { + // If --no-group-separator is set, don't show separator + if args.no_group_separator { + return None; + } + + // Only show separators if there's context to separate + if !has_context { + return None; + } + + // Use custom separator if provided, otherwise default to "--" + Some( + args.group_separator + .clone() + .unwrap_or_else(|| "--".to_string()), + ) +} + +/// Determine if color should be used based on the new --color argument +fn should_use_color(args: &Ssr) -> bool { + match args.color.as_deref() { + Some("always") => true, + Some("never") => false, + Some("auto") | None => { + // Check NO_COLOR environment variable - if set (regardless of value), disable color + // Also check if stdout is connected to a TTY + std::env::var("NO_COLOR").is_err() && std::io::stdout().is_terminal() + } + _ => false, // Should be caught by the guard, but handle anyway + } +} + +/// Print source code context with the specified before/after context lines +fn print_source_with_context( + diag: &diagnostics::Diagnostic, + analysis: &Analysis, + file_id: FileId, + before_lines: usize, + after_lines: usize, + use_color: bool, + cli: &mut dyn Cli, +) -> Result<(), anyhow::Error> { + let line_index = analysis.line_index(file_id)?; + let source = &analysis.file_text(file_id)?; + + let range = diag.range; + let start = line_index.line_col(range.start()); + let end = line_index.line_col(range.end()); + + let lines: Vec<&str> = source.lines().collect(); + let total_lines = lines.len(); + + // Calculate the range of lines to display + let first_line = start.line.saturating_sub(before_lines as u32) as usize; + let last_line = ((end.line + after_lines as u32 + 1) as usize).min(total_lines); + + // Display the source context + for line_idx in first_line..last_line { + let line_num = line_idx + 1; + let line_content = lines.get(line_idx).unwrap_or(&""); + + // Check if this line contains part of the match + let is_match_line = line_idx >= start.line as usize && line_idx <= end.line as usize; + + if use_color { + print_line_with_color( + line_num, + line_content, + is_match_line, + &start, + &end, + line_idx as u32, + cli, + )?; + } else { + // Just print the line without any highlighting + write!(cli, "{:4} | ", line_num)?; + writeln!(cli, "{}", line_content)?; + } + } + + Ok(()) +} + +/// Print source code context with text markers +fn print_source_with_context_markers( + diag: &diagnostics::Diagnostic, + analysis: &Analysis, + file_id: FileId, + before_lines: usize, + after_lines: usize, + cli: &mut dyn Cli, +) -> Result<(), anyhow::Error> { + let line_index = analysis.line_index(file_id)?; + let source = &analysis.file_text(file_id)?; + + let range = diag.range; + let start = line_index.line_col(range.start()); + let end = line_index.line_col(range.end()); + + let lines: Vec<&str> = source.lines().collect(); + let total_lines = lines.len(); + + // Calculate the range of lines to display + let first_line = start.line.saturating_sub(before_lines as u32) as usize; + let last_line = ((end.line + after_lines as u32 + 1) as usize).min(total_lines); + + // Display the source context + for line_idx in first_line..last_line { + let line_num = line_idx + 1; + let line_content = lines.get(line_idx).unwrap_or(&""); + + // Check if this line contains part of the match + let is_match_line = line_idx >= start.line as usize && line_idx <= end.line as usize; + + print_line_with_markers( + line_num, + line_content, + is_match_line, + &start, + &end, + line_idx as u32, + cli, + )?; + } + + Ok(()) +} + +/// Print a line with text markers (like diagnostic carets) +fn print_line_with_markers( + line_num: usize, + line_content: &str, + is_match_line: bool, + start: &LineCol, + end: &LineCol, + current_line: u32, + cli: &mut dyn Cli, +) -> Result<(), anyhow::Error> { + // Line number + write!(cli, "{:4} | ", line_num)?; + writeln!(cli, "{}", line_content)?; + + if is_match_line { + // Print marker line with ^^^ under the match + write!(cli, " | ")?; // Indent to match line content + + if current_line == start.line && current_line == end.line { + // Single-line match + let start_col = start.col_utf16 as usize; + let end_col = end.col_utf16 as usize; + let marker_len = (end_col - start_col).max(1); + + // Spaces before the marker + for _ in 0..start_col { + write!(cli, " ")?; + } + // Marker carets + for _ in 0..marker_len { + write!(cli, "^")?; + } + writeln!(cli)?; + } else if current_line == start.line { + // First line of multi-line match + let start_col = start.col_utf16 as usize; + let marker_len = line_content.len().saturating_sub(start_col).max(1); + + for _ in 0..start_col { + write!(cli, " ")?; + } + for _ in 0..marker_len { + write!(cli, "^")?; + } + writeln!(cli)?; + } else if current_line == end.line { + // Last line of multi-line match + let end_col = end.col_utf16 as usize; + + for _ in 0..end_col { + write!(cli, "^")?; + } + writeln!(cli)?; + } else { + // Middle line of multi-line match + for _ in 0..line_content.len() { + write!(cli, "^")?; + } + writeln!(cli)?; + } + } + + Ok(()) +} diff --git a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_json_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_json_output.stdout index 0385484785..7608728f1e 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_json_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_json_output.stdout @@ -4,6 +4,7 @@ {"path":"app_a/src/app_a.erl","line":13,"char":5,"code":"ELP","severity":"warning","name":"W0026 (unexported_function)","original":null,"replacement":null,"description":"Function 'app_a:baz/2' is not exported.\n\nFor more information see: /erlang-error-index/w/W0026","docPath":"website/docs/erlang-error-index/w/W0026.md"} {"path":"app_a/src/app_a.erl","line":12,"char":1,"code":"ELP","severity":"warning","name":"L1230 (L1230)","original":null,"replacement":null,"description":"function bar/0 is unused\n\nFor more information see: /erlang-error-index/l/L1230","docPath":null} {"path":"app_a/src/app_a.erl","line":16,"char":1,"code":"ELP","severity":"warning","name":"L1230 (L1230)","original":null,"replacement":null,"description":"function baz/2 is unused\n\nFor more information see: /erlang-error-index/l/L1230","docPath":null} +{"path":"app_a/src/app_a.erl","line":20,"char":1,"code":"ELP","severity":"warning","name":"L1230 (L1230)","original":null,"replacement":null,"description":"function bat/2 is unused\n\nFor more information see: /erlang-error-index/l/L1230","docPath":null} {"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_b/src/app_b.erl","line":5,"char":5,"code":"ELP","severity":"warning","name":"W0011 (application_get_env)","original":null,"replacement":null,"description":"module `app_b` belongs to app `app_b`, 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_b/src/app_b_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} diff --git a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout index b2adfefad5..879f0e024c 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout @@ -1,12 +1,13 @@ Reporting all diagnostics codes Diagnostics reported in 7 modules: - app_a: 6 + app_a: 7 5:5-5:35::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` 9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food' 8:7-8:8::[Warning] [W0018] Unexpected ';' 13:5-13:14::[Warning] [W0026] Function 'app_a:baz/2' is not exported. 12:1-12:4::[Warning] [L1230] function bar/0 is unused 16:1-16:4::[Warning] [L1230] function baz/2 is unused + 20:1-20:4::[Warning] [L1230] function bat/2 is unused app_a_unused_param: 1 5:5-5:6::[Warning] [L1268] variable 'X' is unused app_b: 1 diff --git a/crates/elp/src/resources/test/linter/ssr_ad_hoc.stdout b/crates/elp/src/resources/test/linter/ssr_ad_hoc.stdout index 9d331b77c3..b081749a91 100644 --- a/crates/elp/src/resources/test/linter/ssr_ad_hoc.stdout +++ b/crates/elp/src/resources/test/linter/ssr_ad_hoc.stdout @@ -1,3 +1,4 @@ Diagnostics reported in 1 modules: - app_a: 1 + app_a: 2 16:13-16:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {_@A, _@B}. + 20:13-20:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {_@A, _@B}. diff --git a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli.stdout b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli.stdout index 4560a933ae..7e57212d83 100644 --- a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli.stdout +++ b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli.stdout @@ -1,3 +1,4 @@ Matches found in 1 modules: - app_a: 1 + app_a: 2 16:13-16:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {_@A, _@B}. + 20:13-20:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {_@A, _@B}. diff --git a/crates/elp/src/resources/test/linter/ssr_context_separator.stdout b/crates/elp/src/resources/test/linter/ssr_context_separator.stdout new file mode 100644 index 0000000000..99ac995b14 --- /dev/null +++ b/crates/elp/src/resources/test/linter/ssr_context_separator.stdout @@ -0,0 +1,17 @@ +app_a/src/app_a.erl:16:13-16:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {_@A, _@B}. + 14 | app_b:application_env_error(). + 15 | + 16 | baz(A,B) -> {A,B}. + | ^^^^^ + 17 | + 18 | % Some more context, see if it renders + +==== +app_a/src/app_a.erl:20:13-20:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {_@A, _@B}. + 18 | % Some more context, see if it renders + 19 | + 20 | bat(A,B) -> {A,B}. + | ^^^^^ + 21 | + 22 | % And some more context, see if it renders + diff --git a/crates/elp/src/resources/test/linter/warnings_as_errors.stdout b/crates/elp/src/resources/test/linter/warnings_as_errors.stdout index 78880f4472..0625ceb723 100644 --- a/crates/elp/src/resources/test/linter/warnings_as_errors.stdout +++ b/crates/elp/src/resources/test/linter/warnings_as_errors.stdout @@ -1,12 +1,13 @@ Reporting all diagnostics codes Diagnostics reported in 7 modules: - app_a: 6 + app_a: 7 5:5-5:35::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` 9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food' 8:7-8:8::[Warning] [W0018] Unexpected ';' 13:5-13:14::[Warning] [W0026] Function 'app_a:baz/2' is not exported. 12:1-12:4::[Error] [L1230] function bar/0 is unused 16:1-16:4::[Error] [L1230] function baz/2 is unused + 20:1-20:4::[Error] [L1230] function bat/2 is unused app_a_unused_param: 1 5:5-5:6::[Error] [L1268] variable 'X' is unused app_b: 1 diff --git a/crates/elp/src/resources/test/ssr_help.stdout b/crates/elp/src/resources/test/ssr_help.stdout index 7e5d3f4559..982d2327e6 100644 --- a/crates/elp/src/resources/test/ssr_help.stdout +++ b/crates/elp/src/resources/test/ssr_help.stdout @@ -1,20 +1,27 @@ -Usage: [--project PROJECT] [--module MODULE] [--app APP] [--file FILE] [--rebar] [--as PROFILE] [--include-generated] [--include-tests] [[--format FORMAT]] [[--macros STRATEGY]] [--parens] [--dump-config] [--report-system-stats] ... +Usage: [--project PROJECT] [--module MODULE] [--app APP] [--file FILE] [--rebar] [--as PROFILE] [--include-generated] [--include-tests] [[--format FORMAT]] [[--macros STRATEGY]] [--parens] [--dump-config] [--show-source] [-B NUM] [-A NUM] [-C NUM] [--group-separator SEP] [--no-group-separator] [[--color WHEN]] [--report-system-stats] ... Available positional items: SSR specs to use Available options: - --project Path to directory with project, or to a JSON file (defaults to `.`) - --module Parse a single module from the project, not the entire project. - --app Parse a single application from the project, not the entire project. - --file Parse a single file from the project, not the entire project. This can be an include file or escript, etc. - --rebar Run with rebar - --as Rebar3 profile to pickup (default is test) - --include-generated Also generate diagnostics for generated files - --include-tests Also generate diagnostics for test files - --format Show diagnostics in JSON format - --macros Macro expansion strategy: expand | no-expand | visible-expand (default expand) - --parens Explicitly match parentheses. If omitted, they are ignored. - --dump-config Dump a configuration snippet that can be put in .elp_lint.toml to match the given SSR patterns - --report-system-stats Report system memory usage and other statistics - -h, --help Prints help information + --project Path to directory with project, or to a JSON file (defaults to `.`) + --module Parse a single module from the project, not the entire project. + --app Parse a single application from the project, not the entire project. + --file Parse a single file from the project, not the entire project. This can be an include file or escript, etc. + --rebar Run with rebar + --as Rebar3 profile to pickup (default is test) + --include-generated Also generate diagnostics for generated files + --include-tests Also generate diagnostics for test files + --format Show diagnostics in JSON format + --macros Macro expansion strategy: expand | no-expand | visible-expand (default expand) + --parens Explicitly match parentheses. If omitted, they are ignored. + --dump-config Dump a configuration snippet that can be put in .elp_lint.toml to match the given SSR patterns + --show-source Show source code context for matches + -B, --before-context Print NUM lines of leading context, enables --show-source + -A, --after-context Print NUM lines of trailing context, enables --show-source + -C, --context Print NUM lines of output context, enables --show-source + --group-separator Print SEP on line between matches with context, enables --show-source + --no-group-separator Do not print separator for matches with context, enables --show-source + --color Use markers to highlight the matching strings; WHEN is 'always', 'never', or 'auto' + --report-system-stats Report system memory usage and other statistics + -h, --help Prints help information diff --git a/test_projects/linter/app_a/src/app_a.erl b/test_projects/linter/app_a/src/app_a.erl index 167b8e6d6d..86593395dd 100644 --- a/test_projects/linter/app_a/src/app_a.erl +++ b/test_projects/linter/app_a/src/app_a.erl @@ -14,3 +14,9 @@ bar() -> app_b:application_env_error(). baz(A,B) -> {A,B}. + +% Some more context, see if it renders + +bat(A,B) -> {A,B}. + +% And some more context, see if it renders From 0c2b43eb27b5c9a476abb83296f4ef4ad4b17d88 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Fri, 21 Nov 2025 10:19:14 -0800 Subject: [PATCH 044/142] ssr: add test explicitly using colour output Summary: As title Reviewed By: TD5 Differential Revision: D87651912 fbshipit-source-id: 347be29dbb6f0dd71396dbb404b2ac98a9dbabc0 --- crates/elp/src/bin/main.rs | 17 +++++++++++++++++ .../linter/ssr_context_separator_color.stdout | 18 ++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 crates/elp/src/resources/test/linter/ssr_context_separator_color.stdout diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index fb551f4aee..0684c50228 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -2058,6 +2058,23 @@ mod tests { ) } + #[test] + fn lint_ssr_with_context_and_separator_color() { + simple_snapshot( + args_vec![ + "ssr", + "--context", "2", + "--group-separator", "====", + "--colour", "always" + "{_@A, _@B}", + ], + "linter", + expect_file!("../resources/test/linter/ssr_context_separator_color.stdout"), + true, + None, + ) + } + #[test] fn lint_ssr_as_cli_arg_multiple_patterns() { simple_snapshot( diff --git a/crates/elp/src/resources/test/linter/ssr_context_separator_color.stdout b/crates/elp/src/resources/test/linter/ssr_context_separator_color.stdout new file mode 100644 index 0000000000..119d62a52b --- /dev/null +++ b/crates/elp/src/resources/test/linter/ssr_context_separator_color.stdout @@ -0,0 +1,18 @@ + app_a: 2 +app_a/src/app_a.erl:16:13-16:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {_@A, _@B}. + 14 | app_b:application_env_error(). + 15 | + 16 | baz(A,B) -> {A,B}. + 17 | + 18 | % Some more context, see if it renders + +==== +app_a/src/app_a.erl:20:13-20:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {_@A, _@B}. + 18 | % Some more context, see if it renders + 19 | + 20 | bat(A,B) -> {A,B}. + 21 | + 22 | % And some more context, see if it renders + + +Matches found in 1 modules From bc2c3f05d4eff3681364616b5f9423711765e62d Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Sat, 22 Nov 2025 06:22:58 -0800 Subject: [PATCH 045/142] Require glob >=10.5.0 Summary: Solves [GHSA-5j98-mcp5-4vw2](https://github.com/isaacs/node-glob/security/advisories/GHSA-5j98-mcp5-4vw2?fbclid=IwY2xjawONHSxleHRuA2FlbQIxMQBicmlkETFmTTVyb3hBUmNTbUNLeDlrc3J0YwZhcHBfaWQBMAABHt4eAVx-Yk7Cnshn0Hqrh-vQ8xKvHyVaF7_ovUMQhWSrRWN8um2ZimI4Vfmu_aem_B4s_KCafXrEOOcNU-AnJRA). Reviewed By: TheGeorge Differential Revision: D87628344 fbshipit-source-id: ab6ff51e5ddade4b93318606d573e6f7e5d043d5 --- website/package.json | 3 +- website/yarn.lock | 83 ++++++++++++++++++++------------------------ 2 files changed, 39 insertions(+), 47 deletions(-) diff --git a/website/package.json b/website/package.json index 5d813e2035..f8f738328c 100644 --- a/website/package.json +++ b/website/package.json @@ -62,6 +62,7 @@ "brace-expansion": ">=1.1.12", "webpack-dev-server": ">=5.2.1", "on-headers": ">=1.1.0", - "mermaid": ">=11.10.0" + "mermaid": ">=11.10.0", + "glob": ">=10.5.0" } } diff --git a/website/yarn.lock b/website/yarn.lock index 1aa091c893..182874330e 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -2059,6 +2059,18 @@ local-pkg "^1.0.0" mlly "^1.7.4" +"@isaacs/balanced-match@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29" + integrity sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ== + +"@isaacs/brace-expansion@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz#4b3dabab7d8e75a429414a96bd67bf4c1d13e0f3" + integrity sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA== + dependencies: + "@isaacs/balanced-match" "^4.0.1" + "@jest/schemas@^29.6.3": version "29.6.3" resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" @@ -3636,7 +3648,7 @@ boxen@^7.0.0: widest-line "^4.0.1" wrap-ansi "^8.1.0" -brace-expansion@>=1.1.12, brace-expansion@^1.1.7, brace-expansion@^2.0.1: +brace-expansion@>=1.1.12, brace-expansion@^1.1.7: version "4.0.1" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-4.0.1.tgz#3387e13eaa2992025d05ea47308f77e4a8dedd1e" integrity sha512-YClrbvTCXGe70pU2JiEiPLYXO9gQkyxYeKpJIQHVS/gOs6EWMQP2RYBwjFLNT322Ji8TOC3IMPfsYCedNpzKfA== @@ -4187,7 +4199,7 @@ cosmiconfig@^8.1.3, cosmiconfig@^8.3.5: parse-json "^5.2.0" path-type "^4.0.0" -cross-spawn@>=6.0.6, cross-spawn@^7.0.0, cross-spawn@^7.0.3: +cross-spawn@>=6.0.6, cross-spawn@^7.0.3: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -5519,14 +5531,6 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" -foreground-child@^3.1.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77" - integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg== - dependencies: - cross-spawn "^7.0.0" - signal-exit "^4.0.1" - form-data-encoder@^2.1.2: version "2.1.4" resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-2.1.4.tgz#261ea35d2a70d48d30ec7a9603130fa5515e9cd5" @@ -5650,17 +5654,14 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@^10.3.10: - version "10.4.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" - integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== +glob@>=10.5.0, glob@^10.3.10: + version "13.0.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-13.0.0.tgz#9d9233a4a274fc28ef7adce5508b7ef6237a1be3" + integrity sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA== dependencies: - foreground-child "^3.1.0" - jackspeak "^3.1.2" - minimatch "^9.0.4" + minimatch "^10.1.1" minipass "^7.1.2" - package-json-from-dist "^1.0.0" - path-scurry "^1.11.1" + path-scurry "^2.0.0" global-dirs@^3.0.0: version "3.0.1" @@ -6593,7 +6594,7 @@ isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== -jackspeak@2.1.1, jackspeak@^3.1.2: +jackspeak@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.1.1.tgz#2a42db4cfbb7e55433c28b6f75d8b796af9669cd" integrity sha512-juf9stUEwUaILepraGOWIJTLwg48bUnBmRqd2ln2Os1sW987zeoj/hzhbvRB95oMuS2ZTpjULmdwHNX4rzZIZw== @@ -6887,10 +6888,10 @@ lowercase-keys@^3.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2" integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ== -lru-cache@^10.2.0: - version "10.4.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" - integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== +lru-cache@^11.0.0: + version "11.2.2" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.2.2.tgz#40fd37edffcfae4b2940379c0722dc6eeaa75f24" + integrity sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg== lru-cache@^5.1.1: version "5.1.1" @@ -8254,19 +8255,19 @@ minimatch@3.1.2, minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimatch@^9.0.4: - version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== +minimatch@^10.1.1: + version "10.1.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.1.1.tgz#e6e61b9b0c1dcab116b5a7d1458e8b6ae9e73a55" + integrity sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ== dependencies: - brace-expansion "^2.0.1" + "@isaacs/brace-expansion" "^5.0.0" minimist@^1.2.0: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: +minipass@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== @@ -8585,11 +8586,6 @@ p-timeout@^3.2.0: dependencies: p-finally "^1.0.0" -package-json-from-dist@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" - integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== - package-json@^8.1.0: version "8.1.1" resolved "https://registry.yarnpkg.com/package-json/-/package-json-8.1.1.tgz#3e9948e43df40d1e8e78a85485f1070bf8f03dc8" @@ -8724,13 +8720,13 @@ path-root@^0.1.1: dependencies: path-root-regex "^0.1.0" -path-scurry@^1.11.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" - integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== +path-scurry@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.1.tgz#4b6572376cfd8b811fca9cd1f5c24b3cbac0fe10" + integrity sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA== dependencies: - lru-cache "^10.2.0" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + lru-cache "^11.0.0" + minipass "^7.1.2" path-to-regexp@0.1.12: version "0.1.12" @@ -10297,11 +10293,6 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -signal-exit@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - sirv@^2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.4.tgz#5dd9a725c578e34e449f332703eb2a74e46a29b0" From 473c0f1c5620f1560381f3e64b25b9385e4262d9 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Mon, 24 Nov 2025 02:55:57 -0800 Subject: [PATCH 046/142] Fix failing test Summary: As title Reviewed By: ilya-klyuchnikov, michalmuskala Differential Revision: D87765456 fbshipit-source-id: deb9fc32f4bc547215619e43fecdaecf7ceb5a0f --- .../resources/test/linter/ssr_context_separator_color.stdout | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/elp/src/resources/test/linter/ssr_context_separator_color.stdout b/crates/elp/src/resources/test/linter/ssr_context_separator_color.stdout index 119d62a52b..139250e2f2 100644 --- a/crates/elp/src/resources/test/linter/ssr_context_separator_color.stdout +++ b/crates/elp/src/resources/test/linter/ssr_context_separator_color.stdout @@ -1,4 +1,3 @@ - app_a: 2 app_a/src/app_a.erl:16:13-16:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {_@A, _@B}.  14 | app_b:application_env_error().  15 | @@ -14,5 +13,3 @@ app_a/src/app_a.erl:20:13-20:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern m  21 |  22 | % And some more context, see if it renders - -Matches found in 1 modules From 0de513221f8ea2eaa401de92c04d11cbccc8a5a9 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Mon, 24 Nov 2025 05:04:16 -0800 Subject: [PATCH 047/142] ssr: stream command line match results Summary: It is annoying to wait for the entire body of code to be processed before seing results. Since the ssr search is based purely on the ast of a module, the search is embarrasingle trivial. So report the results as they are found. This does mean the summary statistic has to come at the end. Reviewed By: TD5 Differential Revision: D87651432 fbshipit-source-id: b51b7bd94f8069113154adac9b1bf68f9b7351b3 --- crates/elp/src/bin/ssr_cli.rs | 295 ++++++++++-------- .../test/linter/ssr_ad_hoc_cli.stdout | 3 +- .../ssr_ad_hoc_cli_macros_expand.stdout | 3 +- .../ssr_ad_hoc_cli_macros_no_expand.stdout | 3 +- ...sr_ad_hoc_cli_macros_visible_expand.stdout | 3 +- .../linter/ssr_ad_hoc_cli_multiple.stdout | 3 +- .../ssr_ad_hoc_cli_parens_invisible.stdout | 3 +- .../ssr_ad_hoc_cli_parens_visible.stdout | 3 +- .../test/linter/ssr_context_separator.stdout | 3 + 9 files changed, 182 insertions(+), 137 deletions(-) diff --git a/crates/elp/src/bin/ssr_cli.rs b/crates/elp/src/bin/ssr_cli.rs index 46143d1616..9a080a4907 100644 --- a/crates/elp/src/bin/ssr_cli.rs +++ b/crates/elp/src/bin/ssr_cli.rs @@ -12,10 +12,12 @@ use std::fs; use std::io::IsTerminal; use std::path::Path; use std::str; +use std::thread; use std::time::SystemTime; use anyhow::Result; use anyhow::bail; +use crossbeam_channel::unbounded; use elp::build::load; use elp::build::types::LoadResult; use elp::cli::Cli; @@ -44,7 +46,6 @@ use elp_project_model::AppType; use elp_project_model::DiscoverConfig; use elp_project_model::buck::BuckQueryConfig; use hir::Semantic; -use indicatif::ParallelProgressIterator; use paths::Utf8PathBuf; use rayon::prelude::ParallelBridge; use rayon::prelude::ParallelIterator; @@ -181,8 +182,22 @@ pub fn run_ssr( }, }; - let mut diags = match (file_id, name) { - (None, _) => do_parse_all(cli, &analysis, &loaded.project_id, diagnostics_config, args)?, + let mut match_count = 0; + + match (file_id, name) { + (None, _) => { + // Streaming case: process all modules + let project_id = loaded.project_id; + do_parse_all_streaming( + cli, + &analysis, + &project_id, + diagnostics_config, + args, + loaded, + &mut match_count, + )?; + } (Some(file_id), Some(name)) => { if let Some(app) = &args.app && let Ok(Some(file_app)) = analysis.file_app_name(file_id) @@ -190,115 +205,170 @@ pub fn run_ssr( { panic!("Module {} does not belong to app {}", name.as_str(), app) } - do_parse_one(&analysis, diagnostics_config, file_id, &name, args)? - .map_or(vec![], |x| vec![x]) + if let Some(diag) = do_parse_one(&analysis, diagnostics_config, file_id, &name, args)? { + match_count = 1; + print_single_result(cli, loaded, &diag, args)?; + } } (Some(file_id), _) => { panic!("Could not get name from file_id for {file_id:?}") } }; - if diags.is_empty() { + + if match_count == 0 { if args.is_format_normal() { writeln!(cli, "No matches found")?; } - } else { - diags.sort_by(|(a, _, _), (b, _, _)| a.cmp(b)); - if args.is_format_json() { - for (_name, file_id, diags) in &diags { - for diag in diags { - // We use JSON output for CI, and want to see warnings too. - // So do not filter on errors only - let vfs_path = loaded.vfs.file_path(*file_id); - let analysis = loaded.analysis(); - let root_path = &analysis - .project_data(*file_id) - .unwrap_or_else(|_err| panic!("could not find project data")) - .unwrap_or_else(|| panic!("could not find project data")) - .root_dir; - let relative_path = reporting::get_relative_path(root_path, vfs_path); - print_diagnostic_json(diag, &analysis, *file_id, relative_path, false, cli)?; - } - } - } else { - // Determine if we should show source context - // If any context flags are set, automatically enable source display - let show_source = args.show_source - || args.before_context.is_some() - || args.after_context.is_some() - || args.context.is_some() - || args.group_separator.is_some() - || args.no_group_separator; - let (before_lines, after_lines) = calculate_context_lines(args); - let has_context = before_lines > 0 || after_lines > 0; - let group_separator = should_show_group_separator(args, has_context && show_source); - let mut first_match = true; + } else if args.is_format_normal() { + writeln!(cli, "\nMatches found in {} modules", match_count)?; + } - if !show_source { - writeln!(cli, "Matches found in {} modules:", diags.len())?; - } + Ok(()) +} - for (name, file_id, diags) in &diags { - if !show_source { - writeln!(cli, " {}: {}", name, diags.len())?; - } - for diag in diags { - // Print group separator before each match (except the first) if showing source with context - if show_source - && !first_match - && let Some(ref sep) = group_separator +fn do_parse_all_streaming( + cli: &mut dyn Cli, + analysis: &Analysis, + project_id: &ProjectId, + config: &DiagnosticsConfig, + args: &Ssr, + loaded: &mut LoadResult, + match_count: &mut usize, +) -> Result<()> { + let module_index = analysis.module_index(*project_id).unwrap(); + let app_name = args.app.as_ref().map(|name| AppName(name.to_string())); + + // Create a channel for streaming results + let (tx, rx) = unbounded(); + + // Spawn a thread to process modules in parallel and send results + let analysis_clone = analysis.clone(); + let config_clone = config.clone(); + let args_clone = args.clone(); + + // Collect modules into an owned vector + let modules: Vec<_> = module_index + .iter_own() + .map(|(name, source, file_id)| (name.as_str().to_string(), source, file_id)) + .collect(); + + thread::spawn(move || { + modules + .into_iter() + .par_bridge() + .map_with( + (analysis_clone, tx), + |(db, tx), (module_name, _file_source, file_id)| { + if !otp_file_to_ignore(db, file_id) + && db.file_app_type(file_id).ok() != Some(Some(AppType::Dep)) + && (app_name.is_none() + || db.file_app_name(file_id).ok().as_ref() == Some(&app_name)) + && let Ok(Some(result)) = + do_parse_one(db, &config_clone, file_id, &module_name, &args_clone) { - writeln!(cli, "{}", sep)?; + // Send result through channel + let _ = tx.send(result); } - first_match = false; + }, + ) + .for_each(|_| {}); // Consume the iterator + // Channel is dropped here, signaling end of results + }); - // Get relative path for diagnostic output - let vfs_path = loaded.vfs.file_path(*file_id); - let analysis = loaded.analysis(); - let root_path = &analysis - .project_data(*file_id) - .unwrap_or_else(|_err| panic!("could not find project data")) - .unwrap_or_else(|| panic!("could not find project data")) - .root_dir; - let relative_path = reporting::get_relative_path(root_path, vfs_path); + // Process and print results as they arrive from the channel + for result in rx { + *match_count += 1; + print_single_result(cli, loaded, &result, args)?; + } - // Only show path when showing source context - let path_to_show = if show_source { - Some(relative_path) - } else { - None - }; - print_diagnostic(diag, &loaded.analysis(), *file_id, path_to_show, false, cli)?; + Ok(()) +} - // Only show source context if --show-source or --show-source-markers is set - if show_source { - // Determine if color should be used - let should_show_color = should_use_color(args); +fn print_single_result( + cli: &mut dyn Cli, + loaded: &mut LoadResult, + result: &(String, FileId, Vec), + args: &Ssr, +) -> Result<()> { + let (name, file_id, diags) = result; - // When not using color, always use markers to indicate the match - if should_show_color { - print_source_with_context( - diag, - &loaded.analysis(), - *file_id, - before_lines, - after_lines, - true, // use_color - cli, - )?; - } else { - // No color: use markers to indicate the match - print_source_with_context_markers( - diag, - &loaded.analysis(), - *file_id, - before_lines, - after_lines, - cli, - )?; - } - writeln!(cli)?; // Add blank line after source context - } + if args.is_format_json() { + for diag in diags { + let vfs_path = loaded.vfs.file_path(*file_id); + let analysis = loaded.analysis(); + let root_path = &analysis + .project_data(*file_id) + .unwrap_or_else(|_err| panic!("could not find project data")) + .unwrap_or_else(|| panic!("could not find project data")) + .root_dir; + let relative_path = reporting::get_relative_path(root_path, vfs_path); + print_diagnostic_json(diag, &analysis, *file_id, relative_path, false, cli)?; + } + } else { + writeln!(cli, " {}: {}", name, diags.len())?; + + // Determine if we should show source context + let show_source = args.show_source + || args.before_context.is_some() + || args.after_context.is_some() + || args.context.is_some() + || args.group_separator.is_some() + || args.no_group_separator; + let (before_lines, after_lines) = calculate_context_lines(args); + let has_context = before_lines > 0 || after_lines > 0; + let group_separator = should_show_group_separator(args, has_context && show_source); + + for (idx, diag) in diags.iter().enumerate() { + // Print group separator before each match (except the first) if showing source with context + if show_source + && idx > 0 + && let Some(ref sep) = group_separator + { + writeln!(cli, "{}", sep)?; + } + // Get relative path for diagnostic output + let vfs_path = loaded.vfs.file_path(*file_id); + let analysis = loaded.analysis(); + let root_path = &analysis + .project_data(*file_id) + .unwrap_or_else(|_err| panic!("could not find project data")) + .unwrap_or_else(|| panic!("could not find project data")) + .root_dir; + let relative_path = reporting::get_relative_path(root_path, vfs_path); + + // Only show path when showing source context + let path_to_show = if show_source { + Some(relative_path) + } else { + None + }; + print_diagnostic(diag, &loaded.analysis(), *file_id, path_to_show, false, cli)?; + + // Only show source context if --show-source or --show-source-markers is set + if show_source { + let should_show_color = should_use_color(args); + + if should_show_color { + print_source_with_context( + diag, + &loaded.analysis(), + *file_id, + before_lines, + after_lines, + true, + cli, + )?; + } else { + print_source_with_context_markers( + diag, + &loaded.analysis(), + *file_id, + before_lines, + after_lines, + cli, + )?; } + writeln!(cli)?; } } } @@ -321,41 +391,6 @@ fn load_project( query_config, ) } - -fn do_parse_all( - cli: &dyn Cli, - analysis: &Analysis, - project_id: &ProjectId, - config: &DiagnosticsConfig, - args: &Ssr, -) -> Result)>> { - let module_index = analysis.module_index(*project_id).unwrap(); - let module_iter = module_index.iter_own(); - - let pb = cli.progress(module_iter.len() as u64, "Scanning modules"); - let app_name = args.app.as_ref().map(|name| AppName(name.to_string())); - - Ok(module_iter - .par_bridge() - .progress_with(pb) - .map_with( - analysis.clone(), - |db, (module_name, _file_source, file_id)| { - if !otp_file_to_ignore(db, file_id) - && db.file_app_type(file_id).ok() != Some(Some(AppType::Dep)) - && (app_name.is_none() - || db.file_app_name(file_id).ok().as_ref() == Some(&app_name)) - { - do_parse_one(db, config, file_id, module_name.as_str(), args).unwrap() - } else { - None - } - }, - ) - .flatten() - .collect()) -} - fn do_parse_one( db: &Analysis, config: &DiagnosticsConfig, diff --git a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli.stdout b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli.stdout index 7e57212d83..e673a7be31 100644 --- a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli.stdout +++ b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli.stdout @@ -1,4 +1,5 @@ -Matches found in 1 modules: app_a: 2 16:13-16:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {_@A, _@B}. 20:13-20:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {_@A, _@B}. + +Matches found in 1 modules diff --git a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_expand.stdout b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_expand.stdout index 5f34373ba6..78f52012ac 100644 --- a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_expand.stdout +++ b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_expand.stdout @@ -1,3 +1,4 @@ -Matches found in 1 modules: app_a_ssr: 1 11:11-11:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {4}. + +Matches found in 1 modules diff --git a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_no_expand.stdout b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_no_expand.stdout index 17017a5b70..f5a1e98710 100644 --- a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_no_expand.stdout +++ b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_no_expand.stdout @@ -1,3 +1,4 @@ -Matches found in 1 modules: app_a_ssr: 1 11:11-11:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: ?BAR(_@AA). + +Matches found in 1 modules diff --git a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_visible_expand.stdout b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_visible_expand.stdout index 7ea212c37d..6f49895580 100644 --- a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_visible_expand.stdout +++ b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_macros_visible_expand.stdout @@ -1,4 +1,5 @@ -Matches found in 1 modules: app_a_ssr: 2 11:11-11:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: ?BAR(_@AA). 11:11-11:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {4}. + +Matches found in 1 modules diff --git a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_multiple.stdout b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_multiple.stdout index f47bfe428f..a71ebede29 100644 --- a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_multiple.stdout +++ b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_multiple.stdout @@ -1,4 +1,5 @@ -Matches found in 1 modules: app_a_ssr: 2 7:10-7:15::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: 3. 11:11-11:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {4}. + +Matches found in 1 modules diff --git a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_parens_invisible.stdout b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_parens_invisible.stdout index 653173722f..6938fabb87 100644 --- a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_parens_invisible.stdout +++ b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_parens_invisible.stdout @@ -1,3 +1,4 @@ -Matches found in 1 modules: app_a_ssr: 1 7:10-7:15::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: (((3))). + +Matches found in 1 modules diff --git a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_parens_visible.stdout b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_parens_visible.stdout index 7b84e1f651..a43317a2cf 100644 --- a/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_parens_visible.stdout +++ b/crates/elp/src/resources/test/linter/ssr_ad_hoc_cli_parens_visible.stdout @@ -1,5 +1,6 @@ -Matches found in 1 modules: app_a_ssr: 3 7:10-7:15::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: (_@A). 7:11-7:14::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: (_@A). 8:10-8:13::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: (_@A). + +Matches found in 1 modules diff --git a/crates/elp/src/resources/test/linter/ssr_context_separator.stdout b/crates/elp/src/resources/test/linter/ssr_context_separator.stdout index 99ac995b14..f791ea85bf 100644 --- a/crates/elp/src/resources/test/linter/ssr_context_separator.stdout +++ b/crates/elp/src/resources/test/linter/ssr_context_separator.stdout @@ -1,3 +1,4 @@ + app_a: 2 app_a/src/app_a.erl:16:13-16:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {_@A, _@B}. 14 | app_b:application_env_error(). 15 | @@ -15,3 +16,5 @@ app_a/src/app_a.erl:20:13-20:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern m 21 | 22 | % And some more context, see if it renders + +Matches found in 1 modules From be0cb21ea7a277289a43ef96a5b70459d8642ff1 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Mon, 24 Nov 2025 05:04:16 -0800 Subject: [PATCH 048/142] Make `elp search` a synonym for `elp ssr` Summary: As title. In future we may remove `elp ssr`. Reviewed By: TD5 Differential Revision: D87766502 fbshipit-source-id: c9742292afe35984002d02b20ec0e051cfee125f --- crates/elp/src/bin/args.rs | 7 +++++++ crates/elp/src/bin/main.rs | 10 ++++++++++ crates/elp/src/resources/test/help.stdout | 1 + 3 files changed, 18 insertions(+) diff --git a/crates/elp/src/bin/args.rs b/crates/elp/src/bin/args.rs index 93356a202b..a9f4950275 100644 --- a/crates/elp/src/bin/args.rs +++ b/crates/elp/src/bin/args.rs @@ -612,6 +612,12 @@ pub fn command() -> impl Parser { .command("lint") .help("Parse files in project and emit diagnostics, optionally apply fixes."); + let search = ssr() + .map(Command::Ssr) + .to_options() + .command("search") + .help("Alias for 'ssr': Run SSR (Structural Search and Replace) pattern matching on project files."); + let ssr = ssr() .map(Command::Ssr) .to_options() @@ -673,6 +679,7 @@ pub fn command() -> impl Parser { dialyze_all, lint, ssr, + search, parse_all, parse_elp, explain, diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index 0684c50228..7dc91de70e 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -2418,6 +2418,16 @@ mod tests { expected.assert_eq(&stdout); } + #[test] + fn search_help() { + let args = args::args() + .run_inner(Args::from(&["search", "--help"])) + .unwrap_err(); + let expected = expect_file!["../resources/test/ssr_help.stdout"]; + let stdout = args.unwrap_stdout(); + expected.assert_eq(&stdout); + } + #[test] fn build_info_help() { let args = args::args() diff --git a/crates/elp/src/resources/test/help.stdout b/crates/elp/src/resources/test/help.stdout index acc7ae286d..3370ec3090 100644 --- a/crates/elp/src/resources/test/help.stdout +++ b/crates/elp/src/resources/test/help.stdout @@ -20,6 +20,7 @@ Available commands: dialyze-all Run Dialyzer on the whole project by shelling out to a `dialyzer-run` tool on the path to do the legwork. lint Parse files in project and emit diagnostics, optionally apply fixes. ssr Run SSR (Structural Search and Replace) pattern matching on project files. + search Alias for 'ssr': Run SSR (Structural Search and Replace) pattern matching on project files. parse-all Dump ast for all files in a project for specified rebar.config file parse-elp Tree-sitter parse all files in a project for specified rebar.config file explain Explain a diagnostic code From fe49f6ba3cfa5477ec49948833a3633fffc7e761 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Mon, 24 Nov 2025 09:47:00 -0800 Subject: [PATCH 049/142] Implement support for renaming macros Summary: Long awaited feature: the ability to rename macros. Reviewed By: alanz Differential Revision: D87635298 fbshipit-source-id: 252ccd64193251fa9b0ce91cf6ab421c15c84086 --- crates/ide/src/rename.rs | 315 ++++++++++++++++++++++++++++++++++++ crates/ide_db/src/rename.rs | 81 +++++++++- 2 files changed, 394 insertions(+), 2 deletions(-) diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index ab7731f619..e034fa24a1 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -77,6 +77,28 @@ fn find_definitions( syntax: &SyntaxNode, position: FilePosition, ) -> RenameResult> { + // Try to find a macro name and classify it to resolve definitions (including across includes) + if let Some(macro_name) = algo::find_node_at_offset::(syntax, position.offset) + && let Some(token) = macro_name.syntax().first_token() + { + let location = InFile { + file_id: position.file_id, + value: token, + }; + match SymbolClass::classify(sema, location) { + Some(SymbolClass::Definition(def)) => return Ok(vec![def]), + Some(SymbolClass::Reference { refs, typ: _ }) => match refs { + ReferenceClass::Definition(def) => return Ok(vec![def]), + ReferenceClass::MultiMacro(defs) => { + return Ok(defs.into_iter().map(SymbolDefinition::Define).collect()); + } + _ => {} + }, + None => {} + } + } + + // If not a macro, try to find a general name let symbols = if let Some(name_like) = algo::find_node_at_offset::(syntax, position.offset) { match &name_like { @@ -1183,6 +1205,258 @@ pub(crate) mod tests { ); } + #[test] + fn test_rename_macro() { + check_rename( + "NEW_MACRO", + r#" + -define(OLD_~MACRO, value). + foo() -> ?OLD_MACRO. + "#, + r#" + -define(NEW_MACRO, value). + foo() -> ?NEW_MACRO. + "#, + ); + } + + #[test] + fn test_rename_macro_from_reference() { + check_rename( + "NEW_MACRO", + r#" + -define(OLD_MACRO, value). + foo() -> ?OLD_~MACRO. + "#, + r#" + -define(NEW_MACRO, value). + foo() -> ?NEW_MACRO. + "#, + ); + } + + #[test] + fn test_rename_macro_atom() { + check_rename( + "new_macro", + r#" + -define(old_~macro, value). + foo() -> ?old_macro. + "#, + r#" + -define(new_macro, value). + foo() -> ?new_macro. + "#, + ); + } + + #[test] + fn test_rename_macro_atom_from_reference() { + check_rename( + "new_macro", + r#" + -define(old_macro, value). + foo() -> ?old_~macro. + "#, + r#" + -define(new_macro, value). + foo() -> ?new_macro. + "#, + ); + } + + #[test] + fn test_rename_macro_from_definition_multi_file() { + check_rename( + "NEW_MACRO", + r#" + //- /src/common.hrl + -define(OLD_~MACRO, value). + + //- /src/module_a.erl + -module(module_a). + -include("common.hrl"). + foo() -> ?OLD_MACRO. + + //- /src/module_b.erl + -module(module_b). + -include("common.hrl"). + bar() -> ?OLD_MACRO. + "#, + r#" + //- /src/common.hrl + -define(NEW_MACRO, value). + + //- /src/module_a.erl + -module(module_a). + -include("common.hrl"). + foo() -> ?NEW_MACRO. + + //- /src/module_b.erl + -module(module_b). + -include("common.hrl"). + bar() -> ?NEW_MACRO. + "#, + ); + } + + #[test] + fn test_rename_macro_from_usage_multi_file() { + check_rename( + "NEW_MACRO", + r#" + //- /src/common.hrl + -define(OLD_MACRO, value). + + //- /src/module_a.erl + -module(module_a). + -include("common.hrl"). + foo() -> ?OLD_~MACRO. + + //- /src/module_b.erl + -module(module_b). + -include("common.hrl"). + bar() -> ?OLD_MACRO. + "#, + r#" + //- /src/common.hrl + -define(NEW_MACRO, value). + + //- /src/module_a.erl + -module(module_a). + -include("common.hrl"). + foo() -> ?NEW_MACRO. + + //- /src/module_b.erl + -module(module_b). + -include("common.hrl"). + bar() -> ?NEW_MACRO. + "#, + ); + } + + #[test] + fn test_rename_macro_name_clash() { + check_rename( + "EXISTING_MACRO", + r#" + -define(OLD_~MACRO, value1). + -define(EXISTING_MACRO, value2). + foo() -> ?OLD_MACRO. + "#, + r#"error: Macro 'EXISTING_MACRO' already in scope"#, + ); + } + + #[test] + fn test_rename_macro_from_usage_ifdef() { + check_rename( + "NEW_MACRO", + r#" + //- /src/common.hrl + -ifndef(TEST). + -define(OLD_MACRO, value1). + -else. + -define(OLD_MACRO, value2). + -endif. + + //- /src/module_a.erl + -module(module_a). + -include("common.hrl"). + foo() -> ?OLD_~MACRO. + + //- /src/module_b.erl + -module(module_b). + -include("common.hrl"). + bar() -> ?OLD_MACRO. + "#, + r#" + //- /src/common.hrl + -ifndef(TEST). + -define(OLD_MACRO, value1). + -else. + -define(NEW_MACRO, value2). + -endif. + + //- /src/module_a.erl + -module(module_a). + -include("common.hrl"). + foo() -> ?NEW_MACRO. + + //- /src/module_b.erl + -module(module_b). + -include("common.hrl"). + bar() -> ?NEW_MACRO. + "#, + ); + } + + #[test] + fn test_rename_macro_name_clash_from_reference() { + check_rename( + "EXISTING_MACRO", + r#" + -define(OLD_MACRO, value1). + -define(EXISTING_MACRO, value2). + foo() -> ?OLD_~MACRO. + "#, + r#"error: Macro 'EXISTING_MACRO' already in scope"#, + ); + } + + #[test] + fn test_rename_macro_name_clash_from_reference_multi_file() { + check_rename( + "EXISTING_MACRO", + r#" + //- /src/common.hrl + -define(OLD_MACRO, value1). + + //- /src/module_a.erl + -module(module_a). + -include("common.hrl"). + foo() -> ?OLD_~MACRO. + + //- /src/module_b.erl + -module(module_b). + -include("common.hrl"). + -define(EXISTING_MACRO, value3). + bar() -> ?OLD_MACRO. + "#, + r#"error: Macro 'EXISTING_MACRO' already in scope"#, + ); + } + + #[test] + fn test_rename_macro_different_arity_ok() { + check_rename( + "EXISTING_MACRO", + r#" + -define(OLD_~MACRO, value1). + -define(EXISTING_MACRO(X), X). + foo() -> ?OLD_MACRO. + "#, + r#" + -define(EXISTING_MACRO, value1). + -define(EXISTING_MACRO(X), X). + foo() -> ?EXISTING_MACRO. + "#, + ); + } + + #[test] + fn test_rename_macro_same_arity_conflict() { + check_rename( + "EXISTING_MACRO", + r#" + -define(OLD_~MACRO(X), X). + -define(EXISTING_MACRO(Y), Y). + foo() -> ?OLD_MACRO(1). + "#, + r#"error: Macro 'EXISTING_MACRO' already in scope"#, + ); + } + #[test] fn test_rename_in_rpc_call_4() { check_rename( @@ -2018,4 +2292,45 @@ pub(crate) mod tests { ok."#, ); } + + #[test] + fn test_rename_macro_separate_definitions() { + check_rename( + "NEW_MACRO", + r#" + //- /src/a.hrl + -define(MACRO, ok). + + //- /src/a.erl + -module(a). + -include("a.hrl"). + foo() -> ?MACRO~. + + //- /src/b.hrl + -define(MACRO, error). + + //- /src/b.erl + -module(b). + -include("b.hrl"). + bar() -> ?MACRO. + "#, + r#" + //- /src/a.hrl + -define(NEW_MACRO, ok). + + //- /src/a.erl + -module(a). + -include("a.hrl"). + foo() -> ?NEW_MACRO. + + //- /src/b.hrl + -define(MACRO, error). + + //- /src/b.erl + -module(b). + -include("b.hrl"). + bar() -> ?MACRO. + "#, + ); + } } diff --git a/crates/ide_db/src/rename.rs b/crates/ide_db/src/rename.rs index 3ea610554e..6d7ee4e907 100644 --- a/crates/ide_db/src/rename.rs +++ b/crates/ide_db/src/rename.rs @@ -79,6 +79,18 @@ pub fn is_valid_function_name(new_name: &String) -> bool { } } +// Delegate checking macro name validity to the parser +// Macros can be either atoms or variables +pub fn is_valid_macro_name(new_name: &String) -> bool { + let parse = ast::SourceFile::parse_text(format!("-define({new_name}, value).").as_str()); + matches!( + parse.tree().forms().next(), + Some(ast::Form::PreprocessorDirective( + ast::PreprocessorDirective::PpDefine(_) + )) + ) +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SafetyChecks { Yes, @@ -123,8 +135,30 @@ impl SymbolDefinition { SymbolDefinition::Callback(_) => { rename_error!("Cannot rename callback") } - SymbolDefinition::Define(_) => { - rename_error!("Cannot rename define") + SymbolDefinition::Define(define) => { + if safety_check == SafetyChecks::Yes && !is_valid_macro_name(new_name) { + rename_error!("Invalid new macro name: '{}'", new_name); + } + + let arity = define.define.name.arity(); + if safety_check == SafetyChecks::Yes { + // Check safety in the file where the macro is defined + if !is_safe_macro(sema, define.file.file_id, new_name, arity) { + rename_error!("Macro '{}' already in scope", new_name); + } + + // Also check safety in all files where the macro is used + let usages = self.clone().usages(sema).all(); + for (file_id, _refs) in usages.iter() { + if file_id != define.file.file_id + && !is_safe_macro(sema, file_id, new_name, arity) + { + rename_error!("Macro '{}' already in scope", new_name); + } + } + } + + self.rename_reference(sema, new_name, parens_needed_in_context, safety_check) } SymbolDefinition::Header(_) => { rename_error!("Cannot rename header") @@ -151,6 +185,19 @@ impl SymbolDefinition { range, }) } + SymbolDefinition::Define(d) => { + // Get the macro definition location + let source = d.source(sema.db.upcast()); + if let Some(name) = source.name() { + let range = name.syntax().text_range(); + Some(FileRange { + file_id: d.file.file_id, + range, + }) + } else { + None + } + } _ => None, } } @@ -215,6 +262,23 @@ impl SymbolDefinition { ); Ok(source_change) } + SymbolDefinition::Define(_define) => { + // Find all usages of the macro + let usages = self.clone().usages(sema).all(); + + // Also need to rename the definition itself + // Get the macro definition location + let (file_id, def_edit) = source_edit_from_def(sema, self.clone(), new_name)?; + source_change.insert_source_edit(file_id, def_edit); + + source_edit_from_usages( + &mut source_change, + usages.iter().collect(), + new_name, + parens_needed_in_context, + ); + Ok(source_change) + } SymbolDefinition::Var(var) => { let usages = sema .find_local_usages_ast(InFile { @@ -388,6 +452,19 @@ pub fn is_safe_function(sema: &Semantic, file_id: FileId, new_name: &str, arity: scope_ok && !in_erlang_module(new_name, arity as usize) } +/// Check that the new macro name is not already defined in scope. +/// Macros are identified by both name and arity, similar to functions. +pub fn is_safe_macro(sema: &Semantic, file_id: FileId, new_name: &str, arity: Option) -> bool { + sema.db + .def_map(file_id) + .get_macros() + .iter() + .all(|(name, _)| { + // A macro is considered different if either the name or arity differs + *name.name().to_string() != *new_name || name.arity() != arity + }) +} + /// Check that the new function name is not in scope already in the /// module via an explicit import. pub fn is_safe_remote_function( From 8dbbd1f6d4e22ae9131f82ec1d7124b324518ce7 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Mon, 24 Nov 2025 10:10:03 -0800 Subject: [PATCH 050/142] Back out "Fix failing test" Summary: As title Reviewed By: robertoaloi Differential Revision: D87786080 fbshipit-source-id: ab642c7b92b51c4df38fdef64f6741423e70e55a --- .../resources/test/linter/ssr_context_separator_color.stdout | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/elp/src/resources/test/linter/ssr_context_separator_color.stdout b/crates/elp/src/resources/test/linter/ssr_context_separator_color.stdout index 139250e2f2..119d62a52b 100644 --- a/crates/elp/src/resources/test/linter/ssr_context_separator_color.stdout +++ b/crates/elp/src/resources/test/linter/ssr_context_separator_color.stdout @@ -1,3 +1,4 @@ + app_a: 2 app_a/src/app_a.erl:16:13-16:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {_@A, _@B}.  14 | app_b:application_env_error().  15 | @@ -13,3 +14,5 @@ app_a/src/app_a.erl:20:13-20:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern m  21 |  22 | % And some more context, see if it renders + +Matches found in 1 modules From 4fa7b6c8ac17ff26f9157e85ab00302fb4055d82 Mon Sep 17 00:00:00 2001 From: Cj Longoria Date: Tue, 25 Nov 2025 02:19:37 -0800 Subject: [PATCH 051/142] add internal rustfmt bin to elp cargo script Summary: Add our nightly rustfmt binary to PATH for elp cargo shell script. This keeps the formatting consistent between `arc f` and `cargo.sh fmt` by using the same rustfmt.toml and rustfmt binary. Reviewed By: alanz Differential Revision: D87853695 fbshipit-source-id: dbb378acb8e48f5910187d32757d5f5597305df7 --- crates/syntax/src/syntax_kind/generated.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/syntax/src/syntax_kind/generated.rs b/crates/syntax/src/syntax_kind/generated.rs index 716d46d25a..781d35aaaf 100644 --- a/crates/syntax/src/syntax_kind/generated.rs +++ b/crates/syntax/src/syntax_kind/generated.rs @@ -1,7 +1,8 @@ //! @generated file, do not edit by hand, see `xtask/src/codegen.rs` #![allow(bad_style, missing_docs, unreachable_pub)] -use num_derive::{FromPrimitive, ToPrimitive}; +use num_derive::FromPrimitive; +use num_derive::ToPrimitive; #[doc = r" The kind of syntax node, e.g. `ATOM`, `IF_KW`, or `DOT`."] #[derive( Clone, From 8997d5eb037df7145f010d6c79d0b74ab7e1abb9 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Tue, 25 Nov 2025 02:19:42 -0800 Subject: [PATCH 052/142] Implement support for renaming types Summary: Long awaited feature: the ability to rename types. Take the opportunity to fix the range of the type name not to include arguments. Reviewed By: alanz Differential Revision: D87643493 fbshipit-source-id: e7fd3d13ff71ef5a3049aae3a242606899e582be --- crates/hir/src/module_data.rs | 5 +- crates/ide/src/document_symbols.rs | 4 +- crates/ide/src/rename.rs | 364 ++++++++++++++++++++++++++++- crates/ide_db/src/rename.rs | 79 ++++++- 4 files changed, 435 insertions(+), 17 deletions(-) diff --git a/crates/hir/src/module_data.rs b/crates/hir/src/module_data.rs index b081cbd539..0420e19bb6 100644 --- a/crates/hir/src/module_data.rs +++ b/crates/hir/src/module_data.rs @@ -615,8 +615,9 @@ impl TypeAliasDef { } pub fn name_range(&self, db: &dyn SourceDatabase) -> Option { - let range = self.source(db).type_name()?.syntax().text_range(); - Some(range) + let type_name = self.source(db).type_name()?; + let name = type_name.name()?; + Some(name.syntax().text_range()) } /// This information is used for completion. diff --git a/crates/ide/src/document_symbols.rs b/crates/ide/src/document_symbols.rs index 390a975132..daed2c6368 100644 --- a/crates/ide/src/document_symbols.rs +++ b/crates/ide/src/document_symbols.rs @@ -264,7 +264,7 @@ mod tests { -record(my_second_record, {my_list :: [] }). %% ^^^^^^^^^^^^^^^^ Record | my_second_record -type my_integer() :: integer(). -%% ^^^^^^^^^^^^ Type | my_integer/0 +%% ^^^^^^^^^^ Type | my_integer/0 -define(MEANING_OF_LIFE, 42). %% ^^^^^^^^^^^^^^^ Define | MEANING_OF_LIFE @@ -340,7 +340,7 @@ mod tests { -record(included_record, {my_field :: integer()}). %% ^^^^^^^^^^^^^^^ Record | included_record -type local_type() :: integer(). -%% ^^^^^^^^^^^^ Type | local_type/0 +%% ^^^^^^^^^^ Type | local_type/0 local_function() -> ok. %% ^^^^^^^^^^^^^^ Function | local_function/0 "#, diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index e034fa24a1..0cedbe1d90 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -2276,20 +2276,362 @@ pub(crate) mod tests { check_rename( "new_name", r#" - //- /src/baz.erl - -module(baz). - foo() -> - erpc:send_request(node, ?MODULE, bar, [], label, collection). + //- /src/baz.erl + -module(baz). + foo() -> + erpc:send_request(node, ?MODULE, bar, [], label, collection). - b~ar() -> - ok."#, + b~ar() -> + ok."#, r#" - -module(baz). - foo() -> - erpc:send_request(node, ?MODULE, new_name, [], label, collection). + -module(baz). + foo() -> + erpc:send_request(node, ?MODULE, new_name, [], label, collection). - new_name() -> - ok."#, + new_name() -> + ok."#, + ); + } + + // --------------------------------- + // Type Renaming Tests + // --------------------------------- + + #[test] + fn test_rename_type_simple() { + check_rename( + "new_type", + r#" + -module(main). + -type ol~d_type() :: ok. + -spec foo(old_type()) -> ok. + foo(_) -> ok. + "#, + r#" + -module(main). + -type new_type() :: ok. + -spec foo(new_type()) -> ok. + foo(_) -> ok. + "#, + ); + } + + #[test] + fn test_rename_type_with_arity() { + check_rename( + "new_type", + r#" + -module(main). + -type old_~type(T) :: {ok, T}. + -spec foo(old_type(integer())) -> ok. + foo(_) -> ok. + "#, + r#" + -module(main). + -type new_type(T) :: {ok, T}. + -spec foo(new_type(integer())) -> ok. + foo(_) -> ok. + "#, + ); + } + + #[test] + fn test_rename_type_from_usage() { + check_rename( + "new_type", + r#" + -module(main). + -type old_type() :: ok. + -spec foo(old_~type()) -> ok. + foo(_) -> ok. + "#, + r#" + -module(main). + -type new_type() :: ok. + -spec foo(new_type()) -> ok. + foo(_) -> ok. + "#, + ); + } + + #[test] + fn test_rename_opaque_type() { + check_rename( + "new_type", + r#" + -module(main). + -opaque old_~type() :: {internal, term()}. + -spec create() -> old_type(). + create() -> {internal, data}. + "#, + r#" + -module(main). + -opaque new_type() :: {internal, term()}. + -spec create() -> new_type(). + create() -> {internal, data}. + "#, + ); + } + + #[test] + fn test_rename_type_fails_name_clash() { + check_rename( + "existing_type", + r#" + -module(main). + -type old_~type() :: ok. + -type existing_type() :: error. + "#, + r#"error: Type 'existing_type/0' already in scope"#, + ); + } + + #[test] + fn test_rename_type_allows_different_arity() { + check_rename( + "existing_type", + r#" + -module(main). + -type old_~type() :: ok. + -type existing_type(T) :: {error, T}. + "#, + r#" + -module(main). + -type existing_type() :: ok. + -type existing_type(T) :: {error, T}. + "#, + ); + } + + #[test] + fn test_rename_type_invalid_name() { + check_rename( + "New_Type", + r#" + -module(main). + -type old_~type() :: ok. + "#, + r#"error: Invalid new type name: 'New_Type'"#, + ); + } + + #[test] + fn test_rename_type_remote_reference() { + check_rename( + "new_type", + r#" + //- /src/module_a.erl + -module(module_a). + -export_type([old_~type/0]). + -type old_type() :: {ok, term()}. + + //- /src/module_b.erl + -module(module_b). + -spec foo(module_a:old_type()) -> ok. + foo(_) -> ok. + "#, + r#" + //- /src/module_a.erl + -module(module_a). + -export_type([new_type/0]). + -type new_type() :: {ok, term()}. + + //- /src/module_b.erl + -module(module_b). + -spec foo(module_a:new_type()) -> ok. + foo(_) -> ok. + "#, + ); + } + + #[test] + fn test_rename_type_remote_reference_from_usage() { + check_rename( + "new_type", + r#" + //- /src/module_a.erl + -module(module_a). + -export_type([old_type/0]). + -type old_type() :: {ok, term()}. + + //- /src/module_b.erl + -module(module_b). + -spec foo(module_a:old_~type()) -> ok. + foo(_) -> ok. + "#, + r#" + //- /src/module_a.erl + -module(module_a). + -export_type([new_type/0]). + -type new_type() :: {ok, term()}. + + //- /src/module_b.erl + -module(module_b). + -spec foo(module_a:new_type()) -> ok. + foo(_) -> ok. + "#, + ); + } + + #[test] + fn test_rename_type_from_header_multi_file() { + check_rename( + "new_type", + r#" + //- /src/types.hrl + -type old_~type() :: {ok, term()}. + + //- /src/module_a.erl + -module(module_a). + -include("types.hrl"). + -spec foo(old_type()) -> ok. + foo(_) -> ok. + + //- /src/module_b.erl + -module(module_b). + -include("types.hrl"). + -spec bar(old_type()) -> ok. + bar(_) -> ok. + "#, + r#" + //- /src/types.hrl + -type new_type() :: {ok, term()}. + + //- /src/module_a.erl + -module(module_a). + -include("types.hrl"). + -spec foo(new_type()) -> ok. + foo(_) -> ok. + + //- /src/module_b.erl + -module(module_b). + -include("types.hrl"). + -spec bar(new_type()) -> ok. + bar(_) -> ok. + "#, + ); + } + + #[test] + fn test_rename_type_from_usage_in_header() { + check_rename( + "new_type", + r#" + //- /src/types.hrl + -type old_type() :: {ok, term()}. + + //- /src/module_a.erl + -module(module_a). + -include("types.hrl"). + -spec foo(old_~type()) -> ok. + foo(_) -> ok. + + //- /src/module_b.erl + -module(module_b). + -include("types.hrl"). + -spec bar(old_type()) -> ok. + bar(_) -> ok. + "#, + r#" + //- /src/types.hrl + -type new_type() :: {ok, term()}. + + //- /src/module_a.erl + -module(module_a). + -include("types.hrl"). + -spec foo(new_type()) -> ok. + foo(_) -> ok. + + //- /src/module_b.erl + -module(module_b). + -include("types.hrl"). + -spec bar(new_type()) -> ok. + bar(_) -> ok. + "#, + ); + } + + #[test] + fn test_rename_type_name_clash_in_remote_module() { + check_rename( + "existing_type", + r#" + //- /src/module_a.erl + -module(module_a). + -export_type([old_type/0]). + -type old_~type() :: {ok, term()}. + + //- /src/module_b.erl + -module(module_b). + -type existing_type() :: error. + -spec foo(module_a:old_type()) -> ok. + foo(_) -> ok. + "#, + r#"error: Type 'existing_type/0' already in scope"#, + ); + } + + #[test] + fn test_rename_type_name_clash_from_header_multi_file() { + check_rename( + "existing_type", + r#" + //- /src/types.hrl + -type old_type() :: {ok, term()}. + + //- /src/module_a.erl + -module(module_a). + -include("types.hrl"). + -spec foo(old_~type()) -> ok. + foo(_) -> ok. + + //- /src/module_b.erl + -module(module_b). + -include("types.hrl"). + -type existing_type() :: error. + -spec bar(old_type()) -> ok. + bar(_) -> ok. + "#, + r#"error: Type 'existing_type/0' already in scope"#, + ); + } + + #[test] + fn test_rename_opaque_type_from_header() { + check_rename( + "new_type", + r#" + //- /src/types.hrl + -opaque old_~type() :: {internal, term()}. + + //- /src/module_a.erl + -module(module_a). + -include("types.hrl"). + -spec create() -> old_type(). + create() -> {internal, data}. + + //- /src/module_b.erl + -module(module_b). + -include("types.hrl"). + -spec use(old_type()) -> ok. + use(_) -> ok. + "#, + r#" + //- /src/types.hrl + -opaque new_type() :: {internal, term()}. + + //- /src/module_a.erl + -module(module_a). + -include("types.hrl"). + -spec create() -> new_type(). + create() -> {internal, data}. + + //- /src/module_b.erl + -module(module_b). + -include("types.hrl"). + -spec use(new_type()) -> ok. + use(_) -> ok. + "#, ); } diff --git a/crates/ide_db/src/rename.rs b/crates/ide_db/src/rename.rs index 6d7ee4e907..889569ca4e 100644 --- a/crates/ide_db/src/rename.rs +++ b/crates/ide_db/src/rename.rs @@ -91,6 +91,21 @@ pub fn is_valid_macro_name(new_name: &String) -> bool { ) } +// Delegate checking type name validity to the parser +pub fn is_valid_type_name(new_name: &String) -> bool { + let parse = ast::SourceFile::parse_text(format!("-type {new_name}() :: ok.").as_str()); + // Check that we got a TypeAlias form + if let Some(ast::Form::TypeAlias(type_alias)) = parse.tree().forms().next() { + // Check that the name is an atom (not a variable) + if let Some(type_name) = type_alias.name() + && let Some(ast::Name::Atom(_)) = type_name.name() + { + return true; + } + } + false +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SafetyChecks { Yes, @@ -129,8 +144,30 @@ impl SymbolDefinition { SymbolDefinition::RecordField(_) => { rename_error!("Cannot rename record field") } - SymbolDefinition::Type(_) => { - rename_error!("Cannot rename type") + SymbolDefinition::Type(type_alias) => { + if safety_check == SafetyChecks::Yes && !is_valid_type_name(new_name) { + rename_error!("Invalid new type name: '{}'", new_name); + } + + let arity = type_alias.name().arity(); + if safety_check == SafetyChecks::Yes { + // Check safety in the file where the type is defined + if !is_safe_type(sema, type_alias.file.file_id, new_name, arity) { + rename_error!("Type '{}/{}' already in scope", new_name, arity); + } + + // Also check safety in all files where the type is used + let usages = self.clone().usages(sema).all(); + for (file_id, _refs) in usages.iter() { + if file_id != type_alias.file.file_id + && !is_safe_type(sema, file_id, new_name, arity) + { + rename_error!("Type '{}/{}' already in scope", new_name, arity); + } + } + } + + self.rename_reference(sema, new_name, parens_needed_in_context, safety_check) } SymbolDefinition::Callback(_) => { rename_error!("Cannot rename callback") @@ -198,6 +235,14 @@ impl SymbolDefinition { None } } + SymbolDefinition::Type(t) => { + // Get the type definition location + let range = t.name_range(sema.db.upcast())?; + Some(FileRange { + file_id: t.file.file_id, + range, + }) + } _ => None, } } @@ -279,6 +324,23 @@ impl SymbolDefinition { ); Ok(source_change) } + SymbolDefinition::Type(_type_alias) => { + // Find all usages of the type + let usages = self.clone().usages(sema).all(); + + // Also need to rename the definition itself + // Get the type definition location + let (file_id, def_edit) = source_edit_from_def(sema, self.clone(), new_name)?; + source_change.insert_source_edit(file_id, def_edit); + + source_edit_from_usages( + &mut source_change, + usages.iter().collect(), + new_name, + parens_needed_in_context, + ); + Ok(source_change) + } SymbolDefinition::Var(var) => { let usages = sema .find_local_usages_ast(InFile { @@ -465,6 +527,19 @@ pub fn is_safe_macro(sema: &Semantic, file_id: FileId, new_name: &str, arity: Op }) } +/// Check that the new type name is not already defined in scope. +/// Types are identified by both name and arity, similar to functions and macros. +pub fn is_safe_type(sema: &Semantic, file_id: FileId, new_name: &str, arity: u32) -> bool { + sema.db + .def_map(file_id) + .get_types() + .iter() + .all(|(name, _)| { + // A type is considered different if either the name or arity differs + *name.name().to_string() != *new_name || name.arity() != arity + }) +} + /// Check that the new function name is not in scope already in the /// module via an explicit import. pub fn is_safe_remote_function( From 0da6ec3079dd58b21448ccc8f370a07dedb290aa Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Tue, 25 Nov 2025 02:35:22 -0800 Subject: [PATCH 053/142] Introduce elp --color argument to control ANSI color codes Summary: Also respond to the `NO_COLOR` environment variable, as per [no-color.org](no-color.org). Reviewed By: jcpetruzza Differential Revision: D77446683 fbshipit-source-id: c7b14c64813ca98a1163fb2f9e651ea304e5757d --- crates/elp/src/bin/args.rs | 20 +-- crates/elp/src/bin/main.rs | 159 +++++++++++++++++- crates/elp/src/bin/ssr_cli.rs | 20 ++- crates/elp/src/cli.rs | 114 +++++++++++-- crates/elp/src/resources/test/help.stdout | 3 +- crates/elp/src/resources/test/ssr_help.stdout | 3 +- 6 files changed, 280 insertions(+), 39 deletions(-) diff --git a/crates/elp/src/bin/args.rs b/crates/elp/src/bin/args.rs index a9f4950275..f533688b82 100644 --- a/crates/elp/src/bin/args.rs +++ b/crates/elp/src/bin/args.rs @@ -413,16 +413,6 @@ pub struct Ssr { #[bpaf(long("no-group-separator"))] pub no_group_separator: bool, - /// Use markers to highlight the matching strings; WHEN is 'always', 'never', or 'auto' - #[bpaf( - long("color"), - long("colour"), - argument("WHEN"), - fallback(Some("auto".to_string())), - guard(color_guard, "Please use always, never, or auto") - )] - pub color: Option, - /// Report system memory usage and other statistics #[bpaf(long("report-system-stats"))] pub report_system_stats: bool, @@ -527,6 +517,16 @@ pub struct Args { /// Use buck2 targets for first stage project loading pub buck_quick_start: bool, + /// Use color in output; WHEN is 'always', 'never', or 'auto' + #[bpaf( + long("color"), + long("colour"), + argument("WHEN"), + fallback(Some("always".to_string())), + guard(color_guard, "Please use always, never, or auto") + )] + pub color: Option, + #[bpaf(external(command))] pub command: Command, } diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index 7dc91de70e..909224f6f2 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -10,6 +10,7 @@ use std::env; use std::fs; +use std::io::IsTerminal; use std::io::Write; use std::path::PathBuf; use std::process; @@ -65,9 +66,23 @@ const THREAD_STACK_SIZE: usize = 10_000_000; fn main() { let _timer = timeit!("main"); - let mut cli = cli::Real::default(); let args = args::args().run(); - let res = try_main(&mut cli, args); + let use_color = match args.color.as_deref() { + Some("always") => true, + Some("never") => false, + Some("auto") | None => { + // Check NO_COLOR environment variable - if set (regardless of value), disable color + // Also check if stdout is connected to a TTY + env::var("NO_COLOR").is_err() && std::io::stdout().is_terminal() + } + _ => false, // Should be caught by the guard, but handle anyway + }; + let mut cli: Box = if use_color { + Box::new(cli::Real::default()) + } else { + Box::new(cli::NoColor::default()) + }; + let res = try_main(&mut *cli, args); let code = handle_res(res, cli.err()); process::exit(code); } @@ -136,7 +151,9 @@ fn try_main(cli: &mut dyn Cli, args: Args) -> Result<()> { args::Command::BuildInfo(args) => build_info_cli::save_build_info(args, &query_config)?, args::Command::ProjectInfo(args) => build_info_cli::save_project_info(args, &query_config)?, args::Command::Lint(args) => lint_cli::run_lint_command(&args, cli, &query_config)?, - args::Command::Ssr(args) => ssr_cli::run_ssr_command(&args, cli, &query_config)?, + args::Command::Ssr(ssr_args) => { + ssr_cli::run_ssr_command(&ssr_args, cli, &query_config, &args.color)? + } args::Command::GenerateCompletions(args) => { let instructions = args::gen_completions(&args.shell); writeln!(cli, "#Please run this:\n{instructions}")? @@ -2045,10 +2062,13 @@ mod tests { fn lint_ssr_with_context_and_separator() { simple_snapshot( args_vec![ + "--colour", + "never", "ssr", - "--context", "2", - "--group-separator", "====", - "--colour", "never" + "--context", + "2", + "--group-separator", + "====", "{_@A, _@B}", ], "linter", @@ -2062,10 +2082,13 @@ mod tests { fn lint_ssr_with_context_and_separator_color() { simple_snapshot( args_vec![ + "--colour", + "always", "ssr", - "--context", "2", - "--group-separator", "====", - "--colour", "always" + "--context", + "2", + "--group-separator", + "====", "{_@A, _@B}", ], "linter", @@ -2318,6 +2341,117 @@ mod tests { } } + #[test_case(false ; "rebar")] + #[test_case(true ; "buck")] + fn eqwalize_with_color_vs_no_color(buck: bool) { + if otp_supported_by_eqwalizer() { + // Test with color (default) + let (mut args_color, _path) = + add_project(args_vec!["eqwalize", "app_a"], "standard", None, None); + if !buck { + args_color.push("--rebar".into()); + } + + // Test without color + let (mut args_no_color, _) = add_project( + args_vec!["--color", "never", "eqwalize", "app_a"], + "standard", + None, + None, + ); + if !buck { + args_no_color.push("--rebar".into()); + } + + let (stdout_color, stderr_color, code_color) = elp(args_color); + let (stdout_no_color, stderr_no_color, code_no_color) = elp(args_no_color); + + // Both should have same exit code + assert_eq!(code_color, code_no_color); + + // Both should have same stderr behavior + if code_color == 0 { + assert!(stderr_color.is_empty()); + assert!(stderr_no_color.is_empty()); + } + + // The content should be similar but no-color version should not contain ANSI escape codes + // ANSI color codes typically start with \x1b[ or \u{1b}[ + let _has_ansi_color = stdout_color.contains('\x1b'); + let has_ansi_no_color = stdout_no_color.contains('\x1b'); + + // With --color never, there should be no ANSI escape sequences + assert!( + !has_ansi_no_color, + "Output with --color never should not contain ANSI escape codes" + ); + + // The outputs should be functionally equivalent when ANSI codes are stripped + let stripped_color = strip_ansi_codes(&stdout_color); + assert_eq!( + stripped_color, stdout_no_color, + "Content should be identical after stripping ANSI codes" + ); + } + } + + #[test_case(false ; "rebar")] + #[test_case(true ; "buck")] + fn eqwalize_with_no_color_env_var(buck: bool) { + if otp_supported_by_eqwalizer() { + // Test with NO_COLOR environment variable set + unsafe { + env::set_var("NO_COLOR", "1"); + } + + let (mut args_no_color_env, _) = + add_project(args_vec!["eqwalize", "app_a"], "standard", None, None); + if !buck { + args_no_color_env.push("--rebar".into()); + } + + let (stdout_no_color_env, stderr_no_color_env, code_no_color_env) = + elp(args_no_color_env); + + // Clean up environment variable + unsafe { + env::remove_var("NO_COLOR"); + } + + // Test with normal color (for comparison) + let (mut args_color, _) = + add_project(args_vec!["eqwalize", "app_a"], "standard", None, None); + if !buck { + args_color.push("--rebar".into()); + } + + let (stdout_color, stderr_color, code_color) = elp(args_color); + + // Both should have same exit code + assert_eq!(code_color, code_no_color_env); + + // Both should have same stderr behavior + if code_color == 0 { + assert!(stderr_color.is_empty()); + assert!(stderr_no_color_env.is_empty()); + } + + // The NO_COLOR env var version should not contain ANSI escape codes + let has_ansi_no_color_env = stdout_no_color_env.contains('\x1b'); + assert!( + !has_ansi_no_color_env, + "Output with NO_COLOR env var should not contain ANSI escape codes" + ); + + // The outputs should be functionally equivalent when ANSI codes are stripped + let stripped_color = strip_ansi_codes(&stdout_color); + assert_eq!( + stripped_color, stdout_no_color_env, + "Content should be identical after stripping ANSI codes" + ); + } + } + // ----------------------------------------------------------------- #[test] @@ -2836,6 +2970,13 @@ mod tests { format!("../../test_projects/{project}") } + fn strip_ansi_codes(s: &str) -> String { + lazy_static! { + static ref ANSI_RE: Regex = Regex::new(r"\x1b\[[0-9;]*m").unwrap(); + } + ANSI_RE.replace_all(s, "").to_string() + } + struct BackupFiles { // Restore the first Path to the second restore: Vec<(PathBuf, PathBuf)>, diff --git a/crates/elp/src/bin/ssr_cli.rs b/crates/elp/src/bin/ssr_cli.rs index 9a080a4907..1458e2eb27 100644 --- a/crates/elp/src/bin/ssr_cli.rs +++ b/crates/elp/src/bin/ssr_cli.rs @@ -66,6 +66,7 @@ pub fn run_ssr_command( args: &Ssr, cli: &mut dyn Cli, query_config: &BuckQueryConfig, + global_color: &Option, ) -> Result<()> { let start_time = SystemTime::now(); let memory_start = MemoryUsage::now(); @@ -129,7 +130,7 @@ pub fn run_ssr_command( let mut loaded = load_project(args, cli, query_config)?; telemetry::report_elapsed_time("ssr operational", start_time); - let r = run_ssr(cli, &mut loaded, &diagnostics_config, args); + let r = run_ssr(cli, &mut loaded, &diagnostics_config, args, global_color); telemetry::report_elapsed_time("ssr done", start_time); @@ -149,6 +150,7 @@ pub fn run_ssr( loaded: &mut LoadResult, diagnostics_config: &DiagnosticsConfig, args: &Ssr, + global_color: &Option, ) -> Result<()> { let analysis = loaded.analysis(); let (file_id, name) = match &args.module { @@ -194,6 +196,7 @@ pub fn run_ssr( &project_id, diagnostics_config, args, + global_color, loaded, &mut match_count, )?; @@ -207,7 +210,7 @@ pub fn run_ssr( } if let Some(diag) = do_parse_one(&analysis, diagnostics_config, file_id, &name, args)? { match_count = 1; - print_single_result(cli, loaded, &diag, args)?; + print_single_result(cli, loaded, &diag, args, global_color)?; } } (Some(file_id), _) => { @@ -226,12 +229,14 @@ pub fn run_ssr( Ok(()) } +#[allow(clippy::too_many_arguments)] fn do_parse_all_streaming( cli: &mut dyn Cli, analysis: &Analysis, project_id: &ProjectId, config: &DiagnosticsConfig, args: &Ssr, + global_color: &Option, loaded: &mut LoadResult, match_count: &mut usize, ) -> Result<()> { @@ -278,7 +283,7 @@ fn do_parse_all_streaming( // Process and print results as they arrive from the channel for result in rx { *match_count += 1; - print_single_result(cli, loaded, &result, args)?; + print_single_result(cli, loaded, &result, args, global_color)?; } Ok(()) @@ -289,6 +294,7 @@ fn print_single_result( loaded: &mut LoadResult, result: &(String, FileId, Vec), args: &Ssr, + global_color: &Option, ) -> Result<()> { let (name, file_id, diags) = result; @@ -346,7 +352,7 @@ fn print_single_result( // Only show source context if --show-source or --show-source-markers is set if show_source { - let should_show_color = should_use_color(args); + let should_show_color = should_use_color(global_color); if should_show_color { print_source_with_context( @@ -548,9 +554,9 @@ fn should_show_group_separator(args: &Ssr, has_context: bool) -> Option ) } -/// Determine if color should be used based on the new --color argument -fn should_use_color(args: &Ssr) -> bool { - match args.color.as_deref() { +/// Determine if color should be used based on the global --color argument +fn should_use_color(color: &Option) -> bool { + match color.as_deref() { Some("always") => true, Some("never") => false, Some("auto") | None => { diff --git a/crates/elp/src/cli.rs b/crates/elp/src/cli.rs index b3070768d6..0678aaebc1 100644 --- a/crates/elp/src/cli.rs +++ b/crates/elp/src/cli.rs @@ -30,18 +30,13 @@ pub trait Cli: Write + WriteColor { fn err(&mut self) -> &mut dyn Write; } -pub struct Real(StandardStream, Stderr); +pub struct StandardCli(StandardStream, Stderr); -impl Default for Real { - fn default() -> Self { - Self( - StandardStream::stdout(ColorChoice::Always), - std::io::stderr(), - ) +impl StandardCli { + fn new(color_choice: ColorChoice) -> Self { + Self(StandardStream::stdout(color_choice), std::io::stderr()) } -} -impl Real { fn progress_with_style( &self, len: u64, @@ -59,7 +54,7 @@ impl Real { } } -impl Cli for Real { +impl Cli for StandardCli { fn progress(&self, len: u64, prefix: &'static str) -> ProgressBar { self.progress_with_style(len, prefix, " {prefix:25!} {bar} {pos}/{len} {wide_msg}") } @@ -84,6 +79,63 @@ impl Cli for Real { } } +impl Write for StandardCli { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.0.write(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.0.flush() + } +} + +impl WriteColor for StandardCli { + fn supports_color(&self) -> bool { + self.0.supports_color() + } + + fn set_color(&mut self, spec: &ColorSpec) -> std::io::Result<()> { + self.0.set_color(spec) + } + + fn reset(&mut self) -> std::io::Result<()> { + self.0.reset() + } +} + +pub struct Real(StandardCli); +pub struct NoColor(StandardCli); + +impl Default for Real { + fn default() -> Self { + Real(StandardCli::new(ColorChoice::Always)) + } +} + +impl Default for NoColor { + fn default() -> Self { + NoColor(StandardCli::new(ColorChoice::Never)) + } +} + +impl Cli for Real { + fn progress(&self, len: u64, prefix: &'static str) -> ProgressBar { + self.0.progress(len, prefix) + } + + fn simple_progress(&self, len: u64, prefix: &'static str) -> ProgressBar { + self.0.simple_progress(len, prefix) + } + + fn spinner(&self, prefix: &'static str) -> ProgressBar { + self.0.spinner(prefix) + } + + fn err(&mut self) -> &mut dyn Write { + self.0.err() + } +} + impl Write for Real { fn write(&mut self, buf: &[u8]) -> std::io::Result { self.0.write(buf) @@ -108,6 +160,48 @@ impl WriteColor for Real { } } +impl Cli for NoColor { + fn progress(&self, len: u64, prefix: &'static str) -> ProgressBar { + self.0.progress(len, prefix) + } + + fn simple_progress(&self, len: u64, prefix: &'static str) -> ProgressBar { + self.0.simple_progress(len, prefix) + } + + fn spinner(&self, prefix: &'static str) -> ProgressBar { + self.0.spinner(prefix) + } + + fn err(&mut self) -> &mut dyn Write { + self.0.err() + } +} + +impl Write for NoColor { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.0.write(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.0.flush() + } +} + +impl WriteColor for NoColor { + fn supports_color(&self) -> bool { + self.0.supports_color() + } + + fn set_color(&mut self, spec: &ColorSpec) -> std::io::Result<()> { + self.0.set_color(spec) + } + + fn reset(&mut self) -> std::io::Result<()> { + self.0.reset() + } +} + pub struct Fake(Buffer, Vec); impl Default for Fake { diff --git a/crates/elp/src/resources/test/help.stdout b/crates/elp/src/resources/test/help.stdout index 3370ec3090..1510d5bddb 100644 --- a/crates/elp/src/resources/test/help.stdout +++ b/crates/elp/src/resources/test/help.stdout @@ -1,4 +1,4 @@ -Usage: [--log-file LOG_FILE] [--erl ERL] [--escript ESCRIPT] [--no-log-buffering] [--no-buck-generated] [--buck-quick-start] [COMMAND ...] +Usage: [--log-file LOG_FILE] [--erl ERL] [--escript ESCRIPT] [--no-log-buffering] [--no-buck-generated] [--buck-quick-start] [[--color WHEN]] [COMMAND ...] Available options: --log-file @@ -7,6 +7,7 @@ Available options: --no-log-buffering --no-buck-generated When using buck, do not invoke a build step for generated files. --buck-quick-start Use buck2 targets for first stage project loading + --color Use color in output; WHEN is 'always', 'never', or 'auto' -h, --help Prints help information Available commands: diff --git a/crates/elp/src/resources/test/ssr_help.stdout b/crates/elp/src/resources/test/ssr_help.stdout index 982d2327e6..d51bdf1627 100644 --- a/crates/elp/src/resources/test/ssr_help.stdout +++ b/crates/elp/src/resources/test/ssr_help.stdout @@ -1,4 +1,4 @@ -Usage: [--project PROJECT] [--module MODULE] [--app APP] [--file FILE] [--rebar] [--as PROFILE] [--include-generated] [--include-tests] [[--format FORMAT]] [[--macros STRATEGY]] [--parens] [--dump-config] [--show-source] [-B NUM] [-A NUM] [-C NUM] [--group-separator SEP] [--no-group-separator] [[--color WHEN]] [--report-system-stats] ... +Usage: [--project PROJECT] [--module MODULE] [--app APP] [--file FILE] [--rebar] [--as PROFILE] [--include-generated] [--include-tests] [[--format FORMAT]] [[--macros STRATEGY]] [--parens] [--dump-config] [--show-source] [-B NUM] [-A NUM] [-C NUM] [--group-separator SEP] [--no-group-separator] [--report-system-stats] ... Available positional items: SSR specs to use @@ -22,6 +22,5 @@ Available options: -C, --context Print NUM lines of output context, enables --show-source --group-separator Print SEP on line between matches with context, enables --show-source --no-group-separator Do not print separator for matches with context, enables --show-source - --color Use markers to highlight the matching strings; WHEN is 'always', 'never', or 'auto' --report-system-stats Report system memory usage and other statistics -h, --help Prints help information From 06892be8c11d363251b0d5fc2c82d1b203e18988 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Thu, 27 Nov 2025 01:48:55 -0800 Subject: [PATCH 054/142] Add server telemetry for workspace statistics Summary: As title. Note that for a buck project using quick start, we will get two values per project startup, the first smaller value is from processing the result of `buck2 targets`, the second includes any generated files from running `elp.bxl` Reviewed By: jcpetruzza Differential Revision: D87923914 fbshipit-source-id: 8b10cb1ef685250f022cf360e1b2d5f621c17407 --- crates/elp/src/project_loader.rs | 2 +- crates/elp/src/server.rs | 10 ++++++++++ crates/project_model/src/buck.rs | 10 ++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/crates/elp/src/project_loader.rs b/crates/elp/src/project_loader.rs index d2c76cd270..103353e5db 100644 --- a/crates/elp/src/project_loader.rs +++ b/crates/elp/src/project_loader.rs @@ -192,7 +192,7 @@ impl ReloadManager { self.get_query_config() } - fn get_query_config(&self) -> BuckQueryConfig { + pub fn get_query_config(&self) -> BuckQueryConfig { if self.buck_quick_start { match self.buck_generated { BuckGenerated::NoLoadDone => BuckQueryConfig::BuckTargetsOnly, diff --git a/crates/elp/src/server.rs b/crates/elp/src/server.rs index bec67159cd..523be45ed5 100644 --- a/crates/elp/src/server.rs +++ b/crates/elp/src/server.rs @@ -1453,6 +1453,16 @@ impl Server { self.file_set_config = folders.file_set_config; + let watch_count = folders.watch.len(); + let buck_query_config = self.reload_manager.lock().get_query_config(); + let data = serde_json::json!({ + "app_count": project_apps.all_apps.len(), + "project_count": projects.len(), + "watch_count": watch_count, + "query_config": buck_query_config.to_string(), + }); + telemetry::send("project_size".to_string(), data); + let register_options = lsp_types::DidChangeWatchedFilesRegistrationOptions { watchers: folders.watch, }; diff --git a/crates/project_model/src/buck.rs b/crates/project_model/src/buck.rs index 064c93de26..e360ec848b 100644 --- a/crates/project_model/src/buck.rs +++ b/crates/project_model/src/buck.rs @@ -873,6 +873,16 @@ pub enum BuckQueryConfig { BuckTargetsOnly, } +impl fmt::Display for BuckQueryConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + BuckQueryConfig::BuildGeneratedCode => write!(f, "BuildGeneratedCode"), + BuckQueryConfig::NoBuildGeneratedCode => write!(f, "NoBuildGeneratedCode"), + BuckQueryConfig::BuckTargetsOnly => write!(f, "BuckTargetsOnly"), + } + } +} + fn filter_buck_targets( buck_config: &BuckConfig, result: FxHashMap, From 7b124c7f069b9a41145d590de610f6cc5e76ed52 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Fri, 28 Nov 2025 02:27:14 -0800 Subject: [PATCH 055/142] Server: Impose a priority on next task selection Summary: The ELP main loop is a message pump. It calls `next_message` and processes it, until done. There are multiple message sources, and the current code takes one at random from all available sources. This diff changes this to an ordered selection, giving explicit priority when more than one source is ready at the same time. This is motivated by 1. Make sure the messages we send go out as soon as possible (progress, telemetry) 2. Now that the vfs loader uses all available cores to speed up loading, make sure the loader progress messages do not drown out all others. Reviewed By: michalmuskala Differential Revision: D87984388 fbshipit-source-id: 3b8cc5f6614bdf9699e805b03da6b5696829d280 --- crates/elp/src/server.rs | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/crates/elp/src/server.rs b/crates/elp/src/server.rs index 523be45ed5..65c75a1bbc 100644 --- a/crates/elp/src/server.rs +++ b/crates/elp/src/server.rs @@ -24,7 +24,7 @@ use anyhow::bail; use capabilities::text_document_symbols_dynamic_registration; use crossbeam_channel::Receiver; use crossbeam_channel::Sender; -use crossbeam_channel::select; +use crossbeam_channel::select_biased; use dispatch::NotificationDispatcher; use elp_eqwalizer::ast::Pos; use elp_eqwalizer::types::Type; @@ -434,27 +434,28 @@ impl Server { } fn next_event(&self) -> Option { - select! { - recv(self.connection.receiver) -> msg => { - msg.ok().map(Event::Lsp) - } - - recv(self.vfs_loader.receiver) -> msg => { - Some(Event::Vfs(msg.unwrap())) - } - - recv(self.task_pool.receiver) -> msg => { - Some(Event::Task(msg.unwrap())) - } - + // Select the next event, in order of priority. + // If multiple operations are ready at the same time, the + // operation nearest to the front of the list is always selected. + select_biased! { + // We want to send progress messages as soon as possible recv(self.progress.receiver()) -> msg => { Some(Event::Task(Task::Progress(msg.unwrap()))) } + // Ditto telemetry recv(telemetry::receiver()) -> msg => { Some(Event::Telemetry(msg.unwrap())) } + recv(self.connection.receiver) -> msg => { + msg.ok().map(Event::Lsp) + } + + recv(self.task_pool.receiver) -> msg => { + Some(Event::Task(msg.unwrap())) + } + recv (self.project_pool.receiver) -> msg => { Some(Event::Task(msg.unwrap())) } @@ -467,6 +468,16 @@ impl Server { Some(Event::Task(msg.unwrap())) } + // We put vfs loader events last, they run with all + // available threads, we do not want to starve the + // other sources. + // Note: when we do process one, we will coalesce + // all pending vfs loader events into a single main loop turn + recv(self.vfs_loader.receiver) -> msg => { + Some(Event::Vfs(msg.unwrap())) + } + + } } From 38c84d39a64573e2bdf9a2cd222e62023906b309 Mon Sep 17 00:00:00 2001 From: Daniel Gorin Date: Fri, 28 Nov 2025 03:19:17 -0800 Subject: [PATCH 056/142] Remove unclear bits of W0050 docs Summary: # Context W0050 asks users to prefer the more efficient `tuple_size/1` and `byte_size/1`, instead of `size/1`. # Problem The explanation is muddled by some advice to always guard the usages of `byte_size/1` with `is_binary/1`. This doesn't make intuitive sense and there's nothing in the docs of `byte_size/1` that suggests this is advisable in any way. # This diff Remove this part. I'm also removing the part that rewrites `size/1` in terms of `byte_size/1` and `tuple_size/1` as it doesn't seem to be adding anything. Reviewed By: TD5 Differential Revision: D87986348 fbshipit-source-id: dbd6aeec71989bc367f3d22b58a71d893b7fff76 --- website/docs/erlang-error-index/w/W0050.md | 42 ++++++++-------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/website/docs/erlang-error-index/w/W0050.md b/website/docs/erlang-error-index/w/W0050.md index a0d43d7171..8c8210dcca 100644 --- a/website/docs/erlang-error-index/w/W0050.md +++ b/website/docs/erlang-error-index/w/W0050.md @@ -20,40 +20,26 @@ size(Input) -> ## Explanation -The [`size/1`](https://www.erlang.org/doc/apps/erts/erlang#size/1) BIF returns -the size for both tuples and binaries. +The [`size/1`](https://www.erlang.org/doc/apps/erts/erlang#size/1) BIF can be +used to return the size tuples, binaries and bitstrings. -The BIF is not optimized by the -[JIT](https://www.erlang.org/doc/apps/erts/beamasm.html) though, and its use can -result in worse types for -[dialyzer](https://www.erlang.org/doc/apps/dialyzer/dialyzer.html) and -[eqWAlizer](https://github.com/WhatsApp/eqwalizer). +However, this BIF is not optimized by the +[JIT](https://www.erlang.org/doc/apps/erts/beamasm.html); it may report +inaccurate values for bitstrings, and its use can result in worse types for +[dialyzer](https://www.erlang.org/doc/apps/dialyzer/dialyzer.html). -When one knows that the value being tested must be a tuple, `tuple_size/1` -should always be preferred. +What to do instead: -When one knows that the value being tested must be a binary, `byte_size/1` -should be preferred. However, `byte_size/1` also accepts a bitstring (rounding -up size to a whole number of bytes), so one must make sure that the call to -`byte_size/1` is preceded by a call to `is_binary/1` to ensure that bitstrings -are rejected. Note that the compiler removes redundant calls to `is_binary/1`, -so if one is not sure whether previous code had made sure that the argument is a -binary, it does not harm to add an `is_binary/1` test immediately before the -call to `byte_size/1`. +- For tuples, use `tuple_size/1`. -For the above use case, a better approach would be: +- For binaries, use `byte_size/1`. + +- For bitstrings, use `byte_size/1`, but please notice that while `byte_size/1` + rounds _up_ to the nearest byte, `size/1` would round _down_: ```erlang --module(main). --export([size/1]). - --type input() :: tuple() | binary(). - --spec size(input()) -> non_neg_integer(). -size(Input) when is_tuple(Input) -> - erlang:tuple_size(Input); -size(Input) when is_binary(Input) -> - erlang:byte_size(Input). +4 = byte_size(<<0:25>>). +3 = size(<<0:25>>). ``` For more information, see the Erlang From 87bc60c5248b781687fd17e176fc2c4e2182b8d5 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Fri, 28 Nov 2025 12:31:40 -0800 Subject: [PATCH 057/142] Remove support for obsolete test_utils and elp_enabled Buck2 labels Summary: Simplify the classification of test applications, by removing support for the obsolete "test_utils" and "elp_enabled" labels. A subsequent diff will make it possible to customize the labels via the `.elp.toml` configuration file. Reviewed By: michalmuskala, alanz Differential Revision: D88004904 fbshipit-source-id: fde38e8ce5a4f5c742c647a63ef736d3bbc84f51 --- crates/project_model/src/buck.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/project_model/src/buck.rs b/crates/project_model/src/buck.rs index e360ec848b..d0498a237a 100644 --- a/crates/project_model/src/buck.rs +++ b/crates/project_model/src/buck.rs @@ -823,13 +823,11 @@ fn compute_target_type( if is_prelude_as_third_party || name.contains("//third-party") { TargetType::ThirdParty } else { - let test_utils = target.labels.contains("test_utils"); let test_application = target.labels.contains("test_application"); - let elp_enabled = target.labels.contains("elp_enabled"); - match (elp_enabled, test_application, test_utils) { - (true, _, _) => TargetType::ErlangApp, - (false, false, false) => TargetType::ErlangApp, - (_, _, _) => TargetType::ErlangTestUtils, + if test_application { + TargetType::ErlangTestUtils + } else { + TargetType::ErlangApp } } } From 3e873e691d6711b17850e723c02899a43301bdd9 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Fri, 28 Nov 2025 12:31:40 -0800 Subject: [PATCH 058/142] Make labels to denote test applications configurable Summary: Make it possible to customize the set of labels that in a Buck2 `erlang_application` target can identify a "test" application. Make it default to "test application". Reviewed By: alanz, michalmuskala Differential Revision: D88005051 fbshipit-source-id: d5a7cbb89373def5796420fa178c0a86fd72e3ed --- crates/project_model/src/buck.rs | 29 +++++++++++++++---- crates/project_model/src/lib.rs | 5 ++++ .../get-started/configure-project/elp-toml.md | 18 ++++++++++-- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/crates/project_model/src/buck.rs b/crates/project_model/src/buck.rs index d0498a237a..09e2a7b131 100644 --- a/crates/project_model/src/buck.rs +++ b/crates/project_model/src/buck.rs @@ -88,6 +88,14 @@ pub struct BuckConfig { #[serde(default)] pub excluded_targets: Vec, pub(crate) source_root: Option, + /// Buck2 labels that denote test applications. + /// Defaults to ["test_application"] if not specified. + #[serde(default = "default_test_application_labels")] + pub test_application_labels: Vec, +} + +fn default_test_application_labels() -> Vec { + vec!["test_application".to_string()] } impl BuckConfig { @@ -726,7 +734,7 @@ fn load_buck_targets_bxl( root, name, buck_target, - buck_config.build_deps, + buck_config, targets_include_prelude, &mut dep_path, &mut target_info, @@ -747,7 +755,7 @@ fn make_buck_target( root: &AbsPathBuf, name: &String, target: &BuckTarget, - build_deps: bool, + buck_config: &BuckConfig, targets_include_prelude: bool, dep_path: &mut FxHashMap, target_info: &mut TargetInfo, @@ -763,7 +771,12 @@ fn make_buck_target( (src, TargetType::ErlangTest, false, None) } else { let mut private_header = false; - let target_type = compute_target_type(name, target, targets_include_prelude); + let target_type = compute_target_type( + name, + target, + targets_include_prelude, + &buck_config.test_application_labels, + ); let mut src_files = vec![]; for src in &target.srcs { let src = json::canonicalize(buck_path_to_abs_path(root, src).unwrap())?; @@ -774,7 +787,7 @@ fn make_buck_target( } let ebin = match target_type { - TargetType::ThirdParty if build_deps => dep_path + TargetType::ThirdParty if buck_config.build_deps => dep_path .remove(name) .map(|dir| dir.join(Utf8PathBuf::from("ebin"))), TargetType::ThirdParty => Some(dir.clone()), @@ -817,14 +830,17 @@ fn compute_target_type( name: &TargetFullName, target: &BuckTarget, targets_include_prelude: bool, + test_application_labels: &[String], ) -> TargetType { // Check if we are trying to work on the prelude itself let is_prelude_as_third_party = !targets_include_prelude && name.starts_with("prelude//"); if is_prelude_as_third_party || name.contains("//third-party") { TargetType::ThirdParty } else { - let test_application = target.labels.contains("test_application"); - if test_application { + let is_test_application = test_application_labels + .iter() + .any(|label| target.labels.contains(label)); + if is_test_application { TargetType::ErlangTestUtils } else { TargetType::ErlangApp @@ -1609,6 +1625,7 @@ mod tests { included_targets: vec![], excluded_targets: vec![], source_root: None, + test_application_labels: vec!["test_application".to_string()], }; let generated_args = if build_generated { vec!["--build_generated_code", "true"] diff --git a/crates/project_model/src/lib.rs b/crates/project_model/src/lib.rs index 53e550d05a..2c8727399c 100644 --- a/crates/project_model/src/lib.rs +++ b/crates/project_model/src/lib.rs @@ -1839,6 +1839,7 @@ mod tests { "root//target/four".to_string(), ], source_root: Some(PathBuf::from("path/to/root")), + test_application_labels: vec!["test_application".to_string()], }), eqwalizer: EqwalizerConfig { enable_all: true, @@ -1864,6 +1865,7 @@ mod tests { included_targets = ["root//target/one", "root//target/two"] excluded_targets = ["root//target/three", "root//target/four"] source_root = "path/to/root" + test_application_labels = ["test_application"] [eqwalizer] enable_all = true @@ -1937,6 +1939,9 @@ mod tests { source_root: Some( "path/to/root", ), + test_application_labels: [ + "test_application", + ], }, ), eqwalizer: EqwalizerConfig { diff --git a/website/docs/get-started/configure-project/elp-toml.md b/website/docs/get-started/configure-project/elp-toml.md index 931d207989..90a9eb5f1e 100644 --- a/website/docs/get-started/configure-project/elp-toml.md +++ b/website/docs/get-started/configure-project/elp-toml.md @@ -111,9 +111,21 @@ Configure the interaction between ELP and the [Buck2](https://buck2.build/) build tool. See [this presentation](https://youtu.be/4ALgsBqNBhQ) for details about Erlang support for Buck2. -| Key | Type | Description | -| ------- | ------- | -------------------------------- | -| enabled | Boolean | Discover the project using Buck2 | +| Key | Type | Description | Default | +| ----------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------ | ---------------------- | +| enabled | Boolean | Discover the project using Buck2 | | +| test_application_labels | Array of Strings | Buck2 labels that identify test application targets. Targets with any of these labels will be treated as test utilities. | `["test_application"]` | + +The `test_application_labels` setting allows you to customize which Buck2 labels +indicate test applications. This is useful for: + +Example usage: + +```toml +[buck] +enabled = true +test_application_labels = ["test_application", "integration_test", "e2e_test"] +``` :::warning From 89c0c87b0f23ea14ee6f818f9bc15afad46f17df Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Mon, 1 Dec 2025 07:13:44 -0800 Subject: [PATCH 059/142] Do not check for eqwalizer disappearing if we did not unpack it Summary: The normal case for the ELP executable is to have a copy of eqwalizer as a Graalvm exe embedded inside ELP. At startup, this is unpacked to a temporary directory, so we can run it. In the past we had issues where external processes would clean up the temporary directory, and so it would disappear. To mitigate this, we check if the file still exists before running it, and recreate it if not. BUT, if we are running with ELP_EQWALIZER_PATH pointing to the eqwalizer jar file, as per the OSS local build instructions, then we do not unpack it to a temporary directory, *and* the command name is set to simply "java", which is not a file, as it should be resolved from the path. As a result the current existence check fails, and we continuously report that eqwalizer has disappeared. This diff changes the check to only be made if we have in fact unpacked eqwalizer to a temporary directory. Reviewed By: TD5 Differential Revision: D88071028 fbshipit-source-id: 21e7c4897dd6a8f6ae6fe137c413b2fab094d936 --- crates/eqwalizer/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/eqwalizer/src/lib.rs b/crates/eqwalizer/src/lib.rs index e6f49460a1..54333570fd 100644 --- a/crates/eqwalizer/src/lib.rs +++ b/crates/eqwalizer/src/lib.rs @@ -244,7 +244,9 @@ impl EqwalizerExe { impl Eqwalizer { fn cmd(&self) -> Command { let mut exe = EQWALIZER_EXE.lock(); - if !exe.cmd.is_file() { + // This check is for the temporary directory being removed by an external process. + // As such, it is only applicable if ther *is* a temporary directory. + if exe._file.is_some() && !exe.cmd.is_file() { log::error!("Eqwalizer exe has disappeared, recreating"); // We have a problem with the eqwalizer exe file, recreate it *exe = EqwalizerExe::ensure_exe(); From 270b8f46143ac9788756c59cd4d38dae395adb12 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Mon, 1 Dec 2025 08:24:18 -0800 Subject: [PATCH 060/142] Fix support for rebar3 projects in IDE Summary: # Context A number of `rebar3` users reported false positive diagnostics, including `duplicated module` when using ELP in combination with a basic rebar3 umbrella project. The problem only appears after the project is compiled. # Problem The issue is caused by a bug in the project discovery for rebar3. The project is not correctly recognized as a `rebar3` project, but as a `No manifest` one. This causes ELP to recursively look for Erlang applications within the project directory, discarding the rebar3 structure. Since rebar3 projects populate a `_build` directory with symlinks to the project applications, each application is found twice, leading to "duplicated module" diagnostics. This is also behind a number of related issues, since paths (e.g. `extra_src_dirs`) are not correctly discovered. # Solution The `path` argument for the `discover_in_place` function contains the path to the `.elp.toml` configuration file. When looking for rebar3 projects, we are passing the path as-is, which is why discovery fails. Instead, we should be passing the base directory for the file, where the `rebar.config` resides. Reviewed By: alanz Differential Revision: D88071314 fbshipit-source-id: d79edbcdf7a01512f753db668606cb798e65b74b --- crates/project_model/src/lib.rs | 62 ++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/crates/project_model/src/lib.rs b/crates/project_model/src/lib.rs index 2c8727399c..233c687afc 100644 --- a/crates/project_model/src/lib.rs +++ b/crates/project_model/src/lib.rs @@ -339,7 +339,13 @@ impl ProjectManifest { pub fn discover_in_place(path: &AbsPath, rebar_profile: Profile) -> Result { let _timer = timeit!("discover projects in place"); // We skip looking for the TOML file since we have already found it. - if let Some(r) = Self::discover_rebar(path, Some(rebar_profile), IncludeParentDirs::No)? { + if let Some(elp_config_basedir) = path.parent() + && let Some(r) = Self::discover_rebar( + &elp_config_basedir.to_path_buf(), + Some(rebar_profile), + IncludeParentDirs::No, + )? + { return Ok(r); } if let Some(s) = Self::discover_static(path, IncludeParentDirs::No)? { @@ -1571,6 +1577,60 @@ mod tests { .assert_eq(&debug_normalise_temp_dir(dir, &manifest)); } + #[test] + fn test_toml_empty_rebar_config() { + if cfg!(feature = "buck") { + let spec = r#" + //- /root/.elp.toml + //- /root/rebar.config + //- /root/app_a/src/app.erl + -module(app). + "#; + let dir = FixtureWithProjectMeta::gen_project(spec); + let discovered = ProjectManifest::discover( + &to_abs_path_buf(&dir.path().join("root/app_a/src/app.erl")).unwrap(), + ); + expect![[r#" + Ok( + ( + ElpConfig { + config_path: Some( + AbsPathBuf( + "TMPDIR/root/.elp.toml", + ), + ), + build_info: None, + buck: None, + eqwalizer: EqwalizerConfig { + enable_all: true, + max_tasks: 4, + ignore_modules: [], + ignore_modules_compiled_patterns: [], + }, + rebar: ElpRebarConfig { + profile: "test", + }, + otp: OtpConfig { + exclude_apps: [], + }, + }, + Rebar( + RebarConfig { + config_file: AbsPathBuf( + "TMPDIR/root/rebar.config", + ), + profile: Profile( + "test", + ), + }, + ), + ), + ) + "#]] + .assert_eq(&debug_normalise_temp_dir(dir, &discovered)); + } + } + #[test] fn test_toml_empty() { // This one is a real worst-case. We force discovery to happen in From eafb4940c73510967a4e8a5768736d53bacd9870 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Mon, 1 Dec 2025 09:05:40 -0800 Subject: [PATCH 061/142] Convert edoc linter to use a trait Summary: Mechanical conversion. Reviewed By: alanz Differential Revision: D87546425 fbshipit-source-id: 4d938866586d89f9a6751e9b0c787c1faf1759d0 --- crates/ide/src/diagnostics.rs | 2 +- crates/ide/src/diagnostics/edoc.rs | 189 ++++++++++++++++++----------- 2 files changed, 118 insertions(+), 73 deletions(-) diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index de828e5086..b72e695325 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -1508,7 +1508,6 @@ pub fn diagnostics_descriptors<'a>() -> Vec<&'a DiagnosticDescriptor<'a>> { &boolean_precedence::DESCRIPTOR, &record_tuple_match::DESCRIPTOR, &unspecific_include::DESCRIPTOR, - &edoc::DESCRIPTOR, ] } @@ -1595,6 +1594,7 @@ const GENERIC_LINTERS: &[&dyn GenericDiagnostics] = &[ &duplicate_module::LINTER, &no_nowarn_suppressions::LINTER, ¯o_precedence_suprise::LINTER, + &edoc::LINTER, ]; /// Unified registry for all types of linters diff --git a/crates/ide/src/diagnostics/edoc.rs b/crates/ide/src/diagnostics/edoc.rs index 0daae4f7f6..ead44d7331 100644 --- a/crates/ide/src/diagnostics/edoc.rs +++ b/crates/ide/src/diagnostics/edoc.rs @@ -10,6 +10,7 @@ // Diagnostic: edoc +use elp_ide_assists::Assist; use elp_ide_assists::helpers; use elp_ide_assists::helpers::extend_range; use elp_ide_db::elp_base_db::FileId; @@ -17,87 +18,136 @@ use elp_ide_db::source_change::SourceChangeBuilder; use elp_ide_db::text_edit::TextRange; use elp_ide_db::text_edit::TextSize; use elp_syntax::AstNode; +use elp_syntax::ast; use fxhash::FxHashSet; use hir::Attribute; +use hir::InFileAstPtr; use hir::Semantic; use hir::edoc::EdocHeader; use hir::edoc::EdocHeaderKind; use hir::known; -use super::Diagnostic; use super::DiagnosticCode; -use super::DiagnosticConditions; -use super::DiagnosticDescriptor; +use super::GenericLinter; +use super::GenericLinterMatchContext; +use super::Linter; use super::Severity; -const DIAGNOSTIC_CODE: DiagnosticCode = DiagnosticCode::OldEdocSyntax; -const DIAGNOSTIC_MESSAGE: &str = "EDoc style comments are deprecated. Please use Markdown instead."; -const CONVERT_FIX_ID: &str = "convert_to_markdown"; -const CONVERT_FIX_LABEL: &str = "Convert to Markdown"; +pub(crate) struct EdocLinter; -pub(crate) static DESCRIPTOR: DiagnosticDescriptor = DiagnosticDescriptor { - conditions: DiagnosticConditions { - experimental: false, - include_generated: false, - include_tests: true, - default_disabled: false, - }, - checker: &|diags, sema, file_id, _ext| { - check(diags, sema, file_id); - }, -}; +impl Linter for EdocLinter { + fn id(&self) -> DiagnosticCode { + DiagnosticCode::OldEdocSyntax + } -fn check(diagnostics: &mut Vec, sema: &Semantic, file_id: FileId) { - if let Some(comments) = sema.file_edoc_comments(file_id) { - for header in comments.values() { - if let Some(doc) = &header.doc { - if let Some(doc_start) = header.start() { - diagnostics.push(old_edoc_syntax_diagnostic( - sema, file_id, doc.range, header, doc_start, - )); - } - } else if let Some(equiv) = &header.equiv { - if let Some(doc_start) = header.start() { - diagnostics.push(old_edoc_syntax_diagnostic( - sema, - file_id, - equiv.range, - header, - doc_start, - )); - } - } else if let Some(deprecated) = &header.deprecated { - if let Some(doc_start) = header.start() { - diagnostics.push(old_edoc_syntax_diagnostic( - sema, - file_id, - deprecated.range, - header, - doc_start, - )); - } - } else if let Some(hidden) = &header.hidden - && let Some(doc_start) = header.start() - { - diagnostics.push(old_edoc_syntax_diagnostic( - sema, - file_id, - hidden.range, - header, - doc_start, - )); - } + fn description(&self) -> &'static str { + "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 old_edoc_syntax_diagnostic( +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct Context { + header_ptr: Option>, + doc_start: TextSize, + range: TextRange, +} + +impl GenericLinter for EdocLinter { + type Context = Context; + + fn matches( + &self, + sema: &Semantic, + file_id: FileId, + ) -> Option>> { + let mut res = Vec::new(); + if let Some(comments) = sema.file_edoc_comments(file_id) { + for (header_ptr, header) in comments.iter() { + if let Some(doc) = &header.doc { + if let Some(doc_start) = header.start() { + res.push(GenericLinterMatchContext { + range: doc.range, + context: Context { + header_ptr: Some(*header_ptr), + doc_start, + range: doc.range, + }, + }); + } + } else if let Some(equiv) = &header.equiv { + if let Some(doc_start) = header.start() { + res.push(GenericLinterMatchContext { + range: equiv.range, + context: Context { + header_ptr: Some(*header_ptr), + doc_start, + range: equiv.range, + }, + }); + } + } else if let Some(deprecated) = &header.deprecated { + if let Some(doc_start) = header.start() { + res.push(GenericLinterMatchContext { + range: deprecated.range, + context: Context { + header_ptr: Some(*header_ptr), + doc_start, + range: deprecated.range, + }, + }); + } + } else if let Some(hidden) = &header.hidden + && let Some(doc_start) = header.start() + { + res.push(GenericLinterMatchContext { + range: hidden.range, + context: Context { + header_ptr: Some(*header_ptr), + doc_start, + range: hidden.range, + }, + }); + } + } + } + Some(res) + } + + fn fixes( + &self, + context: &Self::Context, + sema: &Semantic, + file_id: FileId, + ) -> Option> { + let comments = sema.file_edoc_comments(file_id)?; + let header_ptr = context.header_ptr.as_ref()?; + let header = comments.get(header_ptr)?; + Some(vec![old_edoc_syntax_fix( + sema, + file_id, + header, + context.doc_start, + context.range, + )]) + } +} + +pub static LINTER: EdocLinter = EdocLinter; + +fn old_edoc_syntax_fix( sema: &Semantic, file_id: FileId, - show_range: TextRange, header: &EdocHeader, start_offset: TextSize, -) -> Diagnostic { + range: TextRange, +) -> Assist { let eep59_insert_offset = match header.kind { EdocHeaderKind::Module => { helpers::moduledoc_insert_offset(sema, file_id).unwrap_or(start_offset) @@ -135,17 +185,12 @@ fn old_edoc_syntax_diagnostic( } builder.insert(eep59_insert_offset, header.to_eep59()); let source_change = builder.finish(); - let fix = crate::fix(CONVERT_FIX_ID, CONVERT_FIX_LABEL, source_change, show_range); - Diagnostic::new(DIAGNOSTIC_CODE, DIAGNOSTIC_MESSAGE, show_range) - .with_severity(severity(sema, file_id)) - .with_fixes(Some(vec![fix])) -} - -fn severity(sema: &Semantic, file_id: FileId) -> Severity { - match sema.db.is_test_suite_or_test_helper(file_id) { - Some(true) => Severity::WeakWarning, - _ => Severity::Warning, - } + crate::fix( + "convert_to_markdown", + "Convert to Markdown", + source_change, + range, + ) } fn author_exists(author: &str, authors: &FxHashSet) -> bool { From f80d50389d92dd3858b152c02886266c90fe257f Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Tue, 2 Dec 2025 01:53:20 -0800 Subject: [PATCH 062/142] remove --prefix command line argument Summary: This should no longer be needed, remove it to reduce code complexity Reviewed By: jcpetruzza Differential Revision: D87929715 fbshipit-source-id: c62a6e07084d555bba12dafb96fb893e33f6dc9f --- crates/elp/src/bin/args.rs | 4 -- crates/elp/src/bin/glean.rs | 54 ++----------------- crates/elp/src/bin/lint_cli.rs | 11 +--- crates/elp/src/bin/main.rs | 32 ----------- .../elp/src/resources/test/glean_help.stdout | 3 +- .../elp/src/resources/test/lint_help.stdout | 3 +- .../parse_elp_lint_json_output_prefix.stdout | 3 -- 7 files changed, 8 insertions(+), 102 deletions(-) delete mode 100644 crates/elp/src/resources/test/linter/parse_elp_lint_json_output_prefix.stdout diff --git a/crates/elp/src/bin/args.rs b/crates/elp/src/bin/args.rs index f533688b82..f63d569b52 100644 --- a/crates/elp/src/bin/args.rs +++ b/crates/elp/src/bin/args.rs @@ -274,8 +274,6 @@ pub struct Lint { guard(format_guard, "Please use json") )] pub format: Option, - /// Optional prefix to prepend to each diagnostic file path. Only used when --format=json is set - pub prefix: Option, /// Include diagnostics produced by erlc pub include_erlc_diagnostics: bool, @@ -469,8 +467,6 @@ pub struct Glean { pub pretty: bool, /// Output each fact separately pub multi: bool, - /// Optional prefix to prepend to each fact - pub prefix: Option, } #[derive(Clone, Debug, Bpaf)] diff --git a/crates/elp/src/bin/glean.rs b/crates/elp/src/bin/glean.rs index dad833e0c1..c31d45dc9e 100644 --- a/crates/elp/src/bin/glean.rs +++ b/crates/elp/src/bin/glean.rs @@ -11,7 +11,6 @@ use core::option::Option::None; use std::io::Write; use std::mem; -use std::path::Path; use anyhow::Result; use elp::build::load; @@ -93,7 +92,6 @@ struct GleanFileId(u32); #[derive(Clone, Debug, Default)] struct IndexConfig { pub multi: bool, - pub prefix: Option, } impl From for FileId { @@ -769,10 +767,7 @@ pub struct GleanIndexer { pub fn index(args: &Glean, cli: &mut dyn Cli, query_config: &BuckQueryConfig) -> Result<()> { let (indexer, _loaded) = GleanIndexer::new(args, cli, query_config)?; - let config = IndexConfig { - multi: args.multi, - prefix: args.prefix.clone(), - }; + let config = IndexConfig { multi: args.multi }; let (facts, module_index) = indexer.index(config)?; write_results(facts, module_index, cli, args) } @@ -861,14 +856,7 @@ impl GleanIndexer { let source_root_id = db.file_source_root(file_id); let source_root = db.source_root(source_root_id); let path = source_root.path_for_file(&file_id).unwrap(); - match Self::index_file( - db, - file_id, - path, - project_id, - &module_index, - config.prefix.as_ref(), - ) { + match Self::index_file(db, file_id, path, project_id, &module_index) { Some((file, line, decl, xref, facts, module_fact)) => { let mut result = FxHashMap::default(); result.insert( @@ -884,14 +872,7 @@ impl GleanIndexer { .into_par_iter() .map_with(self.analysis.clone(), |analysis, (file_id, path)| { analysis.with_db(|db| { - Self::index_file( - db, - file_id, - &path, - project_id, - &module_index, - config.prefix.as_ref(), - ) + Self::index_file(db, file_id, &path, project_id, &module_index) }) }) .flatten() @@ -948,7 +929,6 @@ impl GleanIndexer { path: &VfsPath, project_id: ProjectId, module_index: &FxHashMap, - prefix: Option<&String>, ) -> Option<( FileFact, FileLinesFact, @@ -957,7 +937,7 @@ impl GleanIndexer { Option<(Vec, XRefFact)>, Option, )> { - let file_fact = Self::file_fact(db, file_id, path, project_id, prefix)?; + let file_fact = Self::file_fact(db, file_id, path, project_id)?; let line_fact = Self::line_fact(db, file_id); let mut xref_v2 = Self::xrefs_v2(db, file_id, module_index); let mut file_decl = Self::declarations_v2(db, file_id, path)?; @@ -1173,16 +1153,12 @@ impl GleanIndexer { file_id: FileId, path: &VfsPath, project_id: ProjectId, - prefix: Option<&String>, ) -> Option { let project_data = db.project_data(project_id); let root = project_data.root_dir.as_path(); let file_path = path.as_path()?; let file_path = file_path.strip_prefix(root)?; - let file_path = match prefix { - Some(prefix) => Path::new(&prefix).join(file_path).to_str()?.into(), - None => file_path.as_str().to_string(), - }; + let file_path = file_path.as_str().to_string(); Some(FileFact::new(file_id, file_path)) } @@ -2063,7 +2039,6 @@ mod tests { v2: true, pretty: false, multi: false, - prefix: None, }; let mut module_index = FxHashMap::default(); module_index.insert(file_id.into(), module_name.to_string()); @@ -2090,25 +2065,6 @@ mod tests { ); } - #[test] - fn file_fact_prefix_test() { - let spec = r#" - //- /glean/app_glean/src/glean_module2.erl - -module(glean_module2). - "#; - let config = IndexConfig { - multi: false, - prefix: Some("my/prefix".to_string()), - }; - let result = facts_with_annotations_with_config(spec, config).0; - assert_eq!(result.file_facts.len(), 1); - let file_fact = &result.file_facts[0]; - assert_eq!( - file_fact.file_path.as_str(), - "my/prefix/glean/app_glean/src/glean_module2.erl" - ); - } - #[test] fn line_fact_with_new_line_test() { let spec = r#" diff --git a/crates/elp/src/bin/lint_cli.rs b/crates/elp/src/bin/lint_cli.rs index fcce5cac98..6807d81f25 100644 --- a/crates/elp/src/bin/lint_cli.rs +++ b/crates/elp/src/bin/lint_cli.rs @@ -13,7 +13,6 @@ use std::fs; use std::fs::File; use std::io::Write; use std::path::Path; -use std::path::PathBuf; use std::str; use std::sync::Arc; use std::time::SystemTime; @@ -326,12 +325,11 @@ pub fn do_codemod( .unwrap_or_else(|| panic!("could not find project data")) .root_dir; let relative_path = reporting::get_relative_path(root_path, vfs_path); - let prefix = args.prefix.as_ref(); print_diagnostic_json( diag, &analysis, *file_id, - with_prefix(relative_path, prefix).as_path(), + relative_path, args.use_cli_severity, cli, )?; @@ -942,13 +940,6 @@ fn get_form_id_at_offset( Some(form_id) } -fn with_prefix(path: &Path, prefix: Option<&String>) -> PathBuf { - match prefix { - Some(prefix) => Path::new(prefix).join(path), - None => path.into(), - } -} - #[cfg(test)] mod tests { use std::ffi::OsString; diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index 909224f6f2..b227259a52 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -1661,38 +1661,6 @@ mod tests { .expect("bad test"); } - #[test_case(false ; "rebar")] - #[test_case(true ; "buck")] - fn lint_json_output_prefix(buck: bool) { - let tmp_dir = make_tmp_dir(); - let tmp_path = tmp_dir.path(); - check_lint_fix_stderr( - args_vec![ - "lint", - "--diagnostic-filter", - "W0010", - "--experimental", - "--format", - "json", - "--prefix", - "my/prefix" - ], - "linter", - expect_file!("../resources/test/linter/parse_elp_lint_json_output_prefix.stdout"), - 101, - buck, - None, - tmp_path, - Path::new("../resources/test/lint/lint_recursive"), - &[], - false, - Some(expect![[r#" - Errors found - "#]]), - ) - .expect("bad test"); - } - #[test_case(false ; "rebar")] #[test_case(true ; "buck")] fn lint_applies_fix_using_to_dir(buck: bool) { diff --git a/crates/elp/src/resources/test/glean_help.stdout b/crates/elp/src/resources/test/glean_help.stdout index bdde2ec540..c4e938e143 100644 --- a/crates/elp/src/resources/test/glean_help.stdout +++ b/crates/elp/src/resources/test/glean_help.stdout @@ -1,4 +1,4 @@ -Usage: [--project PROJECT] [--module MODULE] [--to TO] [--v2] [--pretty] [--multi] [--prefix ARG] +Usage: [--project PROJECT] [--module MODULE] [--to TO] [--v2] [--pretty] [--multi] Available options: --project Path to directory with project, or to a JSON file (defaults to `.`) @@ -7,5 +7,4 @@ Available options: --v2 Produce glean db with macros, types, xrefs. Incompatible with previous --pretty Pretty print --multi Output each fact separately - --prefix Optional prefix to prepend to each fact -h, --help Prints help information diff --git a/crates/elp/src/resources/test/lint_help.stdout b/crates/elp/src/resources/test/lint_help.stdout index 8d03db6ee3..ec4c25c70a 100644 --- a/crates/elp/src/resources/test/lint_help.stdout +++ b/crates/elp/src/resources/test/lint_help.stdout @@ -1,4 +1,4 @@ -Usage: [--project PROJECT] [--module MODULE] [--app APP] [--file FILE] [--rebar] [--as PROFILE] [--include-generated] [--include-tests] [--no-diags] [[--format FORMAT]] [--prefix ARG] [--include-erlc-diagnostics] [--include-ct-diagnostics] [--include-edoc-diagnostics] [--include-eqwalizer-diagnostics] [--include-suppressed] [--use-cli-severity] [--diagnostic-ignore CODE] [--diagnostic-filter CODE] [--experimental] [--read-config] [--config-file CONFIG_FILE] [--apply-fix] [--ignore-fix-only] [--in-place] [--to TO] [--recursive] [--with-check] [--check-eqwalize-all] [--one-shot] [--report-system-stats] ... +Usage: [--project PROJECT] [--module MODULE] [--app APP] [--file FILE] [--rebar] [--as PROFILE] [--include-generated] [--include-tests] [--no-diags] [[--format FORMAT]] [--include-erlc-diagnostics] [--include-ct-diagnostics] [--include-edoc-diagnostics] [--include-eqwalizer-diagnostics] [--include-suppressed] [--use-cli-severity] [--diagnostic-ignore CODE] [--diagnostic-filter CODE] [--experimental] [--read-config] [--config-file CONFIG_FILE] [--apply-fix] [--ignore-fix-only] [--in-place] [--to TO] [--recursive] [--with-check] [--check-eqwalize-all] [--one-shot] [--report-system-stats] ... Available positional items: Rest of args are space separated list of apps to ignore @@ -14,7 +14,6 @@ Available options: --include-tests Also generate diagnostics for test files --no-diags Do not print the full diagnostics for a file, just the count --format Show diagnostics in JSON format - --prefix Optional prefix to prepend to each diagnostic file path. Only used when --format=json is set --include-erlc-diagnostics Include diagnostics produced by erlc --include-ct-diagnostics Include Common Test diagnostics --include-edoc-diagnostics Include EDoc diagnostics diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_json_output_prefix.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_json_output_prefix.stdout deleted file mode 100644 index 7389cc926a..0000000000 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_json_output_prefix.stdout +++ /dev/null @@ -1,3 +0,0 @@ -{"path":"my/prefix/app_a/src/app_a.erl","line":9,"char":6,"code":"ELP","severity":"warning","name":"W0010 (unused_function_arg)","original":null,"replacement":null,"description":"this variable is unused\n\nFor more information see: /erlang-error-index/w/W0010","docPath":"website/docs/erlang-error-index/w/W0010.md"} -{"path":"my/prefix/app_a/src/app_a_unused_param.erl","line":5,"char":5,"code":"ELP","severity":"warning","name":"W0010 (unused_function_arg)","original":null,"replacement":null,"description":"this variable is unused\n\nFor more information see: /erlang-error-index/w/W0010","docPath":"website/docs/erlang-error-index/w/W0010.md"} -{"path":"my/prefix/app_b/src/app_b_unused_param.erl","line":5,"char":5,"code":"ELP","severity":"warning","name":"W0010 (unused_function_arg)","original":null,"replacement":null,"description":"this variable is unused\n\nFor more information see: /erlang-error-index/w/W0010","docPath":"website/docs/erlang-error-index/w/W0010.md"} From e4b89ebac565bc1fc4e275509c2661e57f91250f Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Tue, 2 Dec 2025 03:07:14 -0800 Subject: [PATCH 063/142] BE: fix clippy warnings Summary: As title Reviewed By: michalmuskala, TD5, robertoaloi Differential Revision: D88143231 fbshipit-source-id: d7eebb3449b3c05a07ba1b5228930c13dd5708f0 --- crates/ide/src/diagnostics.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index b72e695325..b8aa41bcda 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -464,9 +464,10 @@ impl fmt::Display for Diagnostic { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] pub enum Severity { Error, + #[default] Warning, // `WeakWarning` maps onto a Notice warning when used in the LSP // environment, and in VS Code this means it does not show up in @@ -476,12 +477,6 @@ pub enum Severity { Information, } -impl Default for Severity { - fn default() -> Self { - Self::Warning // Pick one - } -} - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum Category { Experimental, From cbe9a058cc3adcb5f66e1e83eac1a33819d94fe9 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Tue, 2 Dec 2025 04:52:04 -0800 Subject: [PATCH 064/142] update dependencies for dependabot Summary: As title Reviewed By: michalmuskala Differential Revision: D88143729 fbshipit-source-id: cd5ecc02442e7661b4bf2f7e3f9b7f346561b2c4 --- website/package.json | 8 +- website/yarn.lock | 621 ++++++++++++++++++++++++------------------- 2 files changed, 353 insertions(+), 276 deletions(-) diff --git a/website/package.json b/website/package.json index f8f738328c..49a34973ca 100644 --- a/website/package.json +++ b/website/package.json @@ -20,8 +20,8 @@ "@docusaurus/preset-classic": "3.9.2", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", - "prism-react-renderer": "^2.3.0", "docusaurus-plugin-internaldocs-fb": "^1.18.4", + "prism-react-renderer": "^2.3.0", "react": "^18.0.0", "react-dom": "^18.0.0" }, @@ -63,6 +63,10 @@ "webpack-dev-server": ">=5.2.1", "on-headers": ">=1.1.0", "mermaid": ">=11.10.0", - "glob": ">=10.5.0" + "glob": ">=10.5.0", + "node-forge": ">=1.3.2", + "mdast-util-to-hast": ">=13.2.1", + "remark-rehype": ">=11.0.0", + "express": ">=4.22.0" } } diff --git a/website/yarn.lock b/website/yarn.lock index 182874330e..1ac9f2033a 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -3251,7 +3251,15 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -accepts@~1.3.4, accepts@~1.3.8: +accepts@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" + integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== + dependencies: + mime-types "^3.0.0" + negotiator "^1.0.0" + +accepts@~1.3.4: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== @@ -3441,11 +3449,6 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" @@ -3589,23 +3592,20 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== -body-parser@1.20.3: - version "1.20.3" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" - integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== +body-parser@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.2.1.tgz#6df606b0eb0a6e3f783dde91dde182c24c82438c" + integrity sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw== dependencies: - bytes "3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.13.0" - raw-body "2.5.2" - type-is "~1.6.18" - unpipe "1.0.0" + bytes "^3.1.2" + content-type "^1.0.5" + debug "^4.4.3" + http-errors "^2.0.0" + iconv-lite "^0.7.0" + on-finished "^2.4.1" + qs "^6.14.0" + raw-body "^3.0.1" + type-is "^2.0.1" bonjour-service@^1.2.1: version "1.3.0" @@ -3708,7 +3708,7 @@ bytes@3.0.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== -bytes@3.1.2: +bytes@3.1.2, bytes@^3.1.2, bytes@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== @@ -3739,6 +3739,14 @@ call-bind-apply-helpers@^1.0.0: es-errors "^1.3.0" function-bind "^1.1.2" +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.7: version "1.0.8" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" @@ -3749,6 +3757,14 @@ call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.7: get-intrinsic "^1.2.4" set-function-length "^1.2.2" +call-bound@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -3782,15 +3798,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001669: - version "1.0.30001687" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz#d0ac634d043648498eedf7a3932836beba90ebae" - integrity sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ== - -caniuse-lite@^1.0.30001754: - version "1.0.30001755" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001755.tgz#c01cfb1c30f5acf1229391666ec03492f4c332ff" - integrity sha512-44V+Jm6ctPj7R52Na4TLi3Zri4dWUljJd+RDm+j8LtNCc/ihLCT+X1TzoOAkRETEWqjuLnh9581Tl80FvK7jVA== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001669, caniuse-lite@^1.0.30001754: + version "1.0.30001757" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz" + integrity sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ== ccount@^2.0.0: version "2.0.1" @@ -4114,14 +4125,12 @@ content-disposition@0.5.2: resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" integrity sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA== -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" +content-disposition@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.1.tgz#a8b7bbeb2904befdfb6787e5c0c086959f605f9b" + integrity sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q== -content-type@~1.0.4, content-type@~1.0.5: +content-type@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== @@ -4131,12 +4140,12 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== +cookie-signature@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" + integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== -cookie@0.7.1, cookie@>=0.7.0: +cookie@>=0.7.0, cookie@^0.7.1: version "1.0.2" resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.0.2.tgz#27360701532116bd3f1f9416929d176afe1e4610" integrity sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA== @@ -4721,6 +4730,13 @@ debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: dependencies: ms "^2.1.3" +debug@^4.3.5, debug@^4.4.3: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + debug@^4.3.6: version "4.4.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" @@ -4812,7 +4828,7 @@ delaunator@5: dependencies: robust-predicates "^3.0.2" -depd@2.0.0: +depd@^2.0.0, depd@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== @@ -4827,11 +4843,6 @@ dequal@^2.0.0, dequal@^2.0.3: resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - detect-node@^2.0.4: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" @@ -5011,6 +5022,15 @@ dot-prop@^6.0.1: dependencies: is-obj "^2.0.0" +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + duplexer@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -5061,12 +5081,7 @@ emoticon@^4.0.1: resolved "https://registry.yarnpkg.com/emoticon/-/emoticon-4.1.0.tgz#d5a156868ee173095627a33de3f1e914c3dde79e" integrity sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ== -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -encodeurl@~2.0.0: +encodeurl@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== @@ -5108,6 +5123,11 @@ es-define-property@^1.0.0: dependencies: get-intrinsic "^1.2.4" +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + es-errors@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" @@ -5118,6 +5138,13 @@ es-module-lexer@^1.2.1: resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78" integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw== +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + esast-util-from-estree@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz#8d1cfb51ad534d2f159dc250e604f3478a79f1ad" @@ -5307,7 +5334,7 @@ eta@^2.2.0: resolved "https://registry.yarnpkg.com/eta/-/eta-2.2.0.tgz#eb8b5f8c4e8b6306561a455e62cd7492fe3a9b8a" integrity sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g== -etag@~1.8.1: +etag@^1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== @@ -5355,42 +5382,39 @@ exenv@^1.2.0: resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" integrity sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw== -express@^4.21.2: - version "4.21.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32" - integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== +express@>=4.22.0, express@^4.21.2: + version "5.2.1" + resolved "https://registry.yarnpkg.com/express/-/express-5.2.1.tgz#8f21d15b6d327f92b4794ecf8cb08a72f956ac04" + integrity sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw== dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.3" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.7.1" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~2.0.0" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.3.1" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.3" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.12" - proxy-addr "~2.0.7" - qs "6.13.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.19.0" - serve-static "1.16.2" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" + accepts "^2.0.0" + body-parser "^2.2.1" + content-disposition "^1.0.0" + content-type "^1.0.5" + cookie "^0.7.1" + cookie-signature "^1.2.1" + debug "^4.4.0" + depd "^2.0.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + finalhandler "^2.1.0" + fresh "^2.0.0" + http-errors "^2.0.0" + merge-descriptors "^2.0.0" + mime-types "^3.0.0" + on-finished "^2.4.1" + once "^1.4.0" + parseurl "^1.3.3" + proxy-addr "^2.0.7" + qs "^6.14.0" + range-parser "^1.2.1" + router "^2.2.0" + send "^1.1.0" + serve-static "^2.2.0" + statuses "^2.0.1" + type-is "^2.0.1" + vary "^1.1.2" exsolve@^1.0.7: version "1.0.7" @@ -5485,18 +5509,17 @@ fill-range@^7.1.1: dependencies: to-regex-range "^5.0.1" -finalhandler@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" - integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== +finalhandler@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.1.1.tgz#a2c517a6559852bcdb06d1f8bd7f51b68fad8099" + integrity sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA== dependencies: - debug "2.6.9" - encodeurl "~2.0.0" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" + debug "^4.4.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + on-finished "^2.4.1" + parseurl "^1.3.3" + statuses "^2.0.1" find-cache-dir@^4.0.0: version "4.0.0" @@ -5556,10 +5579,10 @@ fraction.js@^5.3.4: resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-5.3.4.tgz#8c0fcc6a9908262df4ed197427bdeef563e0699a" integrity sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ== -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== +fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" + integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== fs-extra@^10.1.0: version "10.1.0" @@ -5620,11 +5643,35 @@ get-intrinsic@^1.2.4: has-symbols "^1.0.3" hasown "^2.0.0" +get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + get-stream@^6.0.0, get-stream@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" @@ -5703,7 +5750,7 @@ globby@^13.1.1, globby@^13.1.4: merge2 "^1.4.1" slash "^4.0.0" -gopd@^1.0.1: +gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== @@ -5781,7 +5828,7 @@ has-proto@^1.0.1: dependencies: call-bind "^1.0.7" -has-symbols@^1.0.3: +has-symbols@^1.0.3, has-symbols@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== @@ -6151,16 +6198,16 @@ http-deceiver@^1.2.7: resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== +http-errors@^2.0.0, http-errors@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" + integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" + depd "~2.0.0" + inherits "~2.0.4" + setprototypeof "~1.2.0" + statuses "~2.0.2" + toidentifier "~1.0.1" http-errors@~1.6.2: version "1.6.3" @@ -6216,13 +6263,6 @@ hyperdyperid@^1.2.0: resolved "https://registry.yarnpkg.com/hyperdyperid/-/hyperdyperid-1.2.0.tgz#59668d323ada92228d2a869d3e474d5a33b69e6b" integrity sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A== -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - iconv-lite@0.6: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" @@ -6230,6 +6270,13 @@ iconv-lite@0.6: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +iconv-lite@^0.7.0, iconv-lite@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.7.0.tgz#c50cd80e6746ca8115eb98743afa81aa0e147a3e" + integrity sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + icss-utils@^5.0.0, icss-utils@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" @@ -6288,7 +6335,7 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== -inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: +inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -6521,6 +6568,11 @@ is-plain-object@^5.0.0: resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== +is-promise@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== + is-reference@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-3.0.3.tgz#9ef7bf9029c70a67b2152da4adf57c23d718910f" @@ -6942,14 +6994,10 @@ marked@^16.3.0: resolved "https://registry.yarnpkg.com/marked/-/marked-16.4.2.tgz#4959a64be6c486f0db7467ead7ce288de54290a3" integrity sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA== -mdast-util-definitions@^5.0.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz#9910abb60ac5d7115d6819b57ae0bcef07a3f7a7" - integrity sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA== - dependencies: - "@types/mdast" "^3.0.0" - "@types/unist" "^2.0.0" - unist-util-visit "^4.0.0" +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== mdast-util-directive@^3.0.0: version "3.0.0" @@ -7276,24 +7324,10 @@ mdast-util-phrasing@^4.0.0: "@types/mdast" "^4.0.0" unist-util-is "^6.0.0" -mdast-util-to-hast@^12.1.0: - version "12.3.0" - resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz#045d2825fb04374e59970f5b3f279b5700f6fb49" - integrity sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw== - dependencies: - "@types/hast" "^2.0.0" - "@types/mdast" "^3.0.0" - mdast-util-definitions "^5.0.0" - micromark-util-sanitize-uri "^1.1.0" - trim-lines "^3.0.0" - unist-util-generated "^2.0.0" - unist-util-position "^4.0.0" - unist-util-visit "^4.0.0" - -mdast-util-to-hast@^13.0.0: - version "13.2.0" - resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz#5ca58e5b921cc0a3ded1bc02eed79a4fe4fe41f4" - integrity sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA== +mdast-util-to-hast@>=13.2.1, mdast-util-to-hast@^13.0.0: + version "13.2.1" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz#d7ff84ca499a57e2c060ae67548ad950e689a053" + integrity sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA== dependencies: "@types/hast" "^3.0.0" "@types/mdast" "^4.0.0" @@ -7358,10 +7392,10 @@ mdn-data@2.0.30: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== +media-typer@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" + integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== memfs@^4.6.0: version "4.17.2" @@ -7373,10 +7407,10 @@ memfs@^4.6.0: tree-dump "^1.0.1" tslib "^2.0.0" -merge-descriptors@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" - integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== +merge-descriptors@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" + integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== merge-stream@^2.0.0: version "2.0.0" @@ -7414,11 +7448,6 @@ mermaid@>=11.10.0, mermaid@^10.9.0: ts-dedent "^2.2.0" uuid "^11.1.0" -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - micromark-core-commonmark@^1.0.0, micromark-core-commonmark@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz#1386628df59946b2d39fb2edfd10f3e8e0a75bb8" @@ -8074,7 +8103,7 @@ micromark-util-resolve-all@^2.0.0: dependencies: micromark-util-types "^2.0.0" -micromark-util-sanitize-uri@^1.0.0, micromark-util-sanitize-uri@^1.1.0: +micromark-util-sanitize-uri@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz#613f738e4400c6eedbc53590c67b197e30d7f90d" integrity sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A== @@ -8196,6 +8225,11 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.53.0.tgz#3cb63cd820fc29896d9d4e8c32ab4fcd74ccb447" integrity sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg== +mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + mime-db@~1.33.0: version "1.33.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" @@ -8208,17 +8242,19 @@ mime-types@2.1.18: dependencies: mime-db "~1.33.0" -mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime-types@^3.0.0, mime-types@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.2.tgz#39002d4182575d5af036ffa118100f2524b2e2ab" + integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A== + dependencies: + mime-db "^1.54.0" mimic-fn@^2.1.0: version "2.1.0" @@ -8302,7 +8338,7 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== -ms@2.1.3, ms@^2.1.3: +ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -8339,6 +8375,11 @@ negotiator@0.6.3: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +negotiator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + negotiator@~0.6.4: version "0.6.4" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" @@ -8381,10 +8422,10 @@ node-fetch@2.6.7: dependencies: whatwg-url "^5.0.0" -node-forge@^1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" - integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== +node-forge@>=1.3.2, node-forge@^1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.2.tgz#d0d2659a26eef778bf84d73e7f55c08144ee7750" + integrity sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw== node-releases@^2.0.18: version "2.0.18" @@ -8455,10 +8496,10 @@ object-assign@^4.0.1, object-assign@^4.1.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.13.1: - version "1.13.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.3.tgz#f14c183de51130243d6d18ae149375ff50ea488a" - integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA== +object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== object-is@^1.1.5: version "1.1.6" @@ -8488,7 +8529,7 @@ obuf@^1.0.0, obuf@^1.1.2: resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== -on-finished@2.4.1, on-finished@^2.4.1: +on-finished@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== @@ -8500,6 +8541,13 @@ on-headers@>=1.1.0, on-headers@~1.0.2: resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65" integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== +once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" @@ -8665,7 +8713,7 @@ parse5@^7.0.0: dependencies: entities "^4.5.0" -parseurl@~1.3.2, parseurl@~1.3.3: +parseurl@^1.3.3, parseurl@~1.3.2: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== @@ -8728,11 +8776,6 @@ path-scurry@^2.0.0: lru-cache "^11.0.0" minipass "^7.1.2" -path-to-regexp@0.1.12: - version "0.1.12" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" - integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== - path-to-regexp@3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.3.0.tgz#f7f31d32e8518c2660862b644414b6d5c63a611b" @@ -8745,6 +8788,11 @@ path-to-regexp@^1.7.0: dependencies: isarray "0.0.1" +path-to-regexp@^8.0.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.3.0.tgz#aa818a6981f99321003a08987d3cec9c3474cd1f" + integrity sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA== + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -9456,7 +9504,7 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== -proxy-addr@~2.0.7: +proxy-addr@^2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== @@ -9476,12 +9524,12 @@ pupa@^3.1.0: dependencies: escape-goat "^4.0.0" -qs@6.13.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" - integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== +qs@^6.14.0: + version "6.14.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" + integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== dependencies: - side-channel "^1.0.6" + side-channel "^1.1.0" quansync@^0.2.11: version "0.2.11" @@ -9510,20 +9558,20 @@ range-parser@1.2.0: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" integrity sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A== -range-parser@^1.2.1, range-parser@~1.2.1: +range-parser@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" - integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== +raw-body@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.2.tgz#3e3ada5ae5568f9095d84376fd3a49b8fb000a51" + integrity sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA== dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" + bytes "~3.1.2" + http-errors "~2.0.1" + iconv-lite "~0.7.0" + unpipe "~1.0.0" rc@1.2.8: version "1.2.8" @@ -9908,20 +9956,10 @@ remark-parse@^11.0.0: micromark-util-types "^2.0.0" unified "^11.0.0" -remark-rehype@^10.0.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-10.1.0.tgz#32dc99d2034c27ecaf2e0150d22a6dcccd9a6279" - integrity sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw== - dependencies: - "@types/hast" "^2.0.0" - "@types/mdast" "^3.0.0" - mdast-util-to-hast "^12.1.0" - unified "^10.0.0" - -remark-rehype@^11.0.0: - version "11.1.1" - resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-11.1.1.tgz#f864dd2947889a11997c0a2667cd6b38f685bca7" - integrity sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ== +remark-rehype@>=11.0.0, remark-rehype@^10.0.0, remark-rehype@^11.0.0: + version "11.1.2" + resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-11.1.2.tgz#2addaadda80ca9bd9aa0da763e74d16327683b37" + integrity sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw== dependencies: "@types/hast" "^3.0.0" "@types/mdast" "^4.0.0" @@ -10032,6 +10070,17 @@ roughjs@^4.6.6: points-on-curve "^0.2.0" points-on-path "^0.2.1" +router@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef" + integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ== + dependencies: + debug "^4.4.0" + depd "^2.0.0" + is-promise "^4.0.0" + parseurl "^1.3.3" + path-to-regexp "^8.0.0" + rtlcss@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/rtlcss/-/rtlcss-4.3.0.tgz#f8efd4d5b64f640ec4af8fa25b65bacd9e07cc97" @@ -10076,7 +10125,7 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -10165,24 +10214,22 @@ semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.2, semver@^7.5.4, semve resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== -send@0.19.0: - version "0.19.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" - integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== +send@^1.1.0, send@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/send/-/send-1.2.0.tgz#32a7554fb777b831dfa828370f773a3808d37212" + integrity sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw== dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" + debug "^4.3.5" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + fresh "^2.0.0" + http-errors "^2.0.0" + mime-types "^3.0.1" + ms "^2.1.3" + on-finished "^2.4.1" + range-parser "^1.2.1" + statuses "^2.0.1" serialize-javascript@^6.0.0, serialize-javascript@^6.0.1: version "6.0.2" @@ -10217,15 +10264,15 @@ serve-index@^1.9.1: mime-types "~2.1.17" parseurl "~1.3.2" -serve-static@1.16.2: - version "1.16.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" - integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== +serve-static@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-2.2.0.tgz#9c02564ee259bdd2251b82d659a2e7e1938d66f9" + integrity sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ== dependencies: - encodeurl "~2.0.0" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.19.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + parseurl "^1.3.3" + send "^1.2.0" set-function-length@^1.2.2: version "1.2.2" @@ -10244,7 +10291,7 @@ setprototypeof@1.1.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== -setprototypeof@1.2.0: +setprototypeof@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== @@ -10278,15 +10325,45 @@ shell-quote@^1.8.1: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.2.tgz#d2d83e057959d53ec261311e9e9b8f51dcb2934a" integrity sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA== -side-channel@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== dependencies: - call-bind "^1.0.7" es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.7" @@ -10427,16 +10504,16 @@ srcset@^4.0.0: resolved "https://registry.yarnpkg.com/srcset/-/srcset-4.0.0.tgz#336816b665b14cd013ba545b6fe62357f86e65f4" integrity sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw== -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - "statuses@>= 1.4.0 < 2": version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== +statuses@^2.0.1, statuses@~2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" + integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== + std-env@^3.7.0: version "3.8.0" resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.8.0.tgz#b56ffc1baf1a29dcc80a3bdf11d7fca7c315e7d5" @@ -10715,7 +10792,7 @@ to-vfile@^6.1.0: is-buffer "^2.0.0" vfile "^4.0.0" -toidentifier@1.0.1: +toidentifier@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== @@ -10780,13 +10857,14 @@ type-fest@^2.13.0, type-fest@^2.5.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== +type-is@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.0.1.tgz#64f6cf03f92fce4015c2b224793f6bdd4b068c97" + integrity sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw== dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" + content-type "^1.0.5" + media-typer "^1.1.0" + mime-types "^3.0.0" typedarray-to-buffer@^3.1.5: version "3.1.5" @@ -10885,11 +10963,6 @@ unist-util-find-after@^3.0.0: dependencies: unist-util-is "^4.0.0" -unist-util-generated@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-2.0.1.tgz#e37c50af35d3ed185ac6ceacb6ca0afb28a85cae" - integrity sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A== - unist-util-is@^4.0.0, unist-util-is@^4.0.2: version "4.1.0" resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.1.0.tgz#976e5f462a7a5de73d94b706bac1b90671b57797" @@ -11031,7 +11104,7 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== -unpipe@1.0.0, unpipe@~1.0.0: +unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== @@ -11124,11 +11197,6 @@ utility-types@^3.10.0: resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.11.0.tgz#607c40edb4f258915e901ea7995607fdf319424c" integrity sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw== -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - uuid@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912" @@ -11162,7 +11230,7 @@ value-equal@^1.0.1: resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== -vary@~1.1.2: +vary@^1.1.2, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== @@ -11518,6 +11586,11 @@ wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: string-width "^5.0.1" strip-ansi "^7.0.1" +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + write-file-atomic@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" From 8558c9d5dcd8b416966caa8cef0fe5f558f94b48 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Tue, 2 Dec 2025 08:11:47 -0800 Subject: [PATCH 065/142] Add tree_print for a whole module Summary: As per title. We use tree_print as a development diagnostic tool. The test runner for tree_print itself had code to tree_print an entire module via its forms. This diff extracts this code and makes it available for use elsewhere. Reviewed By: TD5 Differential Revision: D88159685 fbshipit-source-id: daf1bd1c25d22b9d9f48863b428538efe37a267c --- crates/hir/src/body/tree_print.rs | 226 +++++++++++++++++++++++------- 1 file changed, 174 insertions(+), 52 deletions(-) diff --git a/crates/hir/src/body/tree_print.rs b/crates/hir/src/body/tree_print.rs index 1123c46b75..4aa6591d84 100644 --- a/crates/hir/src/body/tree_print.rs +++ b/crates/hir/src/body/tree_print.rs @@ -15,6 +15,8 @@ use std::fmt; use std::fmt::Write as _; use std::str; +use elp_base_db::FileId; + use super::DefineBody; use super::FoldBody; use super::RecordBody; @@ -33,11 +35,15 @@ use crate::ComprehensionExpr; use crate::Define; use crate::Expr; use crate::ExprId; +use crate::FormIdx; use crate::FunType; use crate::FunctionBody; use crate::FunctionClauseBody; +use crate::FunctionDefId; +use crate::InFile; use crate::ListType; use crate::Literal; +use crate::PPDirective; use crate::Pat; use crate::PatId; use crate::Record; @@ -50,6 +56,7 @@ use crate::TermId; use crate::TypeAlias; use crate::TypeExpr; use crate::TypeExprId; +use crate::db::DefDatabase; use crate::db::InternDatabase; use crate::expr::Guards; use crate::expr::MaybeExpr; @@ -332,6 +339,63 @@ pub(crate) fn print_ssr(db: &dyn InternDatabase, body: &SsrBody) -> String { printer.result() } +#[allow(dead_code)] // This is used for debugging +pub fn print_form_list(db: &dyn DefDatabase, file_id: FileId, strategy: Strategy) -> String { + let form_list = db.file_form_list(file_id); + let dbi: &dyn InternDatabase = db; + form_list + .forms() + .iter() + .flat_map(|&form_idx| -> Option { + match form_idx { + FormIdx::FunctionClause(function_id) => { + let body = + db.function_body(InFile::new(file_id, FunctionDefId::new(function_id))); + Some(body.tree_print(dbi, strategy)) + } + FormIdx::TypeAlias(type_alias_id) => { + let type_alias = &form_list[type_alias_id]; + let body = db.type_body(InFile::new(file_id, type_alias_id)); + Some(body.tree_print(dbi, type_alias)) + } + FormIdx::Spec(spec_id) => { + let spec = SpecOrCallback::Spec(form_list[spec_id].clone()); + let body = db.spec_body(InFile::new(file_id, spec_id)); + Some(body.tree_print(dbi, spec)) + } + FormIdx::Callback(callback_id) => { + let spec = SpecOrCallback::Callback(form_list[callback_id].clone()); + let body = db.callback_body(InFile::new(file_id, callback_id)); + Some(body.tree_print(dbi, spec)) + } + FormIdx::Record(record_id) => { + let body = db.record_body(InFile::new(file_id, record_id)); + Some(body.print(dbi, &form_list, record_id)) + } + FormIdx::Attribute(attribute_id) => { + let attribute = AnyAttribute::Attribute(form_list[attribute_id].clone()); + let body = db.attribute_body(InFile::new(file_id, attribute_id)); + Some(body.print(dbi, attribute)) + } + FormIdx::CompileOption(attribute_id) => { + let attribute = AnyAttribute::CompileOption(form_list[attribute_id].clone()); + let body = db.compile_body(InFile::new(file_id, attribute_id)); + Some(body.tree_print(dbi, attribute)) + } + FormIdx::PPDirective(pp) => match form_list[pp] { + PPDirective::Define(define) => { + let body = db.define_body(InFile::new(file_id, define)); + Some(body.tree_print(dbi, &form_list[define])) + } + _ => None, + }, + _ => None, + } + }) + .collect::>() + .join("") +} + struct Printer<'a> { db: &'a dyn InternDatabase, body: &'a FoldBody<'a>, @@ -1523,13 +1587,8 @@ mod tests { use expect_test::Expect; use expect_test::expect; - use crate::AnyAttribute; - use crate::FormIdx; - use crate::FunctionDefId; - use crate::InFile; - use crate::SpecOrCallback; use crate::Strategy; - use crate::db::DefDatabase; + use crate::body::tree_print::print_form_list; use crate::fold::MacroStrategy; use crate::fold::ParenStrategy; use crate::test_db::TestDB; @@ -1546,52 +1605,7 @@ mod tests { #[track_caller] fn check_with_strategy(strategy: Strategy, fixture: &str, expect: Expect) { let (db, file_id) = TestDB::with_single_file(fixture); - let form_list = db.file_form_list(file_id); - let pretty = form_list - .forms() - .iter() - .flat_map(|&form_idx| -> Option { - match form_idx { - FormIdx::FunctionClause(function_id) => { - let body = - db.function_body(InFile::new(file_id, FunctionDefId::new(function_id))); - Some(body.tree_print(&db, strategy)) - } - FormIdx::TypeAlias(type_alias_id) => { - let type_alias = &form_list[type_alias_id]; - let body = db.type_body(InFile::new(file_id, type_alias_id)); - Some(body.tree_print(&db, type_alias)) - } - FormIdx::Spec(spec_id) => { - let spec = SpecOrCallback::Spec(form_list[spec_id].clone()); - let body = db.spec_body(InFile::new(file_id, spec_id)); - Some(body.tree_print(&db, spec)) - } - FormIdx::Callback(callback_id) => { - let spec = SpecOrCallback::Callback(form_list[callback_id].clone()); - let body = db.callback_body(InFile::new(file_id, callback_id)); - Some(body.tree_print(&db, spec)) - } - FormIdx::Record(record_id) => { - let body = db.record_body(InFile::new(file_id, record_id)); - Some(body.print(&db, &form_list, record_id)) - } - FormIdx::Attribute(attribute_id) => { - let attribute = AnyAttribute::Attribute(form_list[attribute_id].clone()); - let body = db.attribute_body(InFile::new(file_id, attribute_id)); - Some(body.print(&db, attribute)) - } - FormIdx::CompileOption(attribute_id) => { - let attribute = - AnyAttribute::CompileOption(form_list[attribute_id].clone()); - let body = db.compile_body(InFile::new(file_id, attribute_id)); - Some(body.tree_print(&db, attribute)) - } - _ => None, - } - }) - .collect::>() - .join(""); + let pretty = print_form_list(&db, file_id, strategy); expect.assert_eq(pretty.trim_start()); } @@ -1999,6 +2013,16 @@ mod tests { foo() -> ?EXPR(2). "#, expect![[r#" + -define(EXPR/1, + Expr<2>:Expr::BinaryOp { + lhs + Expr<0>:Literal(Integer(1)) + rhs + Expr<1>:Expr::Var(X) + op + ArithOp(Add), + } + ). function: foo/0 Clause { pats @@ -3420,4 +3444,102 @@ mod tests { "#]], ); } + + #[test] + fn top_level_macro() { + // Note: we currently lower a macro as an Expr only. + // We have special processing in lower_clause_or_macro_body + // to deal with top level macros, at the ast level. + check( + r#" + -define(FOO(X), baz() -> X). + -define(BAR(X), {X}). + ?FOO(42). + foo() -> ?BAR(42). + "#, + expect![[r#" + -define(FOO/1, + Expr<0>:Expr::Missing + ). + + -define(BAR/1, + Expr<1>:Expr::Tuple { + Expr<0>:Expr::Var(X), + } + ). + function: baz/0 + Clause { + pats + guards + exprs + Expr<2>:Literal(Integer(42)), + }. + function: foo/0 + Clause { + pats + guards + exprs + Expr<4>:Expr::MacroCall { + args + Expr<3>:Literal(Integer(42)), + macro_def + Some(InFile { file_id: FileId(0), value: Idx::(1) }) + expansion + Expr<2>:Expr::Tuple { + Expr<1>:Literal(Integer(42)), + } + }, + }. + "#]], + ); + } + + #[test] + fn top_level_forms() { + check( + r#" + -module(main). + bug + -compile([export_all]). + -wild('foo'). + -type foo() :: ok. + -spec bar() -> ok. + bar() -> ok. + -callback baz() -> ok. + -record(rec, {f}). + "#, + expect![[r#" + -compile( + Term::List { + exprs + Literal(Atom('export_all')), + tail + } + ). + + -wild(foo). + + -type foo() :: Literal(Atom('ok')). + + -spec bar + () -> + Literal(Atom('ok')). + function: bar/0 + Clause { + pats + guards + exprs + Expr<1>:Literal(Atom('ok')), + }. + + -callback baz + () -> + Literal(Atom('ok')). + + -record(rec, { + f + }). + "#]], + ); + } } From 8bd2c5e571793c45c4c6dead3122d3251ee84780 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 3 Dec 2025 03:18:32 -0800 Subject: [PATCH 066/142] Include related information in `elp lint` non-json output Summary: An ELP Diagnostic can have related information in it, which provide additional context to the reported Diagnostic. Although these are shown in the IDE when running as a server, they are not currently shown from the command line. This diff updates the `elp lint` command to display them, when not emitting JSON. There are 3 variations, depending on where the related information is - same file: just show the range - some erlang module: show the module name and range - an erlang include file: show the full path and range Reviewed By: robertoaloi Differential Revision: D88010319 fbshipit-source-id: 26748ec7183a139a57ae97b75a9a61f62da27000 --- crates/elp/src/bin/lint_cli.rs | 69 +++++++++++++++++++ .../diagnostics/parse_elp_lint_fix.stdout | 2 + .../test/linter/parse_elp_lint2.stdout | 1 + .../test/linter/parse_elp_lint_app.stdout | 1 + .../parse_elp_lint_config_output.stdout | 1 + .../parse_elp_lint_fixme_spelling.stdout | 2 + .../parse_elp_no_lint_specified_output.stdout | 2 + .../test/linter/warnings_as_errors.stdout | 2 + 8 files changed, 80 insertions(+) diff --git a/crates/elp/src/bin/lint_cli.rs b/crates/elp/src/bin/lint_cli.rs index 6807d81f25..8450d7b28b 100644 --- a/crates/elp/src/bin/lint_cli.rs +++ b/crates/elp/src/bin/lint_cli.rs @@ -353,6 +353,7 @@ pub fn do_codemod( print_diagnostic( diag, &loaded.analysis(), + &loaded.vfs, *file_id, args.use_cli_severity, cli, @@ -419,12 +420,76 @@ fn get_diagnostics_config(args: &Lint) -> Result { fn print_diagnostic( diag: &diagnostics::Diagnostic, analysis: &Analysis, + vfs: &Vfs, file_id: FileId, use_cli_severity: bool, cli: &mut dyn Cli, ) -> Result<(), anyhow::Error> { let line_index = analysis.line_index(file_id)?; writeln!(cli, " {}", diag.print(&line_index, use_cli_severity))?; + + // Print any related information, indented + if let Some(related_info) = &diag.related_info { + for info in related_info { + let info_line_index = analysis.line_index(info.file_id)?; + let start = info_line_index.line_col(info.range.start()); + let end = info_line_index.line_col(info.range.end()); + + // Include file identifier if related info is from a different file + if info.file_id != file_id { + let file_identifier = + if let Ok(Some(module_name)) = analysis.module_name(info.file_id) { + // It's a module (.erl file), use module name + format!("[{}]", module_name.as_str()) + } else { + // Not a module (e.g., include file), use relative path + let vfs_path = vfs.file_path(info.file_id); + if let Ok(Some(project_data)) = analysis.project_data(info.file_id) { + let relative_path = + reporting::get_relative_path(&project_data.root_dir, vfs_path); + format!("[{}]", relative_path.display()) + } else { + // Fallback: just show location without file identifier + String::new() + } + }; + + if file_identifier.is_empty() { + writeln!( + cli, + " {}:{}-{}:{}: {}", + start.line + 1, + start.col_utf16 + 1, + end.line + 1, + end.col_utf16 + 1, + info.message + )?; + } else { + writeln!( + cli, + " {} {}:{}-{}:{}: {}", + file_identifier, + start.line + 1, + start.col_utf16 + 1, + end.line + 1, + end.col_utf16 + 1, + info.message + )?; + } + } else { + writeln!( + cli, + " {}:{}-{}:{}: {}", + start.line + 1, + start.col_utf16 + 1, + end.line + 1, + end.col_utf16 + 1, + info.message + )?; + } + } + } + Ok(()) } @@ -663,6 +728,7 @@ impl<'a> Lints<'a> { print_diagnostic( diag, &self.analysis_host.analysis(), + self.vfs, *file_id, self.args.use_cli_severity, cli, @@ -738,6 +804,7 @@ impl<'a> Lints<'a> { print_diagnostic( diagnostic, &self.analysis_host.analysis(), + self.vfs, file_id, self.args.use_cli_severity, cli, @@ -805,6 +872,7 @@ impl<'a> Lints<'a> { print_diagnostic( &diagnostic, &self.analysis_host.analysis(), + self.vfs, file_id, self.args.use_cli_severity, cli, @@ -1083,6 +1151,7 @@ mod tests { Diagnostics reported in 1 modules: lints: 1 5:3-5:16::[Error] [P1700] head mismatch 'head_mismatcX' vs 'head_mismatch' + 4:3-4:16: Mismatched clause name "#]], expect![""], ); diff --git a/crates/elp/src/resources/test/diagnostics/parse_elp_lint_fix.stdout b/crates/elp/src/resources/test/diagnostics/parse_elp_lint_fix.stdout index 5cee41edd5..27caa7b1d7 100644 --- a/crates/elp/src/resources/test/diagnostics/parse_elp_lint_fix.stdout +++ b/crates/elp/src/resources/test/diagnostics/parse_elp_lint_fix.stdout @@ -2,10 +2,12 @@ module specified: lints Diagnostics reported in 1 modules: lints: 1 5:1-5:14::[Error] [P1700] head mismatch 'head_mismatcX' vs 'head_mismatch' + 4:1-4:14: Mismatched clause name --------------------------------------------- Applying fix in module 'lints' for 5:1-5:14::[Error] [P1700] head mismatch 'head_mismatcX' vs 'head_mismatch' + 4:1-4:14: Mismatched clause name @@ -1,6 +1,6 @@ -module(lints). -export([head_mismatch/1]). diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint2.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint2.stdout index 00ffa07782..6d3ab47e91 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint2.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint2.stdout @@ -2,3 +2,4 @@ module specified: app_a Diagnostics reported in 1 modules: app_a: 1 9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food' + 7:1-7:5: Mismatched clause name diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_app.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_app.stdout index f1938756fc..0432896183 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_app.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_app.stdout @@ -1,3 +1,4 @@ Diagnostics reported in 1 modules: app_a: 1 9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food' + 7:1-7:5: Mismatched clause name diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_config_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_config_output.stdout index d4ef132ea6..285ca216f3 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_config_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_config_output.stdout @@ -3,6 +3,7 @@ Diagnostics reported in 4 modules: 9:6-9:7::[Warning] [W0010] this variable is unused 5:5-5:35::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` 9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food' + 7:1-7:5: Mismatched clause name app_a_unused_param: 2 5:5-5:6::[Warning] [W0010] this variable is unused 5:5-5:6::[Warning] [L1268] variable 'X' is unused diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_fixme_spelling.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_fixme_spelling.stdout index b8c9d1896e..c7616519bf 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_fixme_spelling.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_fixme_spelling.stdout @@ -2,10 +2,12 @@ module specified: spelling Diagnostics reported in 1 modules: spelling: 1 2:2-2:10::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' + 2:2-2:10: Misspelled attribute --------------------------------------------- Applying fix in module 'spelling' for 2:2-2:10::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' + 2:2-2:10: Misspelled attribute @@ -1,4 +1,5 @@ -module(spelling). +% elp:fixme W0013 (misspelled_attribute) diff --git a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout index 879f0e024c..65f84fc805 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout @@ -3,6 +3,7 @@ Diagnostics reported in 7 modules: app_a: 7 5:5-5:35::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` 9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food' + 7:1-7:5: Mismatched clause name 8:7-8:8::[Warning] [W0018] Unexpected ';' 13:5-13:14::[Warning] [W0026] Function 'app_a:baz/2' is not exported. 12:1-12:4::[Warning] [L1230] function bar/0 is unused @@ -23,3 +24,4 @@ Diagnostics reported in 7 modules: 8:7-9:16::[Warning] [L1318] expression updates a literal spelling: 1 2:2-2:10::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' + 2:2-2:10: Misspelled attribute diff --git a/crates/elp/src/resources/test/linter/warnings_as_errors.stdout b/crates/elp/src/resources/test/linter/warnings_as_errors.stdout index 0625ceb723..706dc801a5 100644 --- a/crates/elp/src/resources/test/linter/warnings_as_errors.stdout +++ b/crates/elp/src/resources/test/linter/warnings_as_errors.stdout @@ -3,6 +3,7 @@ Diagnostics reported in 7 modules: app_a: 7 5:5-5:35::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` 9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food' + 7:1-7:5: Mismatched clause name 8:7-8:8::[Warning] [W0018] Unexpected ';' 13:5-13:14::[Warning] [W0026] Function 'app_a:baz/2' is not exported. 12:1-12:4::[Error] [L1230] function bar/0 is unused @@ -23,3 +24,4 @@ Diagnostics reported in 7 modules: 8:7-9:16::[Error] [L1318] expression updates a literal spelling: 1 2:2-2:10::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' + 2:2-2:10: Misspelled attribute From 1fc2f63da81115faf20d32430f640af4faa52301 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 3 Dec 2025 03:18:32 -0800 Subject: [PATCH 067/142] Correctly report diagnostics from header file Summary: It can happen that an included file has errors in it. ELP is supposed to notice this, and report it with an L0000 (issue_in_included_file) diagnostic. Unfortunately the logic for reporting this in the erlang service did not properly account for the exact location information reported in the `file` attribute, so this always fell back to a location of 0,0 in the including file. As a result, the diagnostic did not show up properly. ## This diff We fix it by reporting the location according to the representation used in the erlang service, and making sure we choose the correct include location. We also add the diagnostic found in the included file as related information, so it is available in the original file Reviewed By: robertoaloi Differential Revision: D88010399 fbshipit-source-id: 4a1d2d76a809c104db059e9433200e1bfcf2ac59 --- crates/elp/src/bin/erlang_service_cli.rs | 7 +- .../resolves_generated_includes.stdout | 6 +- .../diagnostics/parse_all_diagnostics1.stdout | 2 +- crates/erlang_service/src/lib.rs | 15 +- crates/ide/src/diagnostics.rs | 212 ++++++++++++++---- erlang_service/src/erlang_service_lint.erl | 101 ++++++++- .../check_include/src/top_includer.erl | 1 + .../include/include_with_bug.hrl | 5 + .../include/top_includer.hrl | 1 + 9 files changed, 288 insertions(+), 62 deletions(-) create mode 100644 test_projects/buck_tests_2/check_include_separate_1/include/include_with_bug.hrl diff --git a/crates/elp/src/bin/erlang_service_cli.rs b/crates/elp/src/bin/erlang_service_cli.rs index 6611bdffae..5b39a4aa06 100644 --- a/crates/elp/src/bin/erlang_service_cli.rs +++ b/crates/elp/src/bin/erlang_service_cli.rs @@ -150,14 +150,15 @@ pub fn do_parse_one( .chain(result.warnings.iter()) .map(|err| { let relative_path: &Path = err.path.strip_prefix(root_dir).unwrap_or(&err.path); - let (range, line_num) = match err.location { + let (range, line_num) = match &err.location { None => (None, convert::position(&line_index, 0.into()).line + 1), Some(DiagnosticLocation::Normal(range)) => ( Some(range), convert::position(&line_index, range.start()).line + 1, ), Some(DiagnosticLocation::Included { - directive_location, + file_attribute_location: directive_location, + error_path: _, error_location: _, }) => ( Some(directive_location), @@ -169,7 +170,7 @@ pub fn do_parse_one( relative_path: relative_path.to_owned(), line_num, msg: err.msg.to_owned(), - range, + range: range.copied(), } }) .collect(); diff --git a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout index 378f86c01e..b1773fd731 100644 --- a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout +++ b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout @@ -3,8 +3,10 @@ Diagnostics reported in 3 modules: app_a: 1 6:10-6:32::[WeakWarning] [W0037] Unspecific include. top_includer: 2 - 1:2-1:2::[Error] [L0000] Issue in included file - 13:5-13:11::[Error] [E1508] undefined macro 'THIRD/2' + 6:1-6:67::[Error] [L0000] Issue in included file + [check_include_separate_1/include/include_with_bug.hrl] 5:14-5:18: E1507: undefined macro 'FOO' + [check_include_separate_1/include/top_includer.hrl] 3:10-3:30: E1516: can't find include file "does_not_exist.hrl" + 14:5-14:11::[Error] [E1508] undefined macro 'THIRD/2' wa_buck2_module_search: 4 55:19-55:32::[WeakWarning] [W0051] Binary string can be written using sigil syntax. 57:19-57:29::[WeakWarning] [W0051] Binary string can be written using sigil syntax. diff --git a/crates/elp/src/resources/test/diagnostics/parse_all_diagnostics1.stdout b/crates/elp/src/resources/test/diagnostics/parse_all_diagnostics1.stdout index a1db95ccbf..7fe610f781 100644 --- a/crates/elp/src/resources/test/diagnostics/parse_all_diagnostics1.stdout +++ b/crates/elp/src/resources/test/diagnostics/parse_all_diagnostics1.stdout @@ -2,7 +2,7 @@ module specified: diagnostics Diagnostics reported in 1 modules: diagnostics: 6 2:9-2:26::[Hint] [W0037] Unspecific include. - 3:0-3:0::[Error] [L0000] Issue in included file + 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 diff --git a/crates/erlang_service/src/lib.rs b/crates/erlang_service/src/lib.rs index 16020d78f6..072f439db3 100644 --- a/crates/erlang_service/src/lib.rs +++ b/crates/erlang_service/src/lib.rs @@ -59,12 +59,13 @@ lazy_static! { pub static ref ESCRIPT: RwLock = RwLock::new("escript".to_string()); } -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum DiagnosticLocation { Normal(TextRange), Included { - directive_location: TextRange, // Location of include directive in the file compiled - error_location: TextRange, // Location of the error in the included file + file_attribute_location: TextRange, // Location of file_attribute for included file in the file compiled abstract forms + error_path: PathBuf, // Path of the file containing the error. It could be via a deeply nested include + error_location: TextRange, // Location of the error in the included file }, } @@ -735,7 +736,8 @@ fn decode_errors(buf: &[u8]) -> Result> { eetf::Term::decode(buf)? .as_match(pattern::VarList(( - Str, + Str, // path + Str, // error path pattern::Or(( (pattern::U32, pattern::U32), // Normal location pattern::FixList(((pattern::U32, pattern::U32), (pattern::U32, pattern::U32))), // Location in include file @@ -747,7 +749,7 @@ fn decode_errors(buf: &[u8]) -> Result> { .map_err(|err| anyhow!("Failed to decode errors: {:?}", err)) .map(|res| { res.into_iter() - .map(|(path, position, msg, code)| ParseError { + .map(|(path, error_path, position, msg, code)| ParseError { path: path.into(), location: match position { pattern::Union3::A((a, b)) => Some(DiagnosticLocation::Normal( @@ -755,7 +757,8 @@ fn decode_errors(buf: &[u8]) -> Result> { )), pattern::Union3::B(((a, b), (c, d))) => { Some(DiagnosticLocation::Included { - directive_location: safe_textrange(a.into(), b.into()), + file_attribute_location: safe_textrange(a.into(), b.into()), + error_path: error_path.into(), error_location: safe_textrange(c.into(), d.into()), }) } diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index b8aa41bcda..a5a46c442d 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -11,6 +11,7 @@ use std::borrow::Cow; use std::collections::BTreeSet; use std::fmt; +use std::path::Path; use std::sync::Arc; use anyhow::Result; @@ -38,6 +39,7 @@ use elp_ide_db::elp_base_db::FileId; use elp_ide_db::elp_base_db::FileKind; use elp_ide_db::elp_base_db::FileRange; use elp_ide_db::elp_base_db::ProjectId; +use elp_ide_db::elp_base_db::VfsPath; use elp_ide_db::erlang_service::DiagnosticLocation; use elp_ide_db::erlang_service::ParseError; use elp_ide_db::metadata::Kind; @@ -2166,51 +2168,54 @@ pub fn erlang_service_diagnostics( let mut warning_info: BTreeSet<(FileId, TextSize, TextSize, String, String)> = BTreeSet::default(); + // Track related information for L0000 diagnostics from included files + let mut related_info_map: FxHashMap<(FileId, TextSize, TextSize), Vec> = + FxHashMap::default(); + res.errors .iter() - .filter_map(|d| parse_error_to_diagnostic_info(db, file_id, d)) + .filter_map(|d| parse_error_to_diagnostic_info(db, file_id, d, &mut related_info_map)) .for_each(|val| { error_info.insert(val); }); res.warnings .iter() - .filter_map(|d| parse_error_to_diagnostic_info(db, file_id, d)) + .filter_map(|d| parse_error_to_diagnostic_info(db, file_id, d, &mut related_info_map)) .for_each(|val| { warning_info.insert(val); }); let warning_severity = config.erlang_service_warning_severity(); + let to_diagnostic = |file_id: FileId, + start: TextSize, + end: TextSize, + code: String, + msg: String, + severity: Severity| { + let range = TextRange::new(start, end); + let mut diagnostic = tag_erlang_service_diagnostic( + Diagnostic::new(DiagnosticCode::ErlangService(code.clone()), msg, range) + .with_severity(severity), + ); + if code == "L0000" + && let Some(related) = related_info_map.get(&(file_id, start, end)) + { + diagnostic = diagnostic.with_related(Some(related.clone())); + } + (file_id, diagnostic) + }; + let diags: Vec<(FileId, Diagnostic)> = error_info .into_iter() .map(|(file_id, start, end, code, msg)| { - ( - file_id, - tag_erlang_service_diagnostic( - Diagnostic::new( - DiagnosticCode::ErlangService(code), - msg, - TextRange::new(start, end), - ) - .with_severity(Severity::Error), - ), - ) + to_diagnostic(file_id, start, end, code, msg, Severity::Error) }) .chain( warning_info .into_iter() .map(|(file_id, start, end, code, msg)| { - ( - file_id, - tag_erlang_service_diagnostic( - Diagnostic::new( - DiagnosticCode::ErlangService(code), - msg, - TextRange::new(start, end), - ) - .with_severity(warning_severity), - ), - ) + to_diagnostic(file_id, start, end, code, msg, warning_severity) }), ) .collect(); @@ -2599,20 +2604,68 @@ fn parse_error_to_diagnostic_info( db: &RootDatabase, file_id: FileId, parse_error: &ParseError, + related_info_map: &mut FxHashMap<(FileId, TextSize, TextSize), Vec>, ) -> Option<(FileId, TextSize, TextSize, String, String)> { - match parse_error.location { + match &parse_error.location { Some(DiagnosticLocation::Included { - directive_location, + file_attribute_location, + error_path, error_location, - }) => included_file_file_id(db, file_id, directive_location).map(|included_file_id| { - ( - included_file_id, - error_location.start(), - error_location.end(), - parse_error.code.clone(), - parse_error.msg.clone(), - ) - }), + }) => { + // Get the FileId for the file containing the error by searching through source roots + + // Note: we can have our .erl file which includes A.hrl which includes B.hrl, and B.hrl has an error. + // In this case, the error location will be in B.hrl, but the file_attribute_location will be 1..1, for the entry into B.hrl. + // The parse_error.path will correctly refer to B.hrl + // In this case, we need to report the error in the .erl file, at the include directive location. + // But how do we detemine that? We need to find an include chain from the .erl file to the included + // file. The A->B chain can be arbitrarily long. + // So, in the erlang service, we must report the chain of `file` attributes seen. + + // For errors in included files, report L0000 at the include directive location + // and add the actual error as related information + if let Some(directive_range) = + include_directive_range(db, file_id, *file_attribute_location) + { + // Store related info about the actual error in the included file + if let Some(error_file_id) = related_file_id(db, file_id, error_path) { + let related_info = RelatedInformation { + file_id: error_file_id, + range: *error_location, + message: format!("{}: {}", parse_error.code, parse_error.msg), + }; + related_info_map + .entry((file_id, directive_range.start(), directive_range.end())) + .or_default() + .push(related_info); + } else { + log::warn!("Could not find file for path {}", error_path.display()); + } + + // Return diagnostic at the include directive with code L0000 + Some(( + file_id, + directive_range.start(), + directive_range.end(), + "L0000".to_string(), + "Issue in included file".to_string(), + )) + } else { + // Fallback: if we can't find the include directive range, try to report + // at the included file itself + included_file_file_id(db, file_id, *file_attribute_location).map( + |included_file_id| { + ( + included_file_id, + error_location.start(), + error_location.end(), + parse_error.code.clone(), + parse_error.msg.clone(), + ) + }, + ) + } + } Some(DiagnosticLocation::Normal(range)) => { let default_range = ( file_id, @@ -2622,11 +2675,13 @@ fn parse_error_to_diagnostic_info( parse_error.msg.clone(), ); match parse_error.code.as_str() { + // Do not report L0000, it is already reported via a DiagnosticLocation::Included parse error + "L0000" => None, // For certain warnings, OTP returns a diagnostic with a wide range (e.g. a full record definition) // That can be very verbose and distracting, so we try restricting the range to the relevant parts only. "L1227" => { let name = function_undefined_from_message(&parse_error.msg); - match exported_function_name_range(db, file_id, name, range) { + match exported_function_name_range(db, file_id, name, *range) { Some(name_range) => Some(( file_id, name_range.start(), @@ -2639,7 +2694,7 @@ fn parse_error_to_diagnostic_info( } "L1295" => { let name = type_undefined_from_message(&parse_error.msg); - match exported_type_name_range(db, file_id, name, range) { + match exported_type_name_range(db, file_id, name, *range) { Some(name_range) => Some(( file_id, name_range.start(), @@ -2650,7 +2705,7 @@ fn parse_error_to_diagnostic_info( None => Some(default_range), } } - "L1230" | "L1309" => match function_name_range(db, file_id, range) { + "L1230" | "L1309" => match function_name_range(db, file_id, *range) { Some(name_range) => Some(( file_id, name_range.start(), @@ -2660,7 +2715,7 @@ fn parse_error_to_diagnostic_info( )), None => Some(default_range), }, - "L1296" => match type_alias_name_range(db, file_id, range) { + "L1296" => match type_alias_name_range(db, file_id, *range) { Some(name_range) => Some(( file_id, name_range.start(), @@ -2670,7 +2725,7 @@ fn parse_error_to_diagnostic_info( )), None => Some(default_range), }, - "L1308" => match spec_name_range(db, file_id, range) { + "L1308" => match spec_name_range(db, file_id, *range) { Some(name_range) => Some(( file_id, name_range.start(), @@ -2680,7 +2735,7 @@ fn parse_error_to_diagnostic_info( )), None => Some(default_range), }, - "L1260" => match record_name_range(db, file_id, range) { + "L1260" => match record_name_range(db, file_id, *range) { Some(name_range) => Some(( file_id, name_range.start(), @@ -2703,6 +2758,22 @@ fn parse_error_to_diagnostic_info( } } +fn related_file_id(db: &RootDatabase, file_id: FileId, path: &Path) -> Option { + db.file_project_id(file_id).and_then(|project_id| { + let vfs_path = VfsPath::new_real_path(path.to_string_lossy().as_ref().to_string()); + + let project_data = db.project_data(project_id); + // Search through all source roots in the project to find the file + project_data + .source_roots + .iter() + .find_map(|&source_root_id| { + let source_root = db.source_root(source_root_id); + source_root.file_for_path(&vfs_path) + }) + }) +} + fn exported_function_name_range( db: &RootDatabase, file_id: FileId, @@ -2787,13 +2858,13 @@ fn type_alias_name_range( pub fn included_file_file_id( db: &RootDatabase, file_id: FileId, - directive_range: TextRange, + file_attribute_location: TextRange, ) -> Option { let parsed = db.parse(file_id); let form_list = db.file_form_list(file_id); let include = form_list.includes().find_map(|(idx, include)| { let form = include.form_id().get(&parsed.tree()); - if form.syntax().text_range().contains(directive_range.start()) { + if form.syntax().text_range().start() >= file_attribute_location.start() { db.resolve_include(InFile::new(file_id, idx)) } else { None @@ -2802,6 +2873,30 @@ pub fn included_file_file_id( Some(include) } +/// For an error in an included file, find the next form occurring after `file_attribute_location` +/// in the file, and return its full syntax range provided it is an include or include_lib +fn include_directive_range( + db: &RootDatabase, + file_id: FileId, + file_attribute_location: TextRange, +) -> Option { + let parsed = db.parse(file_id); + let form_list = db.file_form_list(file_id); + + form_list.includes().find_map(|(_, include)| { + let form = include.form_id().get(&parsed.tree()); + let form_range = form.syntax().text_range(); + + // Check if this form starts after directive_location + if form_range.start() >= file_attribute_location.start() { + // Return the full syntax range of the include directive + Some(form_range) + } else { + None + } + }) +} + /// Given syntax errors from ELP native and erlang service, combine /// them by discarding any erlang service syntax errors for a form if /// ELP has reported any too. This is done by merging labels, with @@ -3742,6 +3837,37 @@ baz(1)->4. ); } + #[test] + fn erlang_service_nested_include_resolution() { + check_diagnostics( + r#" + //- erlang_service + //- /src/app_a_include.erl + -module(app_a_include). + -export([test/0]). + %% ^^^^^^ error: L1227: function test/0 undefined + + -include("first.hrl"). + %% ^^^^^^^^^^^^^^^^^^^^^^ error: L0000: Issue in included file + %% | Related info: 1:13-30 E1516: can't find include file "second_typo.hrl" + + test() -> + ?FIRST_MACRO, + ?SECOND_MACRO. + %% ^^^^^^^^^^^^^ error: E1507: undefined macro 'SECOND_MACRO' + + //- /src/first.hrl + -include("second_typo.hrl"). + %% ^^^^^^^^^^^^^^^^^ error: E1516: can't find include file "second_typo.hrl" + + -define(FIRST_MACRO, 1). + + //- /src/second.hrl + -define(SECOND_MACRO, "Hello from nested header"). + "#, + ); + } + #[test] fn erlang_service_include_resolution_doc() { check_diagnostics( diff --git a/erlang_service/src/erlang_service_lint.erl b/erlang_service/src/erlang_service_lint.erl index 77223da49a..bc5a2faf3b 100644 --- a/erlang_service/src/erlang_service_lint.erl +++ b/erlang_service/src/erlang_service_lint.erl @@ -178,6 +178,7 @@ format_error(_Forms, _OriginalPath, Path, {Line, Mod, Reason}) when is_integer(L [ { unicode:characters_to_list(Path), + unicode:characters_to_list("ignored"), none, unicode:characters_to_list( io_lib:format("~p: ~ts", [Line, Mod:format_error(Reason)]) @@ -189,6 +190,7 @@ format_error(_Forms, SamePath, SamePath, {Location, Mod, Reason}) -> [ { unicode:characters_to_list(SamePath), + unicode:characters_to_list("ignored"), % Location would be {Line, Col} for a erlc compiler error/warning, % but {ByteStart, ByteEnd} for an eqwalizer diagnostic. % This is deciphered on elp side. @@ -199,18 +201,19 @@ format_error(_Forms, SamePath, SamePath, {Location, Mod, Reason}) -> erlang_service_error_codes:make_code(Mod, Reason) } ]; -format_error(Forms, OriginalPath, Path, {Location, Mod, Reason}) -> +format_error(Forms, IncluderPath, ErrorPath, {Location, Mod, Reason}) -> %% The original path and reported error path are different, the %% error is in an included file. %% This can be from an include, include_lib, behaviour or parse_transform - IncludeLocation = inclusion_range(Forms, Path), + IncludeLocation = inclusion_range(Forms, ErrorPath), %% We return an error at the location the error occurs, as well as %% a list of the errors in the included file. ELP will determine %% the appropriate FileId and emit diagnostics for the include file. [ { - unicode:characters_to_list(OriginalPath), + unicode:characters_to_list(IncluderPath), + unicode:characters_to_list(ErrorPath), % Location would be {Line, Col} for a erlc compiler error/warning, % but {ByteStart, ByteEnd} for an eqwalizer diagnostic. % This is deciphered on elp side. @@ -219,7 +222,8 @@ format_error(Forms, OriginalPath, Path, {Location, Mod, Reason}) -> erlang_service_error_codes:make_code(erlang_service_error_codes, "Issue in included file") }, { - unicode:characters_to_list(Path), + unicode:characters_to_list(IncluderPath), + unicode:characters_to_list(ErrorPath), % Location would be {Line, Col} for a erlc compiler error/warning, % but {ByteStart, ByteEnd} for an eqwalizer diagnostic. % This is deciphered on elp side. @@ -232,13 +236,96 @@ format_error(Forms, OriginalPath, Path, {Location, Mod, Reason}) -> ]. inclusion_range(Forms, Path) -> - case [Location || {attribute, Location, file, {FormPath, _}} <- Forms, FormPath == Path] of - [{Loc, _}] -> - {Loc, Loc}; + %% The `include` or `include_lib` directive is processed by epp, so + %% it does not show up in the abstract forms directly. We infer its + %% location by looking for the `file` attribute with the same path, + %% which is inserted when epp starts processing a new file. + %% The wrinkle is that the range of the `file` attribute does not + %% correspond to the location of the `include` directive, but the last + %% thing processed in the current file. + + %% Track the full include context for a given path. + %% In the forms, process the file attributes. + %% Every time it has a Location of {0,0}, the file has been entered or re-entered + %% Keep a stack for the current location, pushing to it when a {0,0} is found for it, + %% popping when a {0,0} with the same path is found. + FileAttrs = [{Loc, FormPath} || {attribute, Loc, file, {FormPath, _}} <- Forms], + + Context = build_include_context(FileAttrs, Path), + + case Context of + [ _, {IncludeLoc, _IncludePath} | _Rest] -> + %% Return the location from the second entry (the include directive location in the parent file) + %% Extract the position from the location tuple and return as a range + case IncludeLoc of + {Pos, _} -> {Pos, Pos}; + Pos when is_integer(Pos) -> {Pos, Pos} + end; _ -> {1, 1} end. +%% Build the include context stack for a given path +build_include_context(FileAttrs, TargetPath) -> + {_FinalStack, Context} = lists:foldl( + fun({Location, FilePath}, {Stack, ContextAcc}) -> + case Location of + {0, 0} -> + %% Entering or re-entering a file + case find_in_stack(Stack, FilePath) of + not_found -> + %% First time seeing this path - push to stack + NewStack = [{Location, FilePath} | Stack], + NewContextAcc = + case FilePath of + TargetPath -> + %% Found our target path, capture current stack (reversed to get bottom-up order) + lists:reverse(NewStack); + _ -> + ContextAcc + end, + {NewStack, NewContextAcc}; + _Found -> + %% Re-entering a file that was on the stack - pop back to it + NewStack = pop_to_path(Stack, FilePath), + {NewStack, ContextAcc} + end; + _ -> + %% Non-zero location - this is an include directive + %% Push this file onto the stack + NewStack = [{Location, FilePath} | Stack], + NewContextAcc = + case FilePath of + TargetPath -> + %% Found our target path, capture current stack (reversed to get bottom-up order) + lists:reverse(NewStack); + _ -> + ContextAcc + end, + {NewStack, NewContextAcc} + end + end, + {[], []}, + FileAttrs + ), + Context. + +%% Find a path in the stack +find_in_stack([], _Path) -> + not_found; +find_in_stack([{Loc, Path} | _Rest], Path) -> + {found, Loc}; +find_in_stack([_ | Rest], Path) -> + find_in_stack(Rest, Path). + +%% Pop the stack until we find the given path (inclusive) +pop_to_path([], _Path) -> + []; +pop_to_path([{_Loc, Path} | _Rest] = Stack, Path) -> + Stack; +pop_to_path([_ | Rest], Path) -> + pop_to_path(Rest, Path). + extract_forms(Id, FileName, FileId, FileText, Options) -> case filename:extension(FileName) of ".erl" -> diff --git a/test_projects/buck_tests_2/check_include/src/top_includer.erl b/test_projects/buck_tests_2/check_include/src/top_includer.erl index 79f50ee108..6f12fdb31d 100644 --- a/test_projects/buck_tests_2/check_include/src/top_includer.erl +++ b/test_projects/buck_tests_2/check_include/src/top_includer.erl @@ -2,6 +2,7 @@ -compile(warn_missing_spec_all). +-include_lib("stdlib/include/ms_transform.hrl"). -include_lib("check_include_separate_1/include/top_includer.hrl"). -define(A_MACRO, ?FUNCTION_NAME). diff --git a/test_projects/buck_tests_2/check_include_separate_1/include/include_with_bug.hrl b/test_projects/buck_tests_2/check_include_separate_1/include/include_with_bug.hrl new file mode 100644 index 0000000000..82ab343676 --- /dev/null +++ b/test_projects/buck_tests_2/check_include_separate_1/include/include_with_bug.hrl @@ -0,0 +1,5 @@ +%% This file has a deliberate bug in it, to ensure we get a diagnostic +%% in the including file. +%% + +type foo() = ?FOO. diff --git a/test_projects/buck_tests_2/check_include_separate_1/include/top_includer.hrl b/test_projects/buck_tests_2/check_include_separate_1/include/top_includer.hrl index 5c88a7806e..b67b5b8b2b 100644 --- a/test_projects/buck_tests_2/check_include_separate_1/include/top_includer.hrl +++ b/test_projects/buck_tests_2/check_include_separate_1/include/top_includer.hrl @@ -1,5 +1,6 @@ -include_lib("check_include_separate_2/include/separate_include.hrl"). -include("does_not_exist.hrl"). +-include("include_with_bug.hrl"). -define(FIRST, 1). From 3461b8502994efdc62bdd22a1dcf1cbab8dcce3a Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 3 Dec 2025 06:46:58 -0800 Subject: [PATCH 068/142] Improve emacs eglot config Summary: Add a hook to automatically enable it in erlang-mode, and add the missing closing parens. Reviewed By: lisztspace Differential Revision: D88264639 fbshipit-source-id: d3d8166dd233ddc59ff6bb082f3c022851aec5dc --- website/docs/get-started/editors/emacs.md | 33 ++++++++++++++++------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/website/docs/get-started/editors/emacs.md b/website/docs/get-started/editors/emacs.md index a444e87721..28b1c63e24 100644 --- a/website/docs/get-started/editors/emacs.md +++ b/website/docs/get-started/editors/emacs.md @@ -4,17 +4,21 @@ sidebar_position: 2 # Emacs -The ELP project can be used as a [language server](https://microsoft.github.io/language-server-protocol/overviews/lsp/overview/) in the Emacs text editor via the [eglot](https://github.com/joaotavora/eglot) or [lsp-mode](https://emacs-lsp.github.io/lsp-mode/) LSP clients. +The ELP project can be used as a +[language server](https://microsoft.github.io/language-server-protocol/overviews/lsp/overview/) +in the Emacs text editor via the [eglot](https://github.com/joaotavora/eglot) or +[lsp-mode](https://emacs-lsp.github.io/lsp-mode/) LSP clients. ## Eglot -Eglot is part of Emacs core since Emacs 29. -For earlier versions it can be installed with the `eglot` package. +Eglot is part of Emacs core since Emacs 29. For earlier versions it can be +installed with the `eglot` package. ### Configuration ```elisp (use-package eglot + :hook ((erlang-mode . eglot-ensure)) :config ;; Remove default LSP server @@ -23,14 +27,19 @@ For earlier versions it can be installed with the `eglot` package. ;; Enable ELP (add-to-list 'eglot-server-programs - '(erlang-mode . ("elp" "server")) + '(erlang-mode . ("elp" "server")))) ``` -Refer to the [manual](https://elpa.gnu.org/devel/doc/eglot.html#Customization-Variables) for additional configuration options. +Refer to the +[manual](https://elpa.gnu.org/devel/doc/eglot.html#Customization-Variables) for +additional configuration options. ## lsp-mode -Install the `lsp-mode` package, which is a generic Emacs client for LSP servers. You can follow [these instructions](https://emacs-lsp.github.io/lsp-mode/page/installation/) to install it. +Install the `lsp-mode` package, which is a generic Emacs client for LSP servers. +You can follow +[these instructions](https://emacs-lsp.github.io/lsp-mode/page/installation/) to +install it. ### Configuration @@ -52,15 +61,21 @@ Add the following to your emacs `.emacs` file or equivalent. ) ``` -For a list of available configuration option, please refer to [this page](https://emacs-lsp.github.io/lsp-mode/page/lsp-erlang-elp/) and to the [`lsp-mode` settings documentation](https://emacs-lsp.github.io/lsp-mode/page/settings/mode/). +For a list of available configuration option, please refer to +[this page](https://emacs-lsp.github.io/lsp-mode/page/lsp-erlang-elp/) and to +the +[`lsp-mode` settings documentation](https://emacs-lsp.github.io/lsp-mode/page/settings/mode/). -There is also a [`.dotemacs`](https://github.com/WhatsApp/erlang-language-platform/blob/main/editors/emacs/dotemacs.el) file in the ELP repository that you can use as a reference. +There is also a +[`.dotemacs`](https://github.com/WhatsApp/erlang-language-platform/blob/main/editors/emacs/dotemacs.el) +file in the ELP repository that you can use as a reference. ## Troubleshooting #### The following servers support current file but do not have automatic installation -Ensure that the `elp` executable is available in your `PATH` via Emacs. A workaround is: +Ensure that the `elp` executable is available in your `PATH` via Emacs. A +workaround is: ```elisp ;; Ensure your Emacs environment looks like your user's shell one From 9ef2fec36e1bf760482acdbc522e8caef5a86cae Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Thu, 4 Dec 2025 07:38:39 -0800 Subject: [PATCH 069/142] Add test for hierarchical config support Summary: # Context We want ELP to support hierarchical linter configurations. This means that, by placing a `.elp_lint.toml` file inside an OTP application dir it should be possible to specialize a more generic project-level linter configuration. # This diff This diff introduces a new test project, named "hierarchical_config" to showcase the most basic form of the goal: the project is composed by two OTP applications (`app_a` and `app_b`). After applying the generic lint configuration, both applications should have two lint issues: an unused macro and an unused function. By placing an additional `.elp_lint.toml` file inside `app_a` to disable the unused macro linter, the unused macro lint issue should only be reported for `app_b`. Since the feature is not implemented yet and the unused macro linter will be reported for both applications, the test is marked as "should panic". This will change at the end of the stack. Reviewed By: alanz Differential Revision: D85569527 fbshipit-source-id: 0772e3a84a25f23c3d6b7ae6c0c955f1206e2c6a --- crates/elp/src/bin/main.rs | 13 +++++++++++++ .../resources/test/hierarchical_config/basic.stdout | 7 +++++++ test_projects/hierarchical_config/.elp.toml | 5 +++++ test_projects/hierarchical_config/.elp_lint.toml | 5 +++++ .../hierarchical_config/app_a/.elp_lint.toml | 4 ++++ .../hierarchical_config/app_a/src/app_a.app.src | 3 +++ .../hierarchical_config/app_a/src/app_a.erl | 7 +++++++ .../hierarchical_config/app_b/src/app_b.app.src | 3 +++ .../hierarchical_config/app_b/src/app_b.erl | 7 +++++++ test_projects/hierarchical_config/rebar.config | 7 +++++++ 10 files changed, 61 insertions(+) create mode 100644 crates/elp/src/resources/test/hierarchical_config/basic.stdout create mode 100644 test_projects/hierarchical_config/.elp.toml create mode 100644 test_projects/hierarchical_config/.elp_lint.toml create mode 100644 test_projects/hierarchical_config/app_a/.elp_lint.toml create mode 100644 test_projects/hierarchical_config/app_a/src/app_a.app.src create mode 100644 test_projects/hierarchical_config/app_a/src/app_a.erl create mode 100644 test_projects/hierarchical_config/app_b/src/app_b.app.src create mode 100644 test_projects/hierarchical_config/app_b/src/app_b.erl create mode 100644 test_projects/hierarchical_config/rebar.config diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index b227259a52..cc4c6b8332 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -2178,6 +2178,19 @@ mod tests { ) } + #[test_case(false ; "rebar")] + #[test_case(true ; "buck")] + #[should_panic] // Support for hierarchical config is not implemented yet + fn lint_hierarchical_config_basic(buck: bool) { + simple_snapshot( + args_vec!["lint", "--read-config"], + "hierarchical_config", + expect_file!("../resources/test/hierarchical_config/basic.stdout"), + buck, + None, + ); + } + #[test_case(false ; "rebar")] #[test_case(true ; "buck")] fn eqwalizer_tests_check(buck: bool) { diff --git a/crates/elp/src/resources/test/hierarchical_config/basic.stdout b/crates/elp/src/resources/test/hierarchical_config/basic.stdout new file mode 100644 index 0000000000..851813b00c --- /dev/null +++ b/crates/elp/src/resources/test/hierarchical_config/basic.stdout @@ -0,0 +1,7 @@ +Reporting all diagnostics codes +Diagnostics reported in 2 modules: + app_a: 1 + 6:1-6:5::[Warning] [L1230] function main/0 is unused + app_b: 2 + 4:9-4:16::[Warning] [W0002] Unused macro (MACRO_B) + 6:1-6:5::[Warning] [L1230] function main/0 is unused diff --git a/test_projects/hierarchical_config/.elp.toml b/test_projects/hierarchical_config/.elp.toml new file mode 100644 index 0000000000..1033c181ee --- /dev/null +++ b/test_projects/hierarchical_config/.elp.toml @@ -0,0 +1,5 @@ +[buck] +enabled = true +build_deps = false +included_targets = [ "fbcode//whatsapp/elp/test_projects/hierarchical_config/..." ] +source_root = "whatsapp/elp/test_projects/hierarchical_config" diff --git a/test_projects/hierarchical_config/.elp_lint.toml b/test_projects/hierarchical_config/.elp_lint.toml new file mode 100644 index 0000000000..6cc536f399 --- /dev/null +++ b/test_projects/hierarchical_config/.elp_lint.toml @@ -0,0 +1,5 @@ +enabled_lints = [] +disabled_lints = [ + "W0012", + "W0046" +] diff --git a/test_projects/hierarchical_config/app_a/.elp_lint.toml b/test_projects/hierarchical_config/app_a/.elp_lint.toml new file mode 100644 index 0000000000..7445514e73 --- /dev/null +++ b/test_projects/hierarchical_config/app_a/.elp_lint.toml @@ -0,0 +1,4 @@ +enabled_lints = [] +disabled_lints = [ + "W0002" +] diff --git a/test_projects/hierarchical_config/app_a/src/app_a.app.src b/test_projects/hierarchical_config/app_a/src/app_a.app.src new file mode 100644 index 0000000000..1381435a46 --- /dev/null +++ b/test_projects/hierarchical_config/app_a/src/app_a.app.src @@ -0,0 +1,3 @@ +{application, app_a, + [{description, "example app A"}, {vsn, "inplace"}, {applications, [kernel, stdlib]}] +}. diff --git a/test_projects/hierarchical_config/app_a/src/app_a.erl b/test_projects/hierarchical_config/app_a/src/app_a.erl new file mode 100644 index 0000000000..5b856d4f02 --- /dev/null +++ b/test_projects/hierarchical_config/app_a/src/app_a.erl @@ -0,0 +1,7 @@ +-module(app_a). + +-define(MACRO_A, 1). +-define(MACRO_B, 1). + +main() -> + ?MACRO_A. diff --git a/test_projects/hierarchical_config/app_b/src/app_b.app.src b/test_projects/hierarchical_config/app_b/src/app_b.app.src new file mode 100644 index 0000000000..4112b4129f --- /dev/null +++ b/test_projects/hierarchical_config/app_b/src/app_b.app.src @@ -0,0 +1,3 @@ +{application, app_b, + [{description, "example app B"}, {vsn, "inplace"}, {applications, [kernel, stdlib]}] +}. diff --git a/test_projects/hierarchical_config/app_b/src/app_b.erl b/test_projects/hierarchical_config/app_b/src/app_b.erl new file mode 100644 index 0000000000..ad2453d0de --- /dev/null +++ b/test_projects/hierarchical_config/app_b/src/app_b.erl @@ -0,0 +1,7 @@ +-module(app_b). + +-define(MACRO_A, 1). +-define(MACRO_B, 1). + +main() -> + ?MACRO_A. diff --git a/test_projects/hierarchical_config/rebar.config b/test_projects/hierarchical_config/rebar.config new file mode 100644 index 0000000000..2dcacbc5b3 --- /dev/null +++ b/test_projects/hierarchical_config/rebar.config @@ -0,0 +1,7 @@ +{project_app_dirs, [ + "app_a", + "app_b" +]}. + +{erl_opts, [debug_info]}. +{deps, []}. From d42e431df4992ccc707a27d6f3e6789efaf47226 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Thu, 4 Dec 2025 07:38:39 -0800 Subject: [PATCH 070/142] Add merge logic for linters configuration Summary: # Context We want ELP to support hierarchical linter configurations. This means that, by placing a `.elp_lint.toml` file inside an OTP application dir it should be possible to specialize a more generic project-level linter configuration. # This diff Introduce the concept of "merging" two linter configurations. This mechanism, currently unused, will then be used when resolving the linter configuration for a given application. Reviewed By: alanz Differential Revision: D85573621 fbshipit-source-id: f52a9ed100be794be23c97174b12ce36c3ca7aba --- crates/ide/src/diagnostics.rs | 234 ++++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index a5a46c442d..af15ee80e8 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -1167,6 +1167,29 @@ impl DiagnosticsConfig { } impl LintConfig { + /// Merges this LintConfig with another, with the other config taking precedence. + pub fn merge(mut self, other: LintConfig) -> LintConfig { + self.enabled_lints.extend(other.enabled_lints); + self.disabled_lints.extend(other.disabled_lints); + + if other.erlang_service.warnings_as_errors { + self.erlang_service.warnings_as_errors = true; + } + + self.ad_hoc_lints.lints.extend(other.ad_hoc_lints.lints); + + for (key, other_linter_config) in other.linters { + self.linters + .entry(key) + .and_modify(|existing| { + *existing = existing.clone().merge(other_linter_config.clone()) + }) + .or_insert(other_linter_config); + } + + self + } + /// Get the is_enabled override for a linter based on its diagnostic code pub fn get_is_enabled_override(&self, diagnostic_code: &DiagnosticCode) -> Option { self.linters.get(diagnostic_code)?.is_enabled @@ -1243,9 +1266,43 @@ pub struct FunctionCallLinterConfig { exclude: Option>, } +impl FunctionCallLinterConfig { + /// Merges this FunctionCallLinterConfig with another, extending the include and exclude lists. + pub fn merge(self, other: FunctionCallLinterConfig) -> FunctionCallLinterConfig { + let mut merged_include = self.include.unwrap_or_default(); + if let Some(other_include) = other.include { + merged_include.extend(other_include); + } + let include = if merged_include.is_empty() { + None + } else { + Some(merged_include) + }; + + let mut merged_exclude = self.exclude.unwrap_or_default(); + if let Some(other_exclude) = other.exclude { + merged_exclude.extend(other_exclude); + } + let exclude = if merged_exclude.is_empty() { + None + } else { + Some(merged_exclude) + }; + + FunctionCallLinterConfig { include, exclude } + } +} + #[derive(Deserialize, Serialize, Default, Debug, Clone)] pub struct SsrPatternsLinterConfig {} +impl SsrPatternsLinterConfig { + /// Merges this SsrPatternsLinterConfig with another (trivial since it's empty). + pub fn merge(self, _other: SsrPatternsLinterConfig) -> SsrPatternsLinterConfig { + self + } +} + #[derive(Deserialize, Serialize, Debug, Clone)] #[serde(untagged)] pub enum LinterTraitConfig { @@ -1253,6 +1310,25 @@ pub enum LinterTraitConfig { SsrPatternsLinterConfig(SsrPatternsLinterConfig), } +impl LinterTraitConfig { + /// Merges this LinterTraitConfig with another. Only merges if both are the same variant. + /// If variants differ, returns the other config. + pub fn merge(self, other: LinterTraitConfig) -> LinterTraitConfig { + match (self, other) { + ( + LinterTraitConfig::FunctionCallLinterConfig(self_config), + LinterTraitConfig::FunctionCallLinterConfig(other_config), + ) => LinterTraitConfig::FunctionCallLinterConfig(self_config.merge(other_config)), + ( + LinterTraitConfig::SsrPatternsLinterConfig(self_config), + LinterTraitConfig::SsrPatternsLinterConfig(other_config), + ) => LinterTraitConfig::SsrPatternsLinterConfig(self_config.merge(other_config)), + // If variants differ, other takes precedence + (_, other) => other, + } + } +} + /// Configuration for a specific linter that allows overriding default settings #[derive(Deserialize, Serialize, Debug, Clone, Default)] pub struct LinterConfig { @@ -1266,6 +1342,27 @@ pub struct LinterConfig { pub config: Option, } +impl LinterConfig { + /// Merges this LinterConfig with another, with the other config taking precedence + /// for any fields that are Some. + pub fn merge(self, other: LinterConfig) -> LinterConfig { + let merged_config = match (self.config, other.config) { + (Some(self_cfg), Some(other_cfg)) => Some(self_cfg.merge(other_cfg)), + (None, some_cfg) => some_cfg, + (some_cfg, None) => some_cfg, + }; + + LinterConfig { + is_enabled: other.is_enabled.or(self.is_enabled), + severity: other.severity.or(self.severity), + include_tests: other.include_tests.or(self.include_tests), + include_generated: other.include_generated.or(self.include_generated), + experimental: other.experimental.or(self.experimental), + config: merged_config, + } + } +} + impl<'de> Deserialize<'de> for Severity { fn deserialize(deserializer: D) -> Result where @@ -4184,4 +4281,141 @@ baz(1)->4. "#, ); } + + #[test] + fn test_lint_config_merge() { + let code1 = DiagnosticCode::from("W0001"); + let code2 = DiagnosticCode::from("W0002"); + let code3 = DiagnosticCode::from("W0003"); + + let mut config1 = LintConfig { + enabled_lints: vec![code1.clone()], + disabled_lints: vec![code2.clone()], + ..Default::default() + }; + config1.erlang_service.warnings_as_errors = false; + + config1 + .ad_hoc_lints + .lints + .push(Lint::ReplaceCall(ReplaceCall { + matcher: FunctionMatch::mf("mod1", "func1"), + action: ReplaceCallAction::Replace(Replacement::UseOk), + })); + + config1.linters.insert( + code1.clone(), + LinterConfig { + is_enabled: Some(true), + severity: Some(Severity::Error), + include_tests: None, + include_generated: None, + experimental: None, + config: Some(LinterTraitConfig::FunctionCallLinterConfig( + FunctionCallLinterConfig { + include: Some(vec![FunctionMatch::mf("mod_a", "func_a")]), + exclude: None, + }, + )), + }, + ); + + let mut config2 = LintConfig { + enabled_lints: vec![code3.clone()], + disabled_lints: vec![code1.clone()], + ..Default::default() + }; + config2.erlang_service.warnings_as_errors = true; + + config2 + .ad_hoc_lints + .lints + .push(Lint::ReplaceCall(ReplaceCall { + matcher: FunctionMatch::mf("mod2", "func2"), + action: ReplaceCallAction::Replace(Replacement::UseOk), + })); + + config2.linters.insert( + code1.clone(), + LinterConfig { + is_enabled: Some(false), + severity: Some(Severity::Warning), + include_tests: Some(true), + include_generated: None, + experimental: None, + config: Some(LinterTraitConfig::FunctionCallLinterConfig( + FunctionCallLinterConfig { + include: Some(vec![FunctionMatch::mf("mod_b", "func_b")]), + exclude: Some(vec![FunctionMatch::mf("mod_c", "func_c")]), + }, + )), + }, + ); + + config2.linters.insert( + code2.clone(), + LinterConfig { + is_enabled: Some(true), + severity: None, + include_tests: None, + include_generated: Some(true), + experimental: None, + config: None, + }, + ); + + let merged = config1.merge(config2); + + assert_eq!(merged.enabled_lints.len(), 2); + assert!(merged.enabled_lints.contains(&code1)); + assert!(merged.enabled_lints.contains(&code3)); + + assert_eq!(merged.disabled_lints.len(), 2); + assert!(merged.disabled_lints.contains(&code2)); + assert!(merged.disabled_lints.contains(&code1)); + + assert!(merged.erlang_service.warnings_as_errors); + + assert_eq!(merged.ad_hoc_lints.lints.len(), 2); + + assert_eq!(merged.linters.len(), 2); + let linter_config1 = merged.linters.get(&code1).unwrap(); + assert_eq!(linter_config1.is_enabled, Some(false)); + assert_eq!(linter_config1.severity, Some(Severity::Warning)); + assert_eq!(linter_config1.include_tests, Some(true)); + + if let Some(LinterTraitConfig::FunctionCallLinterConfig(function_config)) = + &linter_config1.config + { + assert_eq!(function_config.include.as_ref().unwrap().len(), 2); + assert!( + function_config + .include + .as_ref() + .unwrap() + .contains(&FunctionMatch::mf("mod_a", "func_a")) + ); + assert!( + function_config + .include + .as_ref() + .unwrap() + .contains(&FunctionMatch::mf("mod_b", "func_b")) + ); + assert_eq!(function_config.exclude.as_ref().unwrap().len(), 1); + assert!( + function_config + .exclude + .as_ref() + .unwrap() + .contains(&FunctionMatch::mf("mod_c", "func_c")) + ); + } else { + panic!("Expected FunctionCallLinterConfig"); + } + + let linter_config2 = merged.linters.get(&code2).unwrap(); + assert_eq!(linter_config2.is_enabled, Some(true)); + assert_eq!(linter_config2.include_generated, Some(true)); + } } From 796f015f20f9d6a3adff7febbd8870140292c163 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Thu, 4 Dec 2025 09:45:48 -0800 Subject: [PATCH 071/142] Make it possible to run buck tests in isolation Summary: The buck2 tests seem to rely on a global state, which is only set for some of the test cases. As a consequence, running all tests together works, but running some of them in isolation fails. Consistently set the global state in all tests. As a follow-up, we could refactor this in a helper function. Reviewed By: alanz Differential Revision: D88274947 fbshipit-source-id: 5f22894e74bd41458139a93cd063647d95b86296 --- crates/project_model/src/buck.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/project_model/src/buck.rs b/crates/project_model/src/buck.rs index 09e2a7b131..013f3cbc87 100644 --- a/crates/project_model/src/buck.rs +++ b/crates/project_model/src/buck.rs @@ -1432,6 +1432,10 @@ mod tests { "#; let dir = FixtureWithProjectMeta::gen_project(spec); let root = AbsPath::assert(Utf8Path::from_path(dir.path()).unwrap()); + let _guard = set_test_cell_info(vec![( + "cell".to_string(), + dir.path().to_string_lossy().into_owned(), + )]); let target_name = "cell//app_a:app_a".to_string(); let target = BuckTarget { name: "app_a".to_string(), @@ -1460,6 +1464,10 @@ mod tests { "#; let dir = FixtureWithProjectMeta::gen_project(spec); let root = AbsPath::assert(Utf8Path::from_path(dir.path()).unwrap()); + let _guard = set_test_cell_info(vec![( + "cell".to_string(), + dir.path().to_string_lossy().into_owned(), + )]); let target_name = "cell//app_a:app_a".to_string(); let target = BuckTarget { name: "app_a".to_string(), @@ -1488,6 +1496,10 @@ mod tests { "#; let dir = FixtureWithProjectMeta::gen_project(spec); let root = AbsPath::assert(Utf8Path::from_path(dir.path()).unwrap()); + let _guard = set_test_cell_info(vec![( + "cell".to_string(), + dir.path().to_string_lossy().into_owned(), + )]); let target_name = "cell//app_a:app_a".to_string(); let target = BuckTarget { name: "app_a".to_string(), @@ -1586,6 +1598,10 @@ mod tests { "#; let dir = FixtureWithProjectMeta::gen_project(spec); let root = AbsPath::assert(Utf8Path::from_path(dir.path()).unwrap()); + let _guard = set_test_cell_info(vec![( + "cell".to_string(), + dir.path().to_string_lossy().into_owned(), + )]); let target_name = "cell//app_a:app_a".to_string(); let target = BuckTarget { name: "app_a".to_string(), From c9cba57e40d98230b766531bb2444879e17dc5db Mon Sep 17 00:00:00 2001 From: Eddie Shen Date: Fri, 5 Dec 2025 00:22:38 -0800 Subject: [PATCH 072/142] Replace Duration::from_secs(60) to Duration::from_mins(1) Summary: X-link: https://github.com/meta-pytorch/torchft/pull/297 X-link: https://github.com/meta-pytorch/monarch/pull/2066 Rust 1.91 introduced `Duration::from_mins`. This diff replaces some instances of `Duration::from_secs` with the corresponding `Duration::from_mins`. Reviewed By: dtolnay Differential Revision: D88434166 fbshipit-source-id: e5e86763e0f4cfa6da502d32f1f98ffc198399ed --- crates/elp/tests/slow-tests/support.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/elp/tests/slow-tests/support.rs b/crates/elp/tests/slow-tests/support.rs index bf482a7816..2c794f6246 100644 --- a/crates/elp/tests/slow-tests/support.rs +++ b/crates/elp/tests/slow-tests/support.rs @@ -408,7 +408,7 @@ impl Drop for TestServer { struct Timeout; fn recv_timeout(receiver: &Receiver) -> Result, Timeout> { - let timeout = Duration::from_secs(60); + let timeout = Duration::from_mins(1); select! { recv(receiver) -> msg => Ok(msg.ok()), recv(after(timeout)) -> _ => Err(Timeout), From fd8fb4abeb4231942ab340c4c3fa3c4e48ada36e Mon Sep 17 00:00:00 2001 From: Eddie Shen Date: Fri, 5 Dec 2025 00:22:38 -0800 Subject: [PATCH 073/142] Replace Duration::from_secs(240) to Duration::from_mins(4) Summary: Rust 1.91 introduced `Duration::from_mins`. This diff replaces some instances of `Duration::from_secs` with the corresponding `Duration::from_mins`. Reviewed By: dtolnay Differential Revision: D88441014 fbshipit-source-id: 88d368316aeb6e4deb328e2052c105d70da9be06 --- crates/eqwalizer/src/ipc.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/eqwalizer/src/ipc.rs b/crates/eqwalizer/src/ipc.rs index 30b1dae582..4d10d663ec 100644 --- a/crates/eqwalizer/src/ipc.rs +++ b/crates/eqwalizer/src/ipc.rs @@ -159,8 +159,8 @@ pub struct IpcHandle { _child_for_drop: JodChild, } -const WRITE_TIMEOUT: Duration = Duration::from_secs(240); -const READ_TIMEOUT: Duration = Duration::from_secs(240); +const WRITE_TIMEOUT: Duration = Duration::from_mins(4); +const READ_TIMEOUT: Duration = Duration::from_mins(4); impl IpcHandle { fn spawn_cmd(cmd: &mut Command) -> Result { From 016e0d7c1e4b3b194db11975c0c911d475e952cc Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Fri, 5 Dec 2025 03:48:33 -0800 Subject: [PATCH 074/142] Fix false positive for undefined_function linter Summary: It can happen that, when looking for undefined functions, we could not resolve the module name at compile-time. With the old logic, we would incorrectly flag the case as an "invalid function". With the improved version, we do not flag the case if we are not sure, avoiding a number of false positives. A test case highlighting the issue is added as a unit test. Reviewed By: TD5 Differential Revision: D88474342 fbshipit-source-id: f0f5ae1272f8264c5e11c6e8a7855e75a2832841 --- .../ide/src/diagnostics/undefined_function.rs | 73 ++++++++++++++++--- 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/crates/ide/src/diagnostics/undefined_function.rs b/crates/ide/src/diagnostics/undefined_function.rs index 6472a08f07..189edf06f5 100644 --- a/crates/ide/src/diagnostics/undefined_function.rs +++ b/crates/ide/src/diagnostics/undefined_function.rs @@ -80,19 +80,45 @@ impl FunctionCallLinter for UndefinedFunctionLinter { return None; } - if sema.is_atom_named(name, &known::module_info) && (arity == 0 || arity == 1) - || sema - .resolve_module_expr(def_fb.file_id(), module) - .is_some_and(|module| is_automatically_added(sema, module, name, arity)) - { + if sema.is_atom_named(name, &known::module_info) && (arity == 0 || arity == 1) { return None; } - match context - .target - .resolve_call(arity, sema, def_fb.file_id(), &def_fb.body()) - { - Some(_) => None, - None => Some(context.target.label(arity, sema, &def_fb.body())), + + // Try to resolve the module expression to a static module + let resolved_module = sema.resolve_module_expr(def_fb.file_id(), module); + + match resolved_module { + Some(resolved_module) => { + // Module exists, check if the function exists + match context.target.resolve_call( + arity, + sema, + def_fb.file_id(), + &def_fb.body(), + ) { + Some(_) => None, + None => { + // Function doesn't exist - check if it would be automatically added + if is_automatically_added(sema, resolved_module, name, arity) { + return None; + } + Some(context.target.label(arity, sema, &def_fb.body())) + } + } + } + None => { + // Module cannot be resolved. If the module expression is a static atom, + // it means the module doesn't exist and we should report it. + // If it's a dynamic expression (e.g., a variable or function call), + // we can't determine at compile time whether it's defined. + if module.as_atom().is_some() { + // Static module name that doesn't exist - report as undefined + Some(context.target.label(arity, sema, &def_fb.body())) + } else { + // Dynamic module expression - can't determine at compile time + None + } + } } } // Diagnostic L1227 already covers the case for local calls, so avoid double-reporting @@ -413,4 +439,29 @@ exists() -> ok. "#, ) } + + #[test] + fn test_macro_remote_call() { + check_diagnostics( + r#" + //- /src/main.erl + -module(main). + -export([do/0]). + -define(COUNT_BACKEND, (count_backend:count_module())). + -define(COUNT(Name), ?COUNT_BACKEND:count(Name)). + + do() -> + ?COUNT('error.count'). + + //- /src/stats.erl + -module(stats). + -export([count/1]). + count(_) -> ok. + //- /src/count_backend.erl + -module(count_backend). + -export([count_module/0]). + count_module() -> ?MODULE. + "#, + ) + } } From 4726f8144da03b4e30320bafda8ef80544db9baa Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Mon, 8 Dec 2025 04:50:14 -0800 Subject: [PATCH 075/142] Convert missing_module linter to use a trait Summary: The diagnostic was computed in the `diagnostics.rs` module. By using a trait we ensure the code is more readable and the intention is clear. We also modularize the code in `diagnostics.rs`. Reviewed By: alanz Differential Revision: D88349260 fbshipit-source-id: f761a9c9b0da188643b6e5eae35b30a74cb5d262 --- crates/ide/src/diagnostics.rs | 88 +----------- .../expression_can_be_simplified.rs | 11 +- crates/ide/src/diagnostics/missing_module.rs | 132 ++++++++++++++++++ crates/ide/src/tests.rs | 2 +- 4 files changed, 149 insertions(+), 84 deletions(-) create mode 100644 crates/ide/src/diagnostics/missing_module.rs diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index af15ee80e8..02ad08fc0d 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -119,6 +119,7 @@ mod map_insertion_to_syntax; mod meck; // @fb-only mod missing_compile_warn_missing_spec; +mod missing_module; mod missing_separator; mod misspelled_attribute; mod module_mismatch; @@ -1514,11 +1515,8 @@ pub fn native_diagnostics( let labeled_syntax_errors = if report_diagnostics { let sema = Semantic::new(db); - if file_kind.is_module() { - no_module_definition_diagnostic(&mut res, &parse); - if config.include_generated || !db.is_generated(file_id) { - unused_include::unused_includes(&sema, db, &mut res, file_id); - } + if file_kind.is_module() && (config.include_generated || !db.is_generated(file_id)) { + unused_include::unused_includes(&sema, db, &mut res, file_id); } res.append(&mut form_missing_separator_diagnostics(&parse)); @@ -1689,6 +1687,7 @@ const GENERIC_LINTERS: &[&dyn GenericDiagnostics] = &[ &no_nowarn_suppressions::LINTER, ¯o_precedence_suprise::LINTER, &edoc::LINTER, + &missing_module::LINTER, ]; /// Unified registry for all types of linters @@ -2045,34 +2044,6 @@ pub fn get_hir_diagnostics(db: &RootDatabase, file_id: FileId) -> Vec, - parse: &Parse, -) { - let mut report = |range| { - let diagnostic = - Diagnostic::new(DiagnosticCode::MissingModule, "no module definition", range); - diagnostics.push(diagnostic); - }; - for form in parse.tree().forms() { - match form { - ast::Form::PreprocessorDirective(_) => { - continue; // skip any directives - } - ast::Form::FileAttribute(_) => { - continue; // skip - } - ast::Form::ModuleAttribute(_) => { - break; - } - other_form => { - report(other_form.syntax().text_range()); - break; - } - } - } -} - fn form_missing_separator_diagnostics(parse: &Parse) -> Vec { parse .tree() @@ -3310,53 +3281,6 @@ main(X) -> // ) // } - #[test] - fn fun_decl_module_decl_ok() { - check_diagnostics( - r#" --file("main.erl",1). --define(baz,4). --module(main). -foo(2)->?baz. -"#, - ); - } - - #[test] - fn fun_decl_module_decl_missing() { - check_diagnostics( - r#" - -file("foo.erl",1). - -define(baz,4). - foo(2)->?baz. -%%^^^^^^^^^^^^^ error: L1201: no module definition -"#, - ); - } - - #[test] - fn fun_decl_module_decl_missing_2() { - check_diagnostics( - r#" - baz(1)->4. -%%^^^^^^^^^^ error: L1201: no module definition - foo(2)->3. -"#, - ); - } - - #[test] - fn fun_decl_module_decl_after_preprocessor() { - check_diagnostics( - r#" --ifndef(snmpm_net_if_mt). --module(main). --endif. -baz(1)->4. -"#, - ); - } - #[test] fn filter_diagnostics() { let diag1 = DiagnosticCode::ErlangService("P1700".to_string()); @@ -3472,7 +3396,7 @@ baz(1)->4. check_diagnostics( r#" baz(1)->4. -%%^^^^^^^^^^ error: L1201: no module definition +%%^^^^^^^^^^ 💡 error: L1201: no module definition foo(2)->3. "#, ); @@ -3496,7 +3420,7 @@ baz(1)->4. %% elp:ignore L1201 baz(1)->4. -%%^^^^^^^^^^ error: L1201: no module definition +%%^^^^^^^^^^ 💡 error: L1201: no module definition foo(2)->3. "#, ); diff --git a/crates/ide/src/diagnostics/expression_can_be_simplified.rs b/crates/ide/src/diagnostics/expression_can_be_simplified.rs index 4f3ce6ac57..4c9144e5c0 100644 --- a/crates/ide/src/diagnostics/expression_can_be_simplified.rs +++ b/crates/ide/src/diagnostics/expression_can_be_simplified.rs @@ -261,10 +261,19 @@ fn as_expr_id(any_expre_id: hir::AnyExprId) -> Option { #[cfg(test)] mod tests { + use elp_ide_db::DiagnosticCode; + use expect_test::Expect; use expect_test::expect; + use crate::diagnostics::DiagnosticsConfig; use crate::tests::check_diagnostics; - use crate::tests::check_fix; + use crate::tests::check_fix_with_config; + + #[track_caller] + fn check_fix(fixture_before: &str, fixture_after: Expect) { + let config = DiagnosticsConfig::default().disable(DiagnosticCode::MissingModule); + check_fix_with_config(config, fixture_before, fixture_after); + } #[test] fn test_generates_diagnostics() { diff --git a/crates/ide/src/diagnostics/missing_module.rs b/crates/ide/src/diagnostics/missing_module.rs new file mode 100644 index 0000000000..3fcbbf0fb7 --- /dev/null +++ b/crates/ide/src/diagnostics/missing_module.rs @@ -0,0 +1,132 @@ +/* + * 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: missing-module +// +// Return a diagnostic if a module does not have a module definition + +use elp_ide_db::elp_base_db::FileId; +use elp_syntax::AstNode; +use elp_syntax::ast; +use hir::Semantic; + +use crate::diagnostics::DiagnosticCode; +use crate::diagnostics::GenericLinter; +use crate::diagnostics::GenericLinterMatchContext; +use crate::diagnostics::Linter; +use crate::diagnostics::Severity; + +pub(crate) struct MissingModuleLinter; + +impl Linter for MissingModuleLinter { + fn id(&self) -> DiagnosticCode { + DiagnosticCode::MissingModule + } + + fn description(&self) -> &'static str { + "no module definition" + } + + fn severity(&self, _sema: &Semantic, _file_id: FileId) -> super::Severity { + Severity::Error + } + + fn should_process_file_id(&self, sema: &Semantic, file_id: FileId) -> bool { + let file_kind = sema.db.file_kind(file_id); + file_kind.is_module() + } +} + +impl GenericLinter for MissingModuleLinter { + type Context = (); + + fn matches( + &self, + sema: &Semantic, + file_id: FileId, + ) -> Option>> { + let parse = sema.db.parse(file_id); + let mut res = Vec::new(); + + for form in parse.tree().forms() { + match form { + ast::Form::PreprocessorDirective(_) => { + continue; // skip any directives + } + ast::Form::FileAttribute(_) => { + continue; // skip + } + ast::Form::ModuleAttribute(_) => { + break; + } + other_form => { + let range = other_form.syntax().text_range(); + res.push(GenericLinterMatchContext { range, context: () }); + break; + } + } + } + Some(res) + } +} + +pub static LINTER: MissingModuleLinter = MissingModuleLinter; + +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn fun_decl_module_decl_ok() { + check_diagnostics( + r#" +-file("main.erl",1). +-define(baz,4). +-module(main). +foo(2)->?baz. +"#, + ); + } + + #[test] + fn fun_decl_module_decl_missing() { + check_diagnostics( + r#" + -file("foo.erl",1). + -define(baz,4). + foo(2)->?baz. +%%^^^^^^^^^^^^^💡 error: L1201: no module definition +"#, + ); + } + + #[test] + fn fun_decl_module_decl_missing_2() { + check_diagnostics( + r#" + baz(1)->4. +%%^^^^^^^^^^💡 error: L1201: no module definition + foo(2)->3. +"#, + ); + } + + #[test] + fn fun_decl_module_decl_after_preprocessor() { + check_diagnostics( + r#" +-ifndef(snmpm_net_if_mt). +-module(main). +-endif. +baz(1)->4. +"#, + ); + } +} diff --git a/crates/ide/src/tests.rs b/crates/ide/src/tests.rs index 233a7a33f8..86f0a57555 100644 --- a/crates/ide/src/tests.rs +++ b/crates/ide/src/tests.rs @@ -695,7 +695,7 @@ mod test { fn filtered_diagnostics_passes_syntax_errors() { check_filtered_diagnostics( r#" - %%<^^^^^^^^^^^^ error: L1201: no module definition + %%<^^^^^^^^^^^^ 💡 error: L1201: no module definition foo() -> bug bug. %% ^^^^ error: P1711: Syntax Error From 61512bf9b9ff4be35cdf803e1cf80009ec79660a Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Mon, 8 Dec 2025 04:50:14 -0800 Subject: [PATCH 076/142] Convert unused_include linter to use trait Summary: By using a trait we not only make the code more modular and readable, but we also have the ability to enable/disable the linter via config (without running it). Reviewed By: michalmuskala Differential Revision: D88351141 fbshipit-source-id: 7bcb3b2f72b1eb119f83b1784982d32867f9c486 --- crates/ide/src/diagnostics.rs | 5 +- .../ide/src/diagnostics/unspecific_include.rs | 4 +- crates/ide/src/diagnostics/unused_include.rs | 150 +++++++++++++----- 3 files changed, 110 insertions(+), 49 deletions(-) diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 02ad08fc0d..e4958e25b3 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -1515,10 +1515,6 @@ pub fn native_diagnostics( let labeled_syntax_errors = if report_diagnostics { let sema = Semantic::new(db); - if file_kind.is_module() && (config.include_generated || !db.is_generated(file_id)) { - unused_include::unused_includes(&sema, db, &mut res, file_id); - } - res.append(&mut form_missing_separator_diagnostics(&parse)); res.extend(get_hir_diagnostics(db, file_id)); @@ -1688,6 +1684,7 @@ const GENERIC_LINTERS: &[&dyn GenericDiagnostics] = &[ ¯o_precedence_suprise::LINTER, &edoc::LINTER, &missing_module::LINTER, + &unused_include::LINTER, ]; /// Unified registry for all types of linters diff --git a/crates/ide/src/diagnostics/unspecific_include.rs b/crates/ide/src/diagnostics/unspecific_include.rs index c9a9644ae3..223e5a0064 100644 --- a/crates/ide/src/diagnostics/unspecific_include.rs +++ b/crates/ide/src/diagnostics/unspecific_include.rs @@ -156,6 +156,7 @@ mod tests { use expect_test::expect; use crate::diagnostics::Diagnostic; + use crate::diagnostics::DiagnosticsConfig; use crate::tests; fn filter(d: &Diagnostic) -> bool { @@ -169,7 +170,8 @@ mod tests { #[track_caller] fn check_fix(fixture_before: &str, fixture_after: Expect) { - tests::check_fix(fixture_before, fixture_after) + let config = DiagnosticsConfig::default().disable(DiagnosticCode::UnusedInclude); + tests::check_fix_with_config(config, fixture_before, fixture_after) } #[test] diff --git a/crates/ide/src/diagnostics/unused_include.rs b/crates/ide/src/diagnostics/unused_include.rs index 04a1a8490e..0e0c84458b 100644 --- a/crates/ide/src/diagnostics/unused_include.rs +++ b/crates/ide/src/diagnostics/unused_include.rs @@ -12,6 +12,8 @@ // // Return a warning if nothing is used from an include file +use std::borrow::Cow; + use elp_ide_assists::helpers::extend_range; use elp_ide_db::SearchScope; use elp_ide_db::SymbolDefinition; @@ -19,6 +21,7 @@ use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChange; use elp_ide_db::text_edit::TextEdit; use elp_syntax::SmolStr; +use elp_syntax::TextRange; use elp_syntax::ast::AstNode; use fxhash::FxHashMap; use fxhash::FxHashSet; @@ -31,9 +34,11 @@ use hir::db::DefDatabase; use hir::known; use lazy_static::lazy_static; -use super::Diagnostic; +use crate::Assist; use crate::diagnostics::DiagnosticCode; -use crate::diagnostics::Severity; +use crate::diagnostics::GenericLinter; +use crate::diagnostics::GenericLinterMatchContext; +use crate::diagnostics::Linter; use crate::fix; lazy_static! { @@ -43,56 +48,113 @@ lazy_static! { .collect(); } -pub(crate) fn unused_includes( - sema: &Semantic, - db: &dyn DefDatabase, - diagnostics: &mut Vec, - file_id: FileId, -) { - let form_list = db.file_form_list(file_id); - let mut cache = Default::default(); - let source_file = db.parse(file_id); - for (include_idx, attr) in form_list.includes() { - if !EXCLUDES.contains(attr.path()) { - let in_file = InFile::new(file_id, include_idx); - if let Some(include_file_id) = db.resolve_include(in_file) { - if is_file_used(sema, db, include_file_id, file_id, &mut cache) { - continue; - } +pub(crate) struct UnusedIncludeLinter; - let path = match attr { - IncludeAttribute::Include { path, .. } => path, - IncludeAttribute::IncludeLib { path, .. } => path, - }; - let attribute = attr.form_id().get(&source_file.tree()); - let attribute_syntax = attribute.syntax(); - let attribute_range = attribute_syntax.text_range(); - let mut edit_builder = TextEdit::builder(); - let extended_attribute_range = extend_range(attribute_syntax); - edit_builder.delete(extended_attribute_range); - let edit = edit_builder.finish(); +impl Linter for UnusedIncludeLinter { + fn id(&self) -> DiagnosticCode { + DiagnosticCode::UnusedInclude + } - let diagnostic = Diagnostic::new( - DiagnosticCode::UnusedInclude, - format!("Unused file: {path}"), - attribute_range, - ) - .with_severity(Severity::Warning) - .with_fixes(Some(vec![fix( - "remove_unused_include", - "Remove unused include", - SourceChange::from_text_edit(file_id, edit.clone()), - attribute_range, - )])); + fn description(&self) -> &'static str { + "Unused include file" + } - log::debug!("Found unused include {path:?}"); + fn should_process_file_id(&self, sema: &Semantic, file_id: FileId) -> bool { + let file_kind = sema.db.file_kind(file_id); + file_kind.is_module() + } - diagnostics.push(diagnostic); - } + fn should_process_generated_files(&self) -> bool { + true + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Context { + path: SmolStr, + extended_range: TextRange, +} + +impl Default for Context { + fn default() -> Self { + Context { + path: SmolStr::new(""), + extended_range: TextRange::default(), } } } +impl GenericLinter for UnusedIncludeLinter { + type Context = Context; + + fn matches( + &self, + sema: &Semantic, + file_id: FileId, + ) -> Option>> { + let db = sema.db; + let form_list = db.file_form_list(file_id); + let mut cache = Default::default(); + let source_file = db.parse(file_id); + let mut res = Vec::new(); + + for (include_idx, attr) in form_list.includes() { + if !EXCLUDES.contains(attr.path()) { + let in_file = InFile::new(file_id, include_idx); + if let Some(include_file_id) = db.resolve_include(in_file) { + if is_file_used(sema, db, include_file_id, file_id, &mut cache) { + continue; + } + + let path = match attr { + IncludeAttribute::Include { path, .. } => path, + IncludeAttribute::IncludeLib { path, .. } => path, + }; + let attribute = attr.form_id().get(&source_file.tree()); + let attribute_syntax = attribute.syntax(); + let attribute_range = attribute_syntax.text_range(); + let extended_attribute_range = extend_range(attribute_syntax); + + log::debug!("Found unused include {path:?}"); + + res.push(GenericLinterMatchContext { + range: attribute_range, + context: Context { + path: path.clone(), + extended_range: extended_attribute_range, + }, + }); + } + } + } + Some(res) + } + + fn match_description(&self, context: &Self::Context) -> Cow<'_, str> { + Cow::Owned(format!("Unused file: {}", context.path)) + } + + fn fixes( + &self, + context: &Self::Context, + _sema: &Semantic, + file_id: FileId, + ) -> Option> { + let mut edit_builder = TextEdit::builder(); + edit_builder.delete(context.extended_range); + let edit = edit_builder.finish(); + + Some(vec![fix( + "remove_unused_include", + "Remove unused include", + SourceChange::from_text_edit(file_id, edit), + context.extended_range, + )]) + } +} + +pub static LINTER: UnusedIncludeLinter = UnusedIncludeLinter; + fn is_file_used( sema: &Semantic, db: &dyn DefDatabase, From 4e6851c71a8662f5fb3150866fd27baacd767647 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Mon, 8 Dec 2025 04:50:14 -0800 Subject: [PATCH 077/142] Convert misspelled_attribute linter to use a trait Summary: Mechanical conversion. I also removed the "related info" since they did not seem to provide any additional benefit. Reviewed By: TD5 Differential Revision: D88379801 fbshipit-source-id: 10926fdc31b88b5755ff346f97948cf83569f71e --- .../parse_elp_lint_fixme_spelling.stdout | 2 - .../parse_elp_no_lint_specified_output.stdout | 1 - .../test/linter/warnings_as_errors.stdout | 1 - crates/ide/src/diagnostics.rs | 4 +- .../src/diagnostics/misspelled_attribute.rs | 162 ++++++++++-------- crates/ide/src/diagnostics/unused_include.rs | 1 - 6 files changed, 95 insertions(+), 76 deletions(-) diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_fixme_spelling.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_fixme_spelling.stdout index c7616519bf..b8c9d1896e 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_fixme_spelling.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_fixme_spelling.stdout @@ -2,12 +2,10 @@ module specified: spelling Diagnostics reported in 1 modules: spelling: 1 2:2-2:10::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' - 2:2-2:10: Misspelled attribute --------------------------------------------- Applying fix in module 'spelling' for 2:2-2:10::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' - 2:2-2:10: Misspelled attribute @@ -1,4 +1,5 @@ -module(spelling). +% elp:fixme W0013 (misspelled_attribute) diff --git a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout index 65f84fc805..6a4b28b72b 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout @@ -24,4 +24,3 @@ Diagnostics reported in 7 modules: 8:7-9:16::[Warning] [L1318] expression updates a literal spelling: 1 2:2-2:10::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' - 2:2-2:10: Misspelled attribute diff --git a/crates/elp/src/resources/test/linter/warnings_as_errors.stdout b/crates/elp/src/resources/test/linter/warnings_as_errors.stdout index 706dc801a5..2d9eae2b6e 100644 --- a/crates/elp/src/resources/test/linter/warnings_as_errors.stdout +++ b/crates/elp/src/resources/test/linter/warnings_as_errors.stdout @@ -24,4 +24,3 @@ Diagnostics reported in 7 modules: 8:7-9:16::[Error] [L1318] expression updates a literal spelling: 1 2:2-2:10::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' - 2:2-2:10: Misspelled attribute diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index e4958e25b3..3292aedc6d 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -1685,6 +1685,7 @@ const GENERIC_LINTERS: &[&dyn GenericDiagnostics] = &[ &edoc::LINTER, &missing_module::LINTER, &unused_include::LINTER, + &misspelled_attribute::LINTER, ]; /// Unified registry for all types of linters @@ -1848,13 +1849,12 @@ fn widen_range(range: TextRange) -> TextRange { } } -pub fn syntax_diagnostics( +fn syntax_diagnostics( sema: &Semantic, parse: &Parse, res: &mut Vec, file_id: FileId, ) { - misspelled_attribute::misspelled_attribute(sema, res, file_id); for node in parse.tree().syntax().descendants() { head_mismatch::head_mismatch(res, file_id, &node); module_mismatch::module_mismatch(sema, res, file_id, &node); diff --git a/crates/ide/src/diagnostics/misspelled_attribute.rs b/crates/ide/src/diagnostics/misspelled_attribute.rs index 90695f52d1..f328f57b20 100644 --- a/crates/ide/src/diagnostics/misspelled_attribute.rs +++ b/crates/ide/src/diagnostics/misspelled_attribute.rs @@ -12,14 +12,16 @@ use elp_ide_db::elp_base_db::FileId; use elp_ide_db::source_change::SourceChange; use elp_ide_db::text_edit::TextEdit; use elp_syntax::ast::AstNode; -use elp_syntax::ast::WildAttribute; -use hir::Attribute; use hir::Semantic; -use super::Diagnostic; +use super::DiagnosticCode; +use super::GenericLinter; +use super::GenericLinterMatchContext; +use super::Linter; +use crate::Assist; use crate::TextRange; use crate::TextSize; -use crate::diagnostics::RelatedInformation; +use crate::diagnostics::Severity; use crate::fix; // Diagnostic: misspelled_attribute @@ -34,26 +36,92 @@ use crate::fix; // ``` // -include_lib("/foo/bar/baz.hrl"). // ``` -pub(crate) fn misspelled_attribute( - sema: &Semantic, - diagnostics: &mut Vec, - file_id: FileId, -) { - let form_list = sema.db.file_form_list(file_id); - let potential_misspellings = form_list.attributes().filter_map(|(id, attr)| { - looks_like_misspelling(attr).map(|suggested_rename| (id, attr, suggested_rename)) - }); - potential_misspellings.for_each(|(_id, attr, suggested_rename)| { + +pub(crate) struct MisspelledAttributeLinter; + +impl Linter for MisspelledAttributeLinter { + fn id(&self) -> DiagnosticCode { + DiagnosticCode::MisspelledAttribute + } + + fn description(&self) -> &'static str { + "misspelled attribute" + } + + fn severity(&self, _sema: &Semantic, _file_id: FileId) -> super::Severity { + Severity::Error + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct Context { + range: TextRange, + attr_name: String, + suggested_rename: String, +} + +impl GenericLinter for MisspelledAttributeLinter { + type Context = Context; + + fn matches( + &self, + sema: &Semantic, + file_id: FileId, + ) -> Option>> { + let form_list = sema.db.file_form_list(file_id); let parsed_file = sema.db.parse(file_id); - let attr_form = attr.form_id.get(&parsed_file.tree()); - diagnostics.push(make_diagnostic( - sema, - file_id, - attr, - attr_form, - suggested_rename, - )) - }) + let mut res = Vec::new(); + + for (_id, attr) in form_list.attributes() { + if let Some(suggested_rename) = looks_like_misspelling(attr) { + let attr_form = attr.form_id.get(&parsed_file.tree()); + if let Some(attr_name_node) = attr_form.name() { + let attr_name_range_with_hyphen = attr_name_node.syntax().text_range(); + let attr_name_range = TextRange::new( + attr_name_range_with_hyphen + .start() + .checked_add(TextSize::of('-')) + .unwrap(), + attr_name_range_with_hyphen.end(), + ); + + 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(), + }, + }); + } + } + } + Some(res) + } + + fn match_description(&self, context: &Self::Context) -> std::borrow::Cow<'_, str> { + format!( + "misspelled attribute, saw '{}' but expected '{}'", + context.attr_name, context.suggested_rename + ) + .into() + } + + fn fixes( + &self, + context: &Self::Context, + _sema: &Semantic, + file_id: FileId, + ) -> Option> { + let edit = TextEdit::replace(context.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, + )]) + } } const KNOWN_ATTRIBUTES: &[&str] = &[ @@ -85,7 +153,7 @@ const KNOWN_ATTRIBUTES: &[&str] = &[ "doc", ]; -fn looks_like_misspelling(attr: &Attribute) -> Option<&str> { +fn looks_like_misspelling(attr: &hir::Attribute) -> Option<&str> { let mut suggestions: Vec<(&str, f64)> = KNOWN_ATTRIBUTES .iter() .filter(|&known| &attr.name != known) @@ -103,48 +171,7 @@ fn looks_like_misspelling(attr: &Attribute) -> Option<&str> { .copied() } -fn make_diagnostic( - sema: &Semantic, - file_id: FileId, - attr: &Attribute, - attr_form: WildAttribute, - suggested_rename: &str, -) -> Diagnostic { - // Includes the '-', e.g. "-dialyzer" from `-dialyzer([]).`, but we don't - // want to apply another `.name()`, because for attributes with special - // meanings like `-record(foo, ...).` we would get "foo" - let attr_name_range_with_hyphen = attr_form.name().unwrap().syntax().text_range(); - let attr_name_range = TextRange::new( - attr_name_range_with_hyphen - .start() - .checked_add(TextSize::of('-')) - .unwrap(), - attr_name_range_with_hyphen.end(), - ); - - let edit = TextEdit::replace(attr_name_range, suggested_rename.to_string()); - - Diagnostic::new( - super::DiagnosticCode::MisspelledAttribute, - format!( - "misspelled attribute, saw '{}' but expected '{}'", - attr.name, suggested_rename - ), - attr_name_range, - ) - .with_related(Some(vec![RelatedInformation { - file_id, - range: attr_name_range, - message: "Misspelled attribute".to_string(), - }])) - .with_fixes(Some(vec![fix( - "fix_misspelled_attribute", - format!("Change misspelled attribute to '{suggested_rename}'").as_str(), - SourceChange::from_text_edit(file_id, edit), - attr_name_range, - )])) - .with_ignore_fix(sema, file_id) -} +pub static LINTER: MisspelledAttributeLinter = MisspelledAttributeLinter; // To run the tests via cargo // cargo test --package elp_ide --lib @@ -164,7 +191,6 @@ mod tests { -module(main). -dyalizer({nowarn_function, f/0}). %%% ^^^^^^^^ 💡 error: W0013: misspelled attribute, saw 'dyalizer' but expected 'dialyzer' - %%% | Related info: 0:22-30 Misspelled attribute "#, ); check_fix( @@ -225,7 +251,6 @@ mod tests { -module(main). -module_doc """ %%% ^^^^^^^^^^ 💡 error: W0013: misspelled attribute, saw 'module_doc' but expected 'moduledoc' -%%% | Related info: 0:24-34 Misspelled attribute Hola """. "#, @@ -239,7 +264,6 @@ mod tests { -module(main). -docs """ %%% ^^^^ 💡 error: W0013: misspelled attribute, saw 'docs' but expected 'doc' -%%% | Related info: 0:24-28 Misspelled attribute Hola """. foo() -> ok. diff --git a/crates/ide/src/diagnostics/unused_include.rs b/crates/ide/src/diagnostics/unused_include.rs index 0e0c84458b..eed17b3c5c 100644 --- a/crates/ide/src/diagnostics/unused_include.rs +++ b/crates/ide/src/diagnostics/unused_include.rs @@ -643,7 +643,6 @@ foo() -> ok. %% The following shows up as a wild attribute, which we regard as being used. -defin e(X, 1). %% ^^^^^ 💡 error: W0013: misspelled attribute, saw 'defin' but expected 'define' -%% | Related info: 1:82-87 Misspelled attribute -def ine(Y, 2). "#, From e1296c5c14b9a4a6fca735796a136fd580c7ce87 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Mon, 8 Dec 2025 04:50:14 -0800 Subject: [PATCH 078/142] Convert boolean_precedence linter to use a trait Summary: Mechanical conversion, no functional change. Reviewed By: TheGeorge Differential Revision: D88620344 fbshipit-source-id: fb8a06f034b9c8ebf088acedad1d75769b340293 --- crates/ide/src/diagnostics.rs | 2 +- .../ide/src/diagnostics/boolean_precedence.rs | 196 ++++++++++-------- 2 files changed, 116 insertions(+), 82 deletions(-) diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 3292aedc6d..f9373e25b4 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -1593,7 +1593,6 @@ pub fn diagnostics_descriptors<'a>() -> Vec<&'a DiagnosticDescriptor<'a>> { &deprecated_function::DESCRIPTOR, &head_mismatch::DESCRIPTOR_SEMANTIC, &missing_separator::DESCRIPTOR, - &boolean_precedence::DESCRIPTOR, &record_tuple_match::DESCRIPTOR, &unspecific_include::DESCRIPTOR, ] @@ -1686,6 +1685,7 @@ const GENERIC_LINTERS: &[&dyn GenericDiagnostics] = &[ &missing_module::LINTER, &unused_include::LINTER, &misspelled_attribute::LINTER, + &boolean_precedence::LINTER, ]; /// Unified registry for all types of linters diff --git a/crates/ide/src/diagnostics/boolean_precedence.rs b/crates/ide/src/diagnostics/boolean_precedence.rs index f2ba0fe542..21e374a4f5 100644 --- a/crates/ide/src/diagnostics/boolean_precedence.rs +++ b/crates/ide/src/diagnostics/boolean_precedence.rs @@ -15,6 +15,7 @@ // https://www.erlang.org/doc/system/expressions.html#operator-precedence // So this often results in incorrect or buggy code. +use std::borrow::Cow; use std::fmt; use std::fmt::Display; @@ -45,29 +46,107 @@ use hir::fold::MacroStrategy; use hir::fold::ParenStrategy; use hir::fold::ParentId; -use super::Diagnostic; -use super::DiagnosticConditions; -use super::DiagnosticDescriptor; -use super::Severity; +use super::GenericLinter; +use super::GenericLinterMatchContext; +use super::Linter; +use crate::Assist; use crate::fix; -pub(crate) static DESCRIPTOR: DiagnosticDescriptor = DiagnosticDescriptor { - conditions: DiagnosticConditions { - experimental: false, - include_generated: false, - include_tests: true, - default_disabled: false, - }, - checker: &|diags, sema, file_id, _ext| { - boolean_precedence(diags, sema, file_id); - }, -}; +pub(crate) struct BooleanPrecedenceLinter; -fn boolean_precedence(diagnostics: &mut Vec, sema: &Semantic, file_id: FileId) { - sema.for_each_function(file_id, |def| check_function(diagnostics, sema, def)); +impl Linter for BooleanPrecedenceLinter { + fn id(&self) -> DiagnosticCode { + DiagnosticCode::BooleanPrecedence + } + + fn description(&self) -> &'static str { + "boolean precedence" + } } -fn check_function(diagnostics: &mut Vec, sema: &Semantic, def: &FunctionDef) { +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct Context { + range: TextRange, + preceding_ws_range: TextRange, + op: Op, + lhs_complex: bool, + rhs_complex: bool, + add_parens_range: TextRange, +} + +impl GenericLinter for BooleanPrecedenceLinter { + type Context = Context; + + fn matches( + &self, + sema: &Semantic, + file_id: FileId, + ) -> Option>> { + let mut res = Vec::new(); + sema.for_each_function(file_id, |def| { + check_function(&mut res, sema, def); + }); + Some(res) + } + + fn match_description(&self, context: &Self::Context) -> Cow<'_, str> { + format!( + "Consider using the short-circuit expression '{}' instead of '{}'.\nOr add parentheses to avoid potential ambiguity.", + context.op.preferred(), + context.op, + ) + .into() + } + + fn fixes( + &self, + context: &Self::Context, + _sema: &Semantic, + file_id: FileId, + ) -> Option> { + let mut fixes = Vec::new(); + + // 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.preferred().to_string(), + ); + fixes.push(fix( + "replace_boolean_operator", + &assist_message, + SourceChange::from_text_edit(file_id, edit), + context.range, + )); + + // Add "add parens" fixes if applicable + if context.lhs_complex { + fixes.push(parens_fix("LHS", file_id, context)); + } + if context.rhs_complex { + fixes.push(parens_fix("RHS", file_id, context)); + } + + Some(fixes) + } +} + +fn parens_fix(side: &str, file_id: FileId, context: &Context) -> 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, + ) +} + +fn check_function( + matches: &mut Vec>, + sema: &Semantic, + def: &FunctionDef, +) { let def_fb = def.in_function_body(sema, def); def_fb.clone().fold_function( Strategy { @@ -91,28 +170,20 @@ fn check_function(diagnostics: &mut Vec, sema: &Semantic, def: &Func _ => None, }; if let Some(op) = op { - report( - sema, - &def_fb, - def.file.file_id, - clause_id, - ctx, - op, - diagnostics, - ); + collect_match(matches, sema, &def_fb, def.file.file_id, clause_id, ctx, op); } }, ) } -fn report( +fn collect_match( + matches: &mut Vec>, sema: &Semantic, def_fb: &InFunctionBody<&FunctionDef>, file_id: FileId, clause_id: ClauseId, ctx: AnyCallBackCtx, binop: (Op, ExprId, ExprId), - diagnostics: &mut Vec, ) -> Option<()> { /* we have (inserting parens for tree) @@ -156,39 +227,26 @@ fn report( let (_op, token) = binop_ast.op()?; let range = token.text_range(); let preceding_ws_range = include_preceding_whitespace(&token); - let mut d = make_diagnostic(file_id, range, preceding_ws_range, binop) - .with_ignore_fix(sema, def_fb.file_id()); - if lhs_complex { - add_parens_fix(file_id, &range, add_parens_range, "LHS", &mut d); - } - if rhs_complex { - add_parens_fix(file_id, &range, add_parens_range, "RHS", &mut d); - } - diagnostics.push(d); + + matches.push(GenericLinterMatchContext { + range, + context: Context { + range, + preceding_ws_range, + op: binop, + lhs_complex, + rhs_complex, + add_parens_range, + }, + }); } }; Some(()) } -fn add_parens_fix( - file_id: FileId, - range: &TextRange, - expr_range: TextRange, - where_str: &str, - diag: &mut Diagnostic, -) { - let assist_message = format!("Add parens to {where_str}"); - let edit = add_parens_edit(&expr_range); - diag.add_fix(fix( - "replace_boolean_operator_add_parens", - &assist_message, - SourceChange::from_text_edit(file_id, edit.clone()), - *range, - )); -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] enum Op { + #[default] And, AndInGuard, Or, @@ -223,31 +281,7 @@ impl Op { } } -fn make_diagnostic( - file_id: FileId, - range: TextRange, - preceding_ws_range: TextRange, - op: Op, -) -> Diagnostic { - let message = format!( - "Consider using the short-circuit expression '{}' instead of '{}'.\nOr add parentheses to avoid potential ambiguity.", - op.preferred(), - op, - ); - let assist_message = format!("Replace '{}' with '{}'", op, op.preferred()); - let edit = TextEdit::replace( - op.range(range, preceding_ws_range), - op.preferred().to_string(), - ); - Diagnostic::new(DiagnosticCode::BooleanPrecedence, message, range) - .with_severity(Severity::Warning) - .with_fixes(Some(vec![fix( - "replace_boolean_operator", - &assist_message, - SourceChange::from_text_edit(file_id, edit.clone()), - range, - )])) -} +pub static LINTER: BooleanPrecedenceLinter = BooleanPrecedenceLinter; #[cfg(test)] mod tests { From 0f7069d02e99ea12300caac84a790bddf1fe2c51 Mon Sep 17 00:00:00 2001 From: Victor Lanvin Date: Tue, 9 Dec 2025 06:29:17 -0800 Subject: [PATCH 079/142] Deprecate old error messages Summary: As title. Remove the "detailed message" part, which is too verbose and mostly useless now. Reviewed By: TD5 Differential Revision: D88633277 fbshipit-source-id: 092691c2520983148fa0ef8d61a62bd1149a1231 --- .../eqwalizer_tests/check/any_fun_type.pretty | 26 - .../test/eqwalizer_tests/check/approx.pretty | 6 - .../eqwalizer_tests/check/auto_imports.pretty | 9 +- .../eqwalizer_tests/check/booleans.pretty | 6 - .../check/case_predicates.pretty | 19 - .../eqwalizer_tests/check/complex_maps.pretty | 76 -- .../check/comprehensions.pretty | 44 -- .../check/contravariant.pretty | 10 - .../check/custom-OTP-27.pretty | 746 +----------------- .../check/dynamic_calls.pretty | 48 +- .../check/dynamic_catch.pretty | 6 - .../eqwalizer_tests/check/dynamic_fun.pretty | 30 - .../check/dynamic_generics.pretty | 32 - .../check/dynamic_local_funs.pretty | 6 - .../check/dynamic_refine.pretty | 23 - .../eqwalizer_tests/check/elab_clause.pretty | 12 - .../check/error_messages.pretty | 67 +- .../test/eqwalizer_tests/check/format.pretty | 6 - .../test/eqwalizer_tests/check/funs.pretty | 74 -- .../test/eqwalizer_tests/check/funs2.pretty | 30 - .../check/generic_fun_application.pretty | 231 ------ .../check/generics_with_unions.pretty | 57 -- .../check/gradual_bounded.pretty | 28 - .../check/gradual_complex_types.pretty | 25 - .../check/gradual_custom.pretty | 74 -- .../check/gradual_maybe.pretty | 8 - .../eqwalizer_tests/check/gradual_misc.pretty | 17 +- .../check/guard_b_connections.pretty | 40 - .../eqwalizer_tests/check/guards_logic.pretty | 15 - .../test/eqwalizer_tests/check/hints.pretty | 12 - .../test/eqwalizer_tests/check/iolists.pretty | 16 - .../eqwalizer_tests/check/lists_tests.pretty | 20 - .../test/eqwalizer_tests/check/misc.pretty | 128 +-- .../test/eqwalizer_tests/check/neg.pretty | 7 - .../test/eqwalizer_tests/check/numbers.pretty | 13 - .../test/eqwalizer_tests/check/opaque.pretty | 32 - .../test/eqwalizer_tests/check/other.pretty | 6 - .../test/eqwalizer_tests/check/otp28.pretty | 45 -- .../eqwalizer_tests/check/otp_opaques.pretty | 33 - .../eqwalizer_tests/check/overloaded.pretty | 30 +- .../check/overloaded_specs_union.pretty | 14 - .../test/eqwalizer_tests/check/pats.pretty | 6 - .../test/eqwalizer_tests/check/records.pretty | 119 +-- .../check/recursive_aliases.pretty | 71 -- .../test/eqwalizer_tests/check/refine.pretty | 120 --- .../check/strict_complex_types.pretty | 18 - .../eqwalizer_tests/check/subtype_neg.pretty | 96 --- .../test/eqwalizer_tests/check/t_maps.pretty | 278 ------- .../test/eqwalizer_tests/check/tries.pretty | 6 - .../eqwalizer_tests/check/tuple_union.pretty | 21 - .../eqwalizer_tests/check/type_asserts.pretty | 57 -- .../check/use_dynamic_gradual.pretty | 7 - .../check/use_dynamic_strict.pretty | 7 - .../eqwalizer_tests/eqwater/eqwater.pretty | 158 +--- .../eqwater/eqwater_lists.pretty | 38 - .../eqwater/eqwater_maps.pretty | 18 - .../eqwater/eqwater_records.pretty | 42 - .../eqwater/eqwater_sound.pretty | 16 - .../eqwater/unlimited_refinement.pretty | 6 - .../fault_tolerance/fault_tolerance.pretty | 12 - .../eqwalize_all_bail_on_error_failure.pretty | 40 - .../standard/eqwalize_all_diagnostics.jsonl | 10 +- .../standard/eqwalize_all_diagnostics.pretty | 40 - .../eqwalize_all_diagnostics_gen.jsonl | 10 +- .../test/standard/eqwalize_app_a.jsonl | 6 +- .../test/standard/eqwalize_app_a.pretty | 26 - .../standard/eqwalize_app_a_lists_fast.pretty | 14 - .../standard/eqwalize_app_diagnostics.pretty | 40 - .../eqwalize_app_diagnostics_gen.pretty | 40 - .../eqwalize_app_diagnostics_gen_rebar.pretty | 40 - .../eqwalize_app_diagnostics_rebar.pretty | 40 - .../eqwalize_target_diagnostics.pretty | 40 - crates/elp/tests/slow-tests/main.rs | 6 +- 73 files changed, 43 insertions(+), 3532 deletions(-) diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/any_fun_type.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/any_fun_type.pretty index c2a5c42406..69251d1a75 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/any_fun_type.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/any_fun_type.pretty @@ -37,12 +37,6 @@ Because in the expression's type: Here the type is: fun((term()) -> term()) Context expects type: fun((term(), term()) -> term()) ------------------------------- Detailed message ------------------------------ - - fun((term()) -> term()) is not compatible with f2() - because - fun((term()) -> term()) is not compatible with fun((term(), term()) -> term()) - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/any_fun_type.erl:64:1 │ @@ -67,16 +61,6 @@ Because in the expression's type: Differs from the expected type: 'a' ) ------------------------------- Detailed message ------------------------------ - - f5('a' | 'b') is not compatible with f4('a') - because - fun((term()) -> 'a' | 'b') is not compatible with f4('a') - because - fun((term()) -> 'a' | 'b') is not compatible with fun((...) -> 'a') - because - 'a' | 'b' is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/any_fun_type.erl:98:20 │ @@ -103,14 +87,4 @@ Because in the expression's type: Differs from the expected type: 'a' ) ------------------------------- Detailed message ------------------------------ - - fun((term()) -> 'a' | 'b') is not compatible with f4('a') - because - fun((term()) -> 'a' | 'b') is not compatible with fun((...) -> 'a') - because - 'a' | 'b' is not compatible with 'a' - because - 'b' is not compatible with 'a' - 7 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/approx.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/approx.pretty index 0fe7555553..15655d9cac 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/approx.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/approx.pretty @@ -60,12 +60,6 @@ Because in the expression's type: However the following candidate: string() Differs from the expected type: 'anything' ------------------------------- Detailed message ------------------------------ - - string() | dynamic() is not compatible with 'anything' - because - string() is not compatible with 'anything' - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/approx.erl:74:1 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/auto_imports.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/auto_imports.pretty index f1d2ca6927..2c9ecb3a1f 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/auto_imports.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/auto_imports.pretty @@ -2,15 +2,8 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/auto_imports.erl:22:20 │ 22 │ erlang:error(ok, ok). - │ ^^ - │ │ - │ 'ok'. + │ ^^ 'ok'. Expression has type: 'ok' Context expected type: [term()] | 'none' - │ - - 'ok' is not compatible with [term()] | 'none' - because - 'ok' is not compatible with [term()] 1 ERROR diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/booleans.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/booleans.pretty index 4407c43d42..7227163b41 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/booleans.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/booleans.pretty @@ -22,10 +22,4 @@ Because in the expression's type: However the following candidate: 'false' Differs from the expected type: 'true' ------------------------------- Detailed message ------------------------------ - - 'false' | 'true' is not compatible with 'true' - because - 'false' is not compatible with 'true' - 2 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/case_predicates.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/case_predicates.pretty index 9aff530657..a3bc7c96f9 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/case_predicates.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/case_predicates.pretty @@ -72,17 +72,6 @@ Because in the expression's type: Differs from the expected type: pid() } ------------------------------- Detailed message ------------------------------ - - {'p', pid() | reference()} is not compatible with {'a', atom()} | {'p', pid()} - because - at tuple index 2: - {'p', pid() | reference()} is not compatible with {'p', pid()} - because - pid() | reference() is not compatible with pid() - because - reference() is not compatible with pid() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/case_predicates.erl:144:10 │ @@ -99,14 +88,6 @@ Because in the expression's type: However the following candidate: 'restarting' Differs from the expected type: {'p', pid()} | 'undefined' ------------------------------- Detailed message ------------------------------ - - 'undefined' | 'restarting' is not compatible with {'p', pid()} | 'undefined' - because - 'restarting' is not compatible with {'p', pid()} | 'undefined' - because - 'restarting' is not compatible with {'p', pid()} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/case_predicates.erl:174:16 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/complex_maps.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/complex_maps.pretty index 3143803509..aae5348c54 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/complex_maps.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/complex_maps.pretty @@ -14,10 +14,6 @@ Because in the expression's type: Context expects type: #{...} The expected map has no corresponding key for: c. ------------------------------- Detailed message ------------------------------ - -key `c` is declared in the former but not in the latter and the latter map has no default association - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/complex_maps.erl:17:21 │ @@ -35,15 +31,6 @@ Because in the expression's type: Context expects type: number() , ... } ------------------------------- Detailed message ------------------------------ - - #{a => 'b', c => 'd'} is not compatible with #{a => 'b', atom() => number()} - because - #{a => 'b', c => 'd'} is not compatible with #{a => 'b', atom() => number()} - key `c` is declared in the former but not in the latter and key `c` isn't compatible with the default association of the latter map - because - 'd' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/complex_maps.erl:20:19 │ @@ -60,13 +47,6 @@ Because in the expression's type: Context expects type: #{...} (no default association) The expected map has no default association while the type of the expression has one. ------------------------------- Detailed message ------------------------------ - - #{a => 'b', atom() => atom()} is not compatible with #{a => 'b', c => 'd'} - key c is not present in the former map but is incompatible with its default association - because - atom() is not compatible with 'd' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/complex_maps.erl:23:19 │ @@ -85,13 +65,6 @@ Because in the expression's type: , ... } The context introduces a new association c => 'd' which is incompatible with the expression's default association. ------------------------------- Detailed message ------------------------------ - - #{a => 'b', atom() => number()} is not compatible with #{a => 'b', c => 'd', atom() => number()} - key c is not present in the former map but is incompatible with its default association - because - number() is not compatible with 'd' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/complex_maps.erl:38:21 │ @@ -108,13 +81,6 @@ Because in the expression's type: Context expects type: #{...} (no default association) The expected map has no default association while the type of the expression has one. ------------------------------- Detailed message ------------------------------ - - #{a => 'b', atom() => atom()} is not compatible with #{a => 'b'} - because - #{a => 'b', atom() => atom()} is not compatible with #{a => 'b'} - the latter map has no default association while the first map has one - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/complex_maps.erl:44:26 │ @@ -131,13 +97,6 @@ Because in the expression's type: Context expects type: #{...} (no default association) The expected map has no default association while the type of the expression has one. ------------------------------- Detailed message ------------------------------ - - #{a => 'b', dynamic(atom()) => atom()} is not compatible with #{a => 'b'} - because - #{a => 'b', dynamic(atom()) => atom()} is not compatible with #{a => 'b'} - the latter map has no default association while the first map has one - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/complex_maps.erl:47:26 │ @@ -154,13 +113,6 @@ Because in the expression's type: Context expects type: #{...} (no default association) The expected map has no default association while the type of the expression has one. ------------------------------- Detailed message ------------------------------ - - #{a => 'b', atom() => dynamic(atom())} is not compatible with #{a => 'b'} - because - #{a => 'b', atom() => dynamic(atom())} is not compatible with #{a => 'b'} - the latter map has no default association while the first map has one - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/complex_maps.erl:56:44 │ @@ -218,13 +170,6 @@ Because in the expression's type: Context expects type: atom() , ... } ------------------------------- Detailed message ------------------------------ - - #{atom() => binary()} is not compatible with #{a => binary(), atom() => atom()} - the default associations are not compatible - because - binary() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/complex_maps.erl:83:28 │ @@ -242,13 +187,6 @@ Because in the expression's type: Context expects type: atom() , ... } ------------------------------- Detailed message ------------------------------ - - #{a := 'b', atom() => binary()} is not compatible with #{a => 'b', {c, d} => atom() | binary(), term() => atom()} - the default associations are not compatible - because - binary() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/complex_maps.erl:86:28 │ @@ -266,13 +204,6 @@ Because in the expression's type: Context expects type: atom() , ... } ------------------------------- Detailed message ------------------------------ - - #{a := 'b', atom() => binary()} is not compatible with #{a => 'b', {c, d} => atom() | binary(), atom() => atom()} - the default associations are not compatible - because - binary() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/complex_maps.erl:89:29 │ @@ -290,11 +221,4 @@ Because in the expression's type: Context expects type: binary() , ... } ------------------------------- Detailed message ------------------------------ - - #{dynamic() => atom()} is not compatible with #{dynamic() => binary()} - the default associations are not compatible - because - atom() is not compatible with binary() - 16 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/comprehensions.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/comprehensions.pretty index 396ce7fc52..2b387ca9b9 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/comprehensions.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/comprehensions.pretty @@ -15,12 +15,6 @@ Because in the expression's type: Context expects type: atom() ] ------------------------------- Detailed message ------------------------------ - - [number()] is not compatible with [atom()] - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/comprehensions.erl:40:16 │ @@ -86,12 +80,6 @@ Because in the expression's type: Context expects type: binary() ] ------------------------------- Detailed message ------------------------------ - - [number()] is not compatible with [binary()] - because - number() is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/comprehensions.erl:72:5 │ @@ -270,12 +258,6 @@ Because in the expression's type: Context expects type: binary() ] ------------------------------- Detailed message ------------------------------ - - [atom()] is not compatible with [binary()] - because - atom() is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/comprehensions.erl:226:5 │ @@ -293,12 +275,6 @@ Because in the expression's type: Context expects type: 'false' ] ------------------------------- Detailed message ------------------------------ - - ['true'] is not compatible with ['false'] - because - 'true' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/comprehensions.erl:231:5 │ @@ -316,12 +292,6 @@ Because in the expression's type: Context expects type: 'false' ] ------------------------------- Detailed message ------------------------------ - - ['true'] is not compatible with ['false'] - because - 'true' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/comprehensions.erl:239:5 │ @@ -339,12 +309,6 @@ Because in the expression's type: Context expects type: atom() ] ------------------------------- Detailed message ------------------------------ - - [number()] is not compatible with [atom()] - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/comprehensions.erl:267:5 │ @@ -363,14 +327,6 @@ Because in the expression's type: Differs from the expected type: binary() ] ------------------------------- Detailed message ------------------------------ - - [binary() | 'undefined'] is not compatible with [binary()] - because - binary() | 'undefined' is not compatible with binary() - because - 'undefined' is not compatible with binary() - error: expected_fun_type (See https://fb.me/eqwalizer_errors#expected_fun_type) ┌─ check/src/comprehensions.erl:386:9 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/contravariant.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/contravariant.pretty index 69c58c0e77..f228f5cd50 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/contravariant.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/contravariant.pretty @@ -16,14 +16,4 @@ Because in the expression's type: Differs from the expected type: 'a' ) -> 'ok') ------------------------------- Detailed message ------------------------------ - - ref_contravariant('a') is not compatible with ref_contravariant_ab() - because - contravariant('a') is not compatible with ref_contravariant_ab() - because - fun(('a') -> 'ok') is not compatible with ref_contravariant_ab() - because - fun(('a') -> 'ok') is not compatible with ref_contravariant('a' | 'b') - 1 ERROR diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-27.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-27.pretty index 370f7689e9..efc8c47aa0 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-27.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-27.pretty @@ -37,12 +37,6 @@ Because in the expression's type: However the following candidate: [dynamic()] Differs from the expected type: tuple() ------------------------------- Detailed message ------------------------------ - - {atom(), string()} | [dynamic()] is not compatible with tuple() - because - [dynamic()] is not compatible with tuple() - error: index_out_of_bounds (See https://fb.me/eqwalizer_errors#index_out_of_bounds) ┌─ check/src/custom.erl:48:5 │ @@ -74,12 +68,6 @@ Because in the expression's type: However the following candidate: number() Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - atom() | number() is not compatible with atom() - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:68:5 │ @@ -96,12 +84,6 @@ Because in the expression's type: However the following candidate: number() Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - dynamic() | number() is not compatible with atom() - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:86:5 │ @@ -118,12 +100,6 @@ Because in the expression's type: However the following candidate: string() Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - 'foo' | 'ok' | 'error' | number() | string() is not compatible with atom() - because - string() is not compatible with atom() - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/custom.erl:91:1 │ @@ -202,12 +178,6 @@ Because in the expression's type: However the following candidate: number() Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - number() | atom() is not compatible with atom() - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:200:5 │ @@ -224,12 +194,6 @@ Because in the expression's type: However the following candidate: number() Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - number() | atom() is not compatible with atom() - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:206:5 │ @@ -249,15 +213,6 @@ Because in the expression's type: Differs from the expected type: number() } ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {atom(), 'undefined' | number()} is not compatible with {atom(), number()} - because - 'undefined' | number() is not compatible with number() - because - 'undefined' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:221:27 │ @@ -274,14 +229,6 @@ Because in the expression's type: However the following candidate: 'c_v' Differs from the expected type: 'a_v' | 'b_v' ------------------------------- Detailed message ------------------------------ - - 'a_v' | 'c_v' is not compatible with 'a_v' | 'b_v' - because - 'c_v' is not compatible with 'a_v' | 'b_v' - because - 'c_v' is not compatible with 'a_v' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:233:27 │ @@ -298,14 +245,6 @@ Because in the expression's type: However the following candidate: 'c_v' Differs from the expected type: 'a_v' | 'b_v' | 'undefined' ------------------------------- Detailed message ------------------------------ - - 'undefined' | 'a_v' | 'c_v' is not compatible with 'a_v' | 'b_v' | 'undefined' - because - 'c_v' is not compatible with 'a_v' | 'b_v' | 'undefined' - because - 'c_v' is not compatible with 'a_v' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:309:5 │ @@ -362,28 +301,13 @@ Because in the expression's type: However the following candidate: 'a' Differs from the expected type: #{term() => term()} | maps:iterator() ------------------------------- Detailed message ------------------------------ - - #{K => V} | 'a' is not compatible with #{term() => term()} | maps:iterator() - because - 'a' is not compatible with #{term() => term()} | maps:iterator() - because - 'a' is not compatible with #{term() => term()} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:425:20 │ 425 │ maps:filter(F, non_kv), - │ ^^^^^^ - │ │ - │ 'non_kv'. + │ ^^^^^^ 'non_kv'. Expression has type: 'non_kv' Context expected type: #{term() => term()} | maps:iterator() - │ - - 'non_kv' is not compatible with #{term() => term()} | maps:iterator() - because - 'non_kv' is not compatible with #{term() => term()} error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:454:5 @@ -403,13 +327,6 @@ Because in the expression's type: Context expects type: boolean() , ... } ------------------------------- Detailed message ------------------------------ - - #{number() => pid()} is not compatible with #{number() => boolean()} - the default associations are not compatible - because - pid() is not compatible with boolean() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:474:14 │ @@ -451,29 +368,13 @@ Because in the expression's type: Context expects type: 'a' , ... } ------------------------------- Detailed message ------------------------------ - - #{a := boolean(), b := boolean()} is not compatible with #{a => 'a', b => 'b'} - because - at key `a`: - #{a := boolean(), b := boolean()} is not compatible with #{a => 'a', b => 'b'} - because - boolean() is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:503:17 │ 503 │ maps:map(F, non_kv), - │ ^^^^^^ - │ │ - │ 'non_kv'. + │ ^^^^^^ 'non_kv'. Expression has type: 'non_kv' Context expected type: #{term() => term()} | maps:iterator() - │ - - 'non_kv' is not compatible with #{term() => term()} | maps:iterator() - because - 'non_kv' is not compatible with #{term() => term()} error: fun_arity_mismatch (See https://fb.me/eqwalizer_errors#fun_arity_mismatch) ┌─ check/src/custom.erl:538:9 @@ -502,14 +403,6 @@ Because in the expression's type: No candidate matches in the expected union. ] ------------------------------- Detailed message ------------------------------ - - [[[]]] is not compatible with [number() | 'a' | 'b'] - because - [[]] is not compatible with number() | 'a' | 'b' - because - [[]] is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:545:28 │ @@ -531,14 +424,6 @@ Because in the expression's type: ] ] ------------------------------- Detailed message ------------------------------ - - [[[[]]]] is not compatible with [[[]]] - because - [[[]]] is not compatible with [[]] - because - [[]] is not compatible with [] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:552:5 │ @@ -555,16 +440,9 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/custom.erl:555:9 │ 555 │ non_kv - │ ^^^^^^ - │ │ - │ 'non_kv'. + │ ^^^^^^ 'non_kv'. Expression has type: 'non_kv' Context expected type: #{term() => term()} | maps:iterator() - │ - - 'non_kv' is not compatible with #{term() => term()} | maps:iterator() - because - 'non_kv' is not compatible with #{term() => term()} error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/custom.erl:560:1 @@ -606,12 +484,6 @@ Because in the expression's type: However the following candidate: [] Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - [] | atom() is not compatible with atom() - because - [] is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:601:9 │ @@ -682,30 +554,13 @@ Because in the expression's type: Differs from the expected type: binary() ] ------------------------------- Detailed message ------------------------------ - - [number() | binary() | atom()] is not compatible with [binary()] | [number()] | [atom()] - because - [number() | binary() | atom()] is not compatible with [binary()] - because - number() | binary() | atom() is not compatible with binary() - because - number() is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:671:41 │ 671 │ maps_to_list_7_neg(Num) -> maps:to_list(Num). - │ ^^^ - │ │ - │ Num. + │ ^^^ Num. Expression has type: number() Context expected type: #{term() => term()} | maps:iterator() - │ - - number() is not compatible with #{term() => term()} | maps:iterator() - because - number() is not compatible with #{term() => term()} error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:675:25 @@ -723,13 +578,6 @@ Because in the expression's type: Context expects type: #{...} (no default association) The expected map has no default association while the type of the expression has one. ------------------------------- Detailed message ------------------------------ - - #{'b' | 'a' | 'c' => number() | string() | atom()} is not compatible with #{a => string(), b => number(), c => atom()} - key a is not present in the former map but is incompatible with its default association - because - number() | string() | atom() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:679:25 │ @@ -746,13 +594,6 @@ Because in the expression's type: Context expects type: #{...} (no default association) The expected map has no default association while the type of the expression has one. ------------------------------- Detailed message ------------------------------ - - #{'b' | 'a' | 'c' => number() | string() | atom()} is not compatible with #{a => string(), b => number() | boolean(), c => atom()} - key a is not present in the former map but is incompatible with its default association - because - number() | string() | atom() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:683:25 │ @@ -769,10 +610,6 @@ Because in the expression's type: Context expects type: #{a := ..., b := ..., ...} The type of the expression is missing the following required keys: a, b. ------------------------------- Detailed message ------------------------------ - -keys `a`, `b` are declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:697:44 │ @@ -797,13 +634,6 @@ Because in the expression's type: Context expects type: #{a := ..., b := ..., ...} The type of the expression is missing the following required keys: a, b. ------------------------------- Detailed message ------------------------------ - - #{'a' | 'b' => atom() | number()} is not compatible with #{a := atom(), b := number()} | #{a := atom()} - because - #{'a' | 'b' => atom() | number()} is not compatible with #{a := atom(), b := number()} - keys `a`, `b` are declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:721:9 │ @@ -822,14 +652,6 @@ Because in the expression's type: No candidate matches in the expected union. ) ------------------------------- Detailed message ------------------------------ - - fun((binary()) -> [number()]) is not compatible with fun((number()) -> boolean() | {'true', term()}) - because - [number()] is not compatible with boolean() | {'true', term()} - because - [number()] is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:739:20 │ @@ -846,12 +668,6 @@ Because in the expression's type: Context expects type: 'false' | 'true' | {'true', term()} No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - - 'wrong_ret' is not compatible with boolean() | {'true', term()} - because - 'wrong_ret' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:749:9 │ @@ -876,14 +692,6 @@ Because in the expression's type: However the following candidate: 'wrong_ret' Differs from the expected type: 'false' | 'true' | {'true', term()} ------------------------------- Detailed message ------------------------------ - - {'true', 'a'} | 'wrong_ret' is not compatible with boolean() | {'true', term()} - because - 'wrong_ret' is not compatible with boolean() | {'true', term()} - because - 'wrong_ret' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:827:9 │ @@ -902,16 +710,6 @@ Because in the expression's type: Differs from the expected type: 'false' | 'true' | ['a' | 'b'] ) ------------------------------- Detailed message ------------------------------ - - fun(('a' | 'b') -> ['a'] | 'true' | 'wrong_ret') is not compatible with fun(('a' | 'b') -> boolean() | ['a' | 'b']) - because - ['a'] | 'true' | 'wrong_ret' is not compatible with boolean() | ['a' | 'b'] - because - 'wrong_ret' is not compatible with boolean() | ['a' | 'b'] - because - 'wrong_ret' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:829:20 │ @@ -928,12 +726,6 @@ Because in the expression's type: Context expects type: 'false' | 'true' | ['a' | 'b'] No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - - 'wrong_ret' is not compatible with boolean() | ['a' | 'b'] - because - 'wrong_ret' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:837:9 │ @@ -951,15 +743,6 @@ Because in the expression's type: Differs from the expected type: 'false' | 'true' | [Item] ) ------------------------------- Detailed message ------------------------------ - - fun((dynamic()) -> {'true', 'a'} | 'true') is not compatible with fun((Item) -> boolean() | [Item]) - because - {'true', 'a'} | 'true' is not compatible with boolean() | [Item] - because - {'true', 'a'} is not compatible with boolean() | [Item] - expected union does not contain any tuple type of size 2 - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:837:20 │ @@ -976,10 +759,6 @@ Because in the expression's type: Context expects type: 'false' | 'true' | [dynamic()] No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - -expected union does not contain any tuple type of size 2 - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:839:9 │ @@ -995,12 +774,6 @@ Because in the expression's type: Here the type is: 'not_a_queue' Context expects type: {[Item], [Item]} ------------------------------- Detailed message ------------------------------ - - 'not_a_queue' is not compatible with queue:queue(Item) - because - 'not_a_queue' is not compatible with {[Item], [Item]} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:839:9 │ @@ -1016,12 +789,6 @@ Because in the expression's type: Here the type is: 'not_a_queue' Context expects type: {[dynamic()], [dynamic()]} ------------------------------- Detailed message ------------------------------ - - 'not_a_queue' is not compatible with queue:queue(dynamic()) - because - 'not_a_queue' is not compatible with {[dynamic()], [dynamic()]} - error: fun_arity_mismatch (See https://fb.me/eqwalizer_errors#fun_arity_mismatch) ┌─ check/src/custom.erl:846:9 │ @@ -1053,15 +820,6 @@ Because in the expression's type: Differs from the expected type: 'false' | 'true' | ['a' | 'b'] ) ------------------------------- Detailed message ------------------------------ - - fun(('a' | 'b') -> ['a'] | 'false' | {'true', 'a'}) is not compatible with fun(('a' | 'b') -> boolean() | ['a' | 'b']) - because - ['a'] | 'false' | {'true', 'a'} is not compatible with boolean() | ['a' | 'b'] - because - {'true', 'a'} is not compatible with boolean() | ['a' | 'b'] - expected union does not contain any tuple type of size 2 - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:857:20 │ @@ -1078,10 +836,6 @@ Because in the expression's type: Context expects type: 'false' | 'true' | ['a' | 'b'] No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - -expected union does not contain any tuple type of size 2 - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:873:9 │ @@ -1104,16 +858,6 @@ Because in the expression's type: Differs from the expected type: 'false' | 'true' | ['a' | 'b'] ) ------------------------------- Detailed message ------------------------------ - - fun(('a' | 'b') -> ['a'] | 'wrong_ret') is not compatible with fun(('a' | 'b') -> boolean() | ['a' | 'b']) - because - ['a'] | 'wrong_ret' is not compatible with boolean() | ['a' | 'b'] - because - 'wrong_ret' is not compatible with boolean() | ['a' | 'b'] - because - 'wrong_ret' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:881:17 │ @@ -1130,14 +874,6 @@ Because in the expression's type: However the following candidate: 'wrong_ret' Differs from the expected type: 'false' | 'true' | ['a' | 'b'] ------------------------------- Detailed message ------------------------------ - - ['a'] | 'wrong_ret' is not compatible with boolean() | ['a' | 'b'] - because - 'wrong_ret' is not compatible with boolean() | ['a' | 'b'] - because - 'wrong_ret' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:891:9 │ @@ -1156,14 +892,6 @@ Because in the expression's type: No candidate matches in the expected union. ) ------------------------------- Detailed message ------------------------------ - - fun((string()) -> atom()) is not compatible with fun((dynamic()) -> boolean() | [dynamic()]) - because - atom() is not compatible with boolean() | [dynamic()] - because - atom() is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:891:9 │ @@ -1182,14 +910,6 @@ Because in the expression's type: No candidate matches in the expected union. ) ------------------------------- Detailed message ------------------------------ - - fun((string()) -> atom()) is not compatible with fun(('a' | 'b') -> boolean() | ['a' | 'b']) - because - atom() is not compatible with boolean() | ['a' | 'b'] - because - atom() is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:900:9 │ @@ -1207,12 +927,6 @@ Because in the expression's type: Context expects type: boolean() | [atom()] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((atom()) -> boolean() | [atom()]) - because - string() is not compatible with boolean() | [atom()] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:900:9 │ @@ -1230,12 +944,6 @@ Because in the expression's type: Context expects type: boolean() | [dynamic(atom())] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((dynamic(atom())) -> boolean() | [dynamic(atom())]) - because - string() is not compatible with boolean() | [dynamic(atom())] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:909:9 │ @@ -1253,12 +961,6 @@ Because in the expression's type: Context expects type: boolean() | [atom()] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((atom()) -> boolean() | [atom()]) - because - string() is not compatible with boolean() | [atom()] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:909:9 │ @@ -1276,12 +978,6 @@ Because in the expression's type: Context expects type: boolean() | [dynamic(atom())] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((dynamic(atom())) -> boolean() | [dynamic(atom())]) - because - string() is not compatible with boolean() | [dynamic(atom())] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:919:9 │ @@ -1299,12 +995,6 @@ Because in the expression's type: Context expects type: boolean() | [atom()] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((atom()) -> boolean() | [atom()]) - because - string() is not compatible with boolean() | [atom()] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:919:9 │ @@ -1322,12 +1012,6 @@ Because in the expression's type: Context expects type: boolean() | [dynamic(atom())] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((dynamic(atom())) -> boolean() | [dynamic(atom())]) - because - string() is not compatible with boolean() | [dynamic(atom())] - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/custom.erl:925:1 │ @@ -1356,12 +1040,6 @@ Because in the expression's type: Context expects type: boolean() | [atom()] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((atom()) -> boolean() | [atom()]) - because - string() is not compatible with boolean() | [atom()] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:927:9 │ @@ -1379,12 +1057,6 @@ Because in the expression's type: Context expects type: boolean() | [dynamic(atom())] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((dynamic(atom())) -> boolean() | [dynamic(atom())]) - because - string() is not compatible with boolean() | [dynamic(atom())] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:955:5 │ @@ -1434,12 +1106,6 @@ Because in the expression's type: Context expects type: tuple() ] ------------------------------- Detailed message ------------------------------ - - ['non_tuple'] is not compatible with [tuple()] - because - 'non_tuple' is not compatible with tuple() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:975:5 │ @@ -1540,14 +1206,6 @@ Because in the expression's type: Differs from the expected type: number() ] ------------------------------- Detailed message ------------------------------ - - ['a' | number()] is not compatible with [number()] - because - 'a' | number() is not compatible with number() - because - 'a' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1042:15 │ @@ -1592,31 +1250,17 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/custom.erl:1100:5 │ 1100 │ proplists:get_value(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('k', L). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L). Expression has type: term() Context expected type: pid() | 'undefined' - │ - - term() is not compatible with pid() | 'undefined' - because - term() is not compatible with pid() error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1106:5 │ 1106 │ proplists:get_value(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('k', L). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L). Expression has type: term() Context expected type: pid() | 'undefined' | 'v' - │ - - term() is not compatible with pid() | 'undefined' | 'v' - because - term() is not compatible with pid() error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1111:5 @@ -1630,31 +1274,17 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/custom.erl:1117:5 │ 1117 │ proplists:get_value(k, L, 3). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('k', L, 3). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L, 3). Expression has type: term() Context expected type: pid() | number() - │ - - term() is not compatible with pid() | number() - because - term() is not compatible with pid() error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1123:5 │ 1123 │ proplists:get_value(k, L, my_default). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('k', L, 'my_default'). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L, 'my_default'). Expression has type: term() Context expected type: 'v1' | 'v2' | 'my_default' - │ - - term() is not compatible with 'v1' | 'v2' | 'my_default' - because - term() is not compatible with 'v1' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1129:5 @@ -1722,14 +1352,6 @@ Because in the expression's type: No candidate matches in the expected union. ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with [pid() | 'default'] - because - term() is not compatible with pid() | 'default' - because - term() is not compatible with pid() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1166:5 │ @@ -1748,14 +1370,6 @@ Because in the expression's type: No candidate matches in the expected union. ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with [pid() | 'default' | 'v'] - because - term() is not compatible with pid() | 'default' | 'v' - because - term() is not compatible with pid() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1171:5 │ @@ -1773,12 +1387,6 @@ Because in the expression's type: Context expects type: pid() ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with [pid()] - because - term() is not compatible with pid() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1176:5 │ @@ -1846,12 +1454,6 @@ Because in the expression's type: Context expects type: 'c' ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with ['c'] - because - term() is not compatible with 'c' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1197:5 │ @@ -1870,14 +1472,6 @@ Because in the expression's type: No candidate matches in the expected union. ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with ['a' | 'b' | 'c'] - because - term() is not compatible with 'a' | 'b' | 'c' - because - term() is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1202:24 │ @@ -1903,41 +1497,21 @@ Because in the expression's type: Context expects type: 'a' ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with ['a'] - because - term() is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1215:5 │ 1215 │ proplists:get_value(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('k', L). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L). Expression has type: term() Context expected type: 'a' | pid() - │ - - term() is not compatible with 'a' | pid() - because - term() is not compatible with 'a' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1220:5 │ 1220 │ proplists:get_value(k, b). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('k', 'b'). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', 'b'). Expression has type: term() Context expected type: 'a' | pid() - │ - - term() is not compatible with 'a' | pid() - because - term() is not compatible with 'a' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1220:28 @@ -1987,12 +1561,6 @@ Because in the expression's type: However the following candidate: tuple() Differs from the expected type: 'none' ------------------------------- Detailed message ------------------------------ - - 'none' | tuple() is not compatible with 'none' - because - tuple() is not compatible with 'none' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1252:29 │ @@ -2065,17 +1633,6 @@ Because in the expression's type: ] , [term()]} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {[[term()]], [term()]} is not compatible with {[plist('a', 'b')], plist('a', 'b')} - because - [[term()]] is not compatible with [plist('a', 'b')] - because - [term()] is not compatible with plist('a', 'b') - because - [term()] is not compatible with ['a' | {'a', 'b'}] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1315:21 │ @@ -2120,46 +1677,25 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/custom.erl:1363:5 │ 1363 │ proplists:get_value(a, [a]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('a', ['a']). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('a', ['a']). Expression has type: term() Context expected type: 'true' | 'undefined' - │ - - term() is not compatible with 'true' | 'undefined' - because - term() is not compatible with 'true' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1368:5 │ 1368 │ proplists:get_value(X, [a]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value(X, ['a']). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value(X, ['a']). Expression has type: term() Context expected type: 'true' | 'undefined' - │ - - term() is not compatible with 'true' | 'undefined' - because - term() is not compatible with 'true' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1373:5 │ 1373 │ proplists:get_value(a, [a], b). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('a', ['a'], 'b'). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('a', ['a'], 'b'). Expression has type: term() Context expected type: 'true' | 'b' - │ - - term() is not compatible with 'true' | 'b' - because - term() is not compatible with 'true' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1416:5 @@ -2194,12 +1730,6 @@ Because in the expression's type: Context expects type: tuple() ] ------------------------------- Detailed message ------------------------------ - - [number()] is not compatible with [tuple()] - because - number() is not compatible with tuple() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1448:5 │ @@ -2222,14 +1752,6 @@ Because in the expression's type: Differs from the expected type: number() ] ------------------------------- Detailed message ------------------------------ - - [string() | number()] is not compatible with [number()] - because - string() | number() is not compatible with number() - because - string() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1482:5 │ @@ -2373,17 +1895,6 @@ Because in the expression's type: , A} ] ------------------------------- Detailed message ------------------------------ - - [{A, B} | {B, A}] is not compatible with [{A, B}] - because - {A, B} | {B, A} is not compatible with {A, B} - because - at tuple index 1: - {B, A} is not compatible with {A, B} - because - B is not compatible with A - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1757:5 │ @@ -2404,10 +1915,6 @@ Because in the expression's type: Context expects type: #{...} The expected map has no corresponding key for: a. ------------------------------- Detailed message ------------------------------ - -key `a` is declared in the former but not in the latter and the latter map has no default association - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1769:18 │ @@ -2451,10 +1958,6 @@ Because in the expression's type: Context expects type: #{b := ..., ...} The type of the expression is missing the following required keys: b. ------------------------------- Detailed message ------------------------------ - -key `b` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1826:5 │ @@ -2472,10 +1975,6 @@ Because in the expression's type: Context expects type: #{b := ..., ...} The type of the expression is missing the following required keys: b. ------------------------------- Detailed message ------------------------------ - -key `b` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1827:12 │ @@ -2501,10 +2000,6 @@ Because in the expression's type: Context expects type: #{b := ..., ...} The type of the expression is missing the following required keys: b. ------------------------------- Detailed message ------------------------------ - -key `b` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1854:5 │ @@ -2525,16 +2020,9 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/custom.erl:1916:23 │ 1916 │ custom_overloaded(X). - │ ^ - │ │ - │ X. + │ ^ X. Expression has type: term() Context expected type: atom() | binary() - │ - - term() is not compatible with atom() | binary() - because - term() is not compatible with atom() error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1939:5 @@ -2554,15 +2042,6 @@ Because in the expression's type: Differs from the expected type: number() } ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {atom(), number() | pid()} is not compatible with {atom(), number()} - because - number() | pid() is not compatible with number() - because - pid() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2041:5 │ @@ -2578,14 +2057,6 @@ Because in the expression's type: Here the type is: string() | binary() Context expects type: string() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with file:filename() - because - string() | binary() is not compatible with file:filename() - because - string() | binary() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2046:5 │ @@ -2601,14 +2072,6 @@ Because in the expression's type: Here the type is: string() | binary() Context expects type: string() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with file:filename() - because - string() | binary() is not compatible with file:filename() - because - string() | binary() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2051:5 │ @@ -2624,14 +2087,6 @@ Because in the expression's type: Here the type is: string() | binary() Context expects type: string() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with file:filename() - because - string() | binary() is not compatible with file:filename() - because - string() | binary() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2066:5 │ @@ -2648,14 +2103,6 @@ Because in the expression's type: However the following candidate: string() Differs from the expected type: binary() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with binary() - because - string() | binary() is not compatible with binary() - because - string() is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2081:19 │ @@ -2674,16 +2121,6 @@ Because in the expression's type: Differs from the expected type: string() | atom() | file:deep_list() | binary() ] ------------------------------- Detailed message ------------------------------ - - [binary() | pid()] is not compatible with [file:name_all()] - because - binary() | pid() is not compatible with file:name_all() - because - binary() | pid() is not compatible with string() | atom() | file:deep_list() | binary() - because - pid() is not compatible with string() | atom() | file:deep_list() | binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2086:5 │ @@ -2699,14 +2136,6 @@ Because in the expression's type: Here the type is: string() | binary() Context expects type: string() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with file:filename() - because - string() | binary() is not compatible with file:filename() - because - string() | binary() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2091:5 │ @@ -2722,14 +2151,6 @@ Because in the expression's type: Here the type is: string() | binary() Context expects type: string() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with file:filename() - because - string() | binary() is not compatible with file:filename() - because - string() | binary() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2096:5 │ @@ -2745,14 +2166,6 @@ Because in the expression's type: Here the type is: string() | binary() Context expects type: string() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with file:filename() - because - string() | binary() is not compatible with file:filename() - because - string() | binary() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2111:5 │ @@ -2769,14 +2182,6 @@ Because in the expression's type: However the following candidate: string() Differs from the expected type: binary() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with binary() - because - string() | binary() is not compatible with binary() - because - string() is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2121:25 │ @@ -2793,14 +2198,6 @@ Because in the expression's type: Context expects type: string() | atom() | file:deep_list() | binary() No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - - pid() is not compatible with file:name_all() - because - pid() is not compatible with string() | atom() | file:deep_list() | binary() - because - pid() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2150:5 │ @@ -2822,17 +2219,6 @@ Because in the expression's type: ] , [atom() | number()]} ------------------------------- Detailed message ------------------------------ - - queue:queue(atom() | number()) is not compatible with queue:queue(number()) - because - {[atom() | number()], [atom() | number()]} is not compatible with queue:queue(number()) - because - at tuple index 1: - {[atom() | number()], [atom() | number()]} is not compatible with {[number()], [number()]} - because - [atom() | number()] is not compatible with [number()] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2191:5 │ @@ -2851,15 +2237,6 @@ Because in the expression's type: Context expects type: #{count := ..., ...} The type of the expression is missing the following required keys: count. ------------------------------- Detailed message ------------------------------ - - #{count := number(), module := 'foo'} | #{module := 'foo'} is not compatible with state1() - because - #{count := number(), module := 'foo'} | #{module := 'foo'} is not compatible with #{count := number(), module := atom()} - because - #{module := 'foo'} is not compatible with #{count := number(), module := atom()} - key `count` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2223:13 │ @@ -2884,10 +2261,6 @@ Because in the expression's type: Context expects type: #{a := ..., ...} The type of the expression is missing the following required keys: a. ------------------------------- Detailed message ------------------------------ - -key `a` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2320:5 │ @@ -2908,10 +2281,6 @@ Because in the expression's type: Context expects type: #{a := ..., b := ..., ...} The type of the expression is missing the following required keys: a, b. ------------------------------- Detailed message ------------------------------ - -keys `a`, `b` are declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2342:5 │ @@ -2932,13 +2301,6 @@ Because in the expression's type: Context expects type: atom() , ... } ------------------------------- Detailed message ------------------------------ - - #{atom() => binary()} is not compatible with #{atom() => atom()} - the default associations are not compatible - because - binary() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2354:23 │ @@ -2955,12 +2317,6 @@ Because in the expression's type: Context expects type: 'false' | 'true' | {'true', term()} No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - - 'err' is not compatible with boolean() | {'true', term()} - because - 'err' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2362:5 │ @@ -2979,13 +2335,6 @@ Because in the expression's type: Context expects type: atom() , ... } ------------------------------- Detailed message ------------------------------ - - #{atom() => binary()} is not compatible with #{atom() => atom()} - the default associations are not compatible - because - binary() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2363:45 │ @@ -3026,12 +2375,6 @@ Because in the expression's type: Context expects type: iolist() | binary() No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - - atom() is not compatible with iodata() | unicode:charlist() - because - atom() is not compatible with iolist() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2386:5 │ @@ -3060,17 +2403,6 @@ Because in the expression's type: } ] ------------------------------- Detailed message ------------------------------ - - [{'return', 'something'}] is not compatible with [{'newline', 'cr' | 'lf' | 'any' | 'crlf' | 'anycrlf'} | 'notempty_atstart' | 'noteol' | 'bsr_unicode' | 'notbol' | 'global' | {'match_limit_recursion', number()} | 'bsr_anycrlf' | re:compile_option() | {'match_limit', number()} | {'offset', number()} | 'notempty' | {'return', 'iodata' | 'list' | 'binary'} | 'anchored'] - because - {'return', 'something'} is not compatible with {'newline', 'cr' | 'lf' | 'any' | 'crlf' | 'anycrlf'} | 'notempty_atstart' | 'noteol' | 'bsr_unicode' | 'notbol' | 'global' | {'match_limit_recursion', number()} | 'bsr_anycrlf' | re:compile_option() | {'match_limit', number()} | {'offset', number()} | 'notempty' | {'return', 'iodata' | 'list' | 'binary'} | 'anchored' - because - at tuple index 2: - {'return', 'something'} is not compatible with {'return', 'iodata' | 'list' | 'binary'} - because - 'something' is not compatible with 'iodata' | 'list' | 'binary' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2506:5 │ @@ -3090,15 +2422,6 @@ Because in the expression's type: ] , [atom()]} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {[number()], [atom()]} is not compatible with {[atom()], [number()]} - because - [number()] is not compatible with [atom()] - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2518:5 │ @@ -3120,18 +2443,6 @@ Because in the expression's type: ] , [{term(), atom()}]} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {[{term(), number()}], [{term(), atom()}]} is not compatible with {[{term(), atom()}], [{term(), number()}]} - because - [{term(), number()}] is not compatible with [{term(), atom()}] - because - at tuple index 2: - {term(), number()} is not compatible with {term(), atom()} - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2536:5 │ @@ -3153,18 +2464,6 @@ Because in the expression's type: ] } ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {[{'ok', atom()}], [{'ok', atom()} | {'error', term()}]} is not compatible with {[{'ok', atom()}], [{'error', term()}]} - because - [{'ok', atom()} | {'error', term()}] is not compatible with [{'error', term()}] - because - {'ok', atom()} | {'error', term()} is not compatible with {'error', term()} - because - at tuple index 1: - {'ok', atom()} is not compatible with {'error', term()} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2576:33 │ @@ -3181,10 +2480,6 @@ Because in the expression's type: Context expects type: #{a := ..., ...} The type of the expression is missing the following required keys: a. ------------------------------- Detailed message ------------------------------ - -key `a` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2586:33 │ @@ -3202,15 +2497,6 @@ Because in the expression's type: Context expects type: 'true' , ... } ------------------------------- Detailed message ------------------------------ - - #{a => number()} is not compatible with #{a => 'true'} - because - at key `a`: - #{a => number()} is not compatible with #{a => 'true'} - because - number() is not compatible with 'true' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2596:33 │ @@ -3227,10 +2513,6 @@ Because in the expression's type: Context expects type: #{a := ..., ...} The type of the expression is missing the following required keys: a. ------------------------------- Detailed message ------------------------------ - -key `a` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2709:40 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_calls.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_calls.pretty index a8dc692a9b..bc6265b8c1 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_calls.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_calls.pretty @@ -66,61 +66,33 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/dynamic_calls.erl:78:14 │ 78 │ (FUnion)(false). - │ ^^^^^ - │ │ - │ 'false'. + │ ^^^^^ 'false'. Expression has type: 'false' Context expected type: 'a1' | 'a2' - │ - - 'false' is not compatible with 'a1' | 'a2' - because - 'false' is not compatible with 'a1' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/dynamic_calls.erl:78:14 │ 78 │ (FUnion)(false). - │ ^^^^^ - │ │ - │ 'false'. + │ ^^^^^ 'false'. Expression has type: 'false' Context expected type: 'a2' | 'a3' - │ - - 'false' is not compatible with 'a2' | 'a3' - because - 'false' is not compatible with 'a2' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/dynamic_calls.erl:86:20 │ 86 │ Res = (FUnion)(false), - │ ^^^^^ - │ │ - │ 'false'. + │ ^^^^^ 'false'. Expression has type: 'false' Context expected type: 'a1' | 'a2' - │ - - 'false' is not compatible with 'a1' | 'a2' - because - 'false' is not compatible with 'a1' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/dynamic_calls.erl:86:20 │ 86 │ Res = (FUnion)(false), - │ ^^^^^ - │ │ - │ 'false'. + │ ^^^^^ 'false'. Expression has type: 'false' Context expected type: 'a2' | 'a3' - │ - - 'false' is not compatible with 'a2' | 'a3' - because - 'false' is not compatible with 'a2' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/dynamic_calls.erl:87:5 @@ -146,12 +118,6 @@ Because in the expression's type: However the following candidate: 'r2' Differs from the expected type: 'r1' ------------------------------- Detailed message ------------------------------ - - 'r1' | 'r2' is not compatible with 'r1' - because - 'r2' is not compatible with 'r1' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/dynamic_calls.erl:94:5 │ @@ -176,12 +142,6 @@ Because in the expression's type: However the following candidate: 'r2' Differs from the expected type: 'r1' ------------------------------- Detailed message ------------------------------ - - 'r1' | 'r2' | 'r3' is not compatible with 'r1' - because - 'r2' is not compatible with 'r1' - error: expected_fun_type (See https://fb.me/eqwalizer_errors#expected_fun_type) ┌─ check/src/dynamic_calls.erl:108:5 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_catch.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_catch.pretty index a8135429d1..78fd9f16f2 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_catch.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_catch.pretty @@ -14,10 +14,4 @@ Because in the expression's type: However the following candidate: atom() Differs from the expected type: binary() ------------------------------- Detailed message ------------------------------ - - atom() | dynamic() is not compatible with binary() - because - atom() is not compatible with binary() - 1 ERROR diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_fun.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_fun.pretty index 1f55b0f88a..7f7add29a8 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_fun.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_fun.pretty @@ -72,16 +72,6 @@ Because in the expression's type: Differs from the expected type: 'a' ) ------------------------------- Detailed message ------------------------------ - - f5('a' | 'b') is not compatible with f4('a') - because - fun((term()) -> 'a' | 'b') is not compatible with f4('a') - because - fun((term()) -> 'a' | 'b') is not compatible with fun((...) -> 'a') - because - 'a' | 'b' is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/dynamic_fun.erl:155:20 │ @@ -108,16 +98,6 @@ Because in the expression's type: Differs from the expected type: 'a' ) ------------------------------- Detailed message ------------------------------ - - fun((term()) -> 'a' | 'b') is not compatible with f4('a') - because - fun((term()) -> 'a' | 'b') is not compatible with fun((...) -> 'a') - because - 'a' | 'b' is not compatible with 'a' - because - 'b' is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/dynamic_fun.erl:176:21 │ @@ -136,16 +116,6 @@ Because in the expression's type: Differs from the expected type: 'a' ) ------------------------------- Detailed message ------------------------------ - - f4('a' | 'b') is not compatible with fun((term()) -> 'a') - because - fun((...) -> 'a' | 'b') is not compatible with fun((term()) -> 'a') - because - 'a' | 'b' is not compatible with 'a' - because - 'b' is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/dynamic_fun.erl:208:34 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_generics.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_generics.pretty index cd54f2d176..78c9a41f3f 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_generics.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_generics.pretty @@ -38,12 +38,6 @@ Because in the expression's type: However the following candidate: number() Differs from the expected type: none() ------------------------------- Detailed message ------------------------------ - - number() | dynamic() is not compatible with none() - because - number() is not compatible with none() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/dynamic_generics.erl:72:3 │ @@ -68,12 +62,6 @@ Because in the expression's type: However the following candidate: string() Differs from the expected type: 'ok' ------------------------------- Detailed message ------------------------------ - - dynamic() | string() is not compatible with 'ok' - because - string() is not compatible with 'ok' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/dynamic_generics.erl:87:3 │ @@ -112,14 +100,6 @@ Because in the expression's type: Differs from the expected type: number() ] ------------------------------- Detailed message ------------------------------ - - [number() | 'three'] is not compatible with [number()] - because - number() | 'three' is not compatible with number() - because - 'three' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/dynamic_generics.erl:115:9 │ @@ -136,12 +116,6 @@ Because in the expression's type: However the following candidate: 'three' Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - number() | 'three' is not compatible with number() - because - 'three' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/dynamic_generics.erl:115:22 │ @@ -158,12 +132,6 @@ Because in the expression's type: However the following candidate: 'three' Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - number() | 'three' is not compatible with number() - because - 'three' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/dynamic_generics.erl:127:13 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_local_funs.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_local_funs.pretty index 3ca5a136e6..6e82aa3702 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_local_funs.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_local_funs.pretty @@ -14,12 +14,6 @@ Because in the expression's type: However the following candidate: number() Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - dynamic() | number() is not compatible with atom() - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/dynamic_local_funs.erl:36:15 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_refine.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_refine.pretty index 9d7187f610..ce03971816 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_refine.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/dynamic_refine.pretty @@ -100,12 +100,6 @@ Because in the expression's type: However the following candidate: 'error' Differs from the expected type: 'ok' ------------------------------- Detailed message ------------------------------ - - dynamic() | 'error' is not compatible with 'ok' - because - 'error' is not compatible with 'ok' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/dynamic_refine.erl:236:3 │ @@ -138,12 +132,6 @@ Because in the expression's type: However the following candidate: 'err' Differs from the expected type: 'ok' ------------------------------- Detailed message ------------------------------ - - dyn_alias() | 'err' is not compatible with 'ok' - because - 'err' is not compatible with 'ok' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/dynamic_refine.erl:260:27 │ @@ -162,15 +150,4 @@ Because in the expression's type: Differs from the expected type: 'ok' } ------------------------------- Detailed message ------------------------------ - - union() is not compatible with {'ok'} - because - at tuple index 1: - {dyn_alias() | 'err'} is not compatible with {'ok'} - because - dyn_alias() | 'err' is not compatible with 'ok' - because - 'err' is not compatible with 'ok' - 16 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/elab_clause.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/elab_clause.pretty index b9fdc2fbce..e02928cc14 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/elab_clause.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/elab_clause.pretty @@ -38,12 +38,6 @@ Because in the expression's type: However the following candidate: 'error' Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - 'error' | 'exit' | 'throw' | number() is not compatible with number() - because - 'error' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/elab_clause.erl:86:5 │ @@ -60,12 +54,6 @@ Because in the expression's type: However the following candidate: [dynamic()] Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - number() | [dynamic()] is not compatible with number() - because - [dynamic()] is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/elab_clause.erl:93:17 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/error_messages.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/error_messages.pretty index ab48c38337..fab550d5e6 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/error_messages.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/error_messages.pretty @@ -15,17 +15,6 @@ Because in the expression's type: Context expects type: number() , ... } ------------------------------- Detailed message ------------------------------ - - #{bar := atom(), baz := atom()} is not compatible with foo_map() | #{foo => atom()} - because - #{bar := atom(), baz := atom()} is not compatible with foo_map() - because - #{bar := atom(), baz := atom()} is not compatible with #{bar => atom(), baz => number(), other => atom(), other2 => atom(), other3 => atom(), other4 => atom(), other5 => atom()} - because - at key `baz`: - #{bar := atom(), baz := atom()} is not compatible with #{bar => atom(), baz => number(), other => atom(), other2 => atom(), other3 => atom(), other4 => atom(), other5 => atom()} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/error_messages.erl:19:24 │ @@ -44,12 +33,6 @@ Because in the expression's type: The expected map has no corresponding key for: baz. ] ------------------------------- Detailed message ------------------------------ - - [#{bar => 'a' | 'b'} | #{baz => 'a' | 'b'}] is not compatible with [#{bar => 'a'}] - because - #{bar => 'a' | 'b'} | #{baz => 'a' | 'b'} is not compatible with #{bar => 'a'} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/error_messages.erl:22:22 │ @@ -68,15 +51,6 @@ Because in the expression's type: Differs from the expected type: binary() , ... } ------------------------------- Detailed message ------------------------------ - - #{'undefined' | binary() => atom()} is not compatible with #{binary() => atom()} - the default associations are not compatible - because - 'undefined' | binary() is not compatible with binary() - because - 'undefined' is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/error_messages.erl:27:32 │ @@ -89,17 +63,9 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/error_messages.erl:30:32 │ 30 │ no_record_conversion_2(Foo) -> Foo. - │ ^^^ - │ │ - │ Foo. + │ ^^^ Foo. Expression has type: #foo{} Context expected type: {binary(), atom(), atom()} - │ - - at tuple index 1: - {'foo', atom(), atom()} is not compatible with {binary(), atom(), atom()} - because - 'foo' is not compatible with binary() error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/error_messages.erl:33:27 @@ -118,13 +84,6 @@ Because in the expression's type: Context expects type: binary() , atom()} ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {'foo', atom(), atom()} is not compatible with {'foo', binary(), atom()} - because - atom() is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/error_messages.erl:37:31 │ @@ -143,18 +102,6 @@ Because in the expression's type: The expected map has no corresponding key for: large_map_key_a. , ... } ------------------------------- Detailed message ------------------------------ - - #{foo => #{large_map_key_a => 'large_map_val_a', large_map_key_b => 'large_map_val_b'}} is not compatible with #{foo => #{large_map_key_c => 'large_map_val_c'} | #{large_map_key_d => 'large_map_val_d'}} - because - at key `foo`: - #{foo => #{large_map_key_a => 'large_map_val_a', large_map_key_b => 'large_map_val_b'}} is not compatible with #{foo => #{large_map_key_c => 'large_map_val_c'} | #{large_map_key_d => 'large_map_val_d'}} - because - #{large_map_key_a => 'large_map_val_a', large_map_key_b => 'large_map_val_b'} is not compatible with #{large_map_key_c => 'large_map_val_c'} | #{large_map_key_d => 'large_map_val_d'} - because - #{large_map_key_a => 'large_map_val_a', large_map_key_b => 'large_map_val_b'} is not compatible with #{large_map_key_c => 'large_map_val_c'} - key `large_map_key_a` is declared in the former but not in the latter and the latter map has no default association - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/error_messages.erl:41:31 │ @@ -173,16 +120,4 @@ Because in the expression's type: The expected map has no corresponding key for: large_map_key_a. , ... } ------------------------------- Detailed message ------------------------------ - - #{foo => #{large_map_key_a => 'large_map_val_a', large_map_key_b => 'large_map_val_b'}} is not compatible with #{foo => #{large_map_key_c => 'large_map_val_c', large_map_key_d => 'large_map_val_d'} | 'any'} - because - at key `foo`: - #{foo => #{large_map_key_a => 'large_map_val_a', large_map_key_b => 'large_map_val_b'}} is not compatible with #{foo => #{large_map_key_c => 'large_map_val_c', large_map_key_d => 'large_map_val_d'} | 'any'} - because - #{large_map_key_a => 'large_map_val_a', large_map_key_b => 'large_map_val_b'} is not compatible with #{large_map_key_c => 'large_map_val_c', large_map_key_d => 'large_map_val_d'} | 'any' - because - #{large_map_key_a => 'large_map_val_a', large_map_key_b => 'large_map_val_b'} is not compatible with #{large_map_key_c => 'large_map_val_c', large_map_key_d => 'large_map_val_d'} - key `large_map_key_a` is declared in the former but not in the latter and the latter map has no default association - 8 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/format.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/format.pretty index f6d7701c2e..1a6ae465e8 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/format.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/format.pretty @@ -13,10 +13,4 @@ Because in the expression's type: Here the type is: [number() | io_lib:chars()] Context expects type: string() ------------------------------- Detailed message ------------------------------ - - io_lib:chars() is not compatible with string() - because - [number() | io_lib:chars()] is not compatible with string() - 1 ERROR diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/funs.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/funs.pretty index 589448836f..1e2c339ecc 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/funs.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/funs.pretty @@ -29,12 +29,6 @@ Because in the expression's type: Here the type is: number() Context expects type: [term()] ------------------------------- Detailed message ------------------------------ - - n() is not compatible with [term()] - because - number() is not compatible with [term()] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/funs.erl:63:9 │ @@ -50,12 +44,6 @@ Because in the expression's type: Here the type is: [dynamic()] Context expects type: number() ------------------------------- Detailed message ------------------------------ - - [dynamic()] is not compatible with n() - because - [dynamic()] is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/funs.erl:70:13 │ @@ -71,12 +59,6 @@ Because in the expression's type: Here the type is: binary() Context expects type: number() ------------------------------- Detailed message ------------------------------ - - binary() is not compatible with n() - because - binary() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/funs.erl:70:35 │ @@ -92,12 +74,6 @@ Because in the expression's type: Here the type is: number() Context expects type: atom() ------------------------------- Detailed message ------------------------------ - - n() is not compatible with atom() - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/funs.erl:77:5 │ @@ -116,14 +92,6 @@ Because in the expression's type: Context expects type: 'a' ] ------------------------------- Detailed message ------------------------------ - - [n()] is not compatible with ['a'] - because - n() is not compatible with 'a' - because - number() is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/funs.erl:90:18 │ @@ -139,12 +107,6 @@ Because in the expression's type: Here the type is: number() Context expects type: [term()] ------------------------------- Detailed message ------------------------------ - - n() is not compatible with [term()] - because - number() is not compatible with [term()] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/funs.erl:90:18 │ @@ -160,12 +122,6 @@ Because in the expression's type: Here the type is: [dynamic()] Context expects type: number() ------------------------------- Detailed message ------------------------------ - - [dynamic()] is not compatible with n() - because - [dynamic()] is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/funs.erl:90:23 │ @@ -181,12 +137,6 @@ Because in the expression's type: Here the type is: number() Context expects type: [term()] ------------------------------- Detailed message ------------------------------ - - n() is not compatible with [term()] - because - number() is not compatible with [term()] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/funs.erl:96:31 │ @@ -202,12 +152,6 @@ Because in the expression's type: Here the type is: number() Context expects type: atom() ------------------------------- Detailed message ------------------------------ - - n() is not compatible with atom() - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/funs.erl:97:9 │ @@ -223,12 +167,6 @@ Because in the expression's type: Here the type is: number() Context expects type: [term()] ------------------------------- Detailed message ------------------------------ - - n() is not compatible with [term()] - because - number() is not compatible with [term()] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/funs.erl:97:9 │ @@ -244,12 +182,6 @@ Because in the expression's type: Here the type is: [dynamic()] Context expects type: number() ------------------------------- Detailed message ------------------------------ - - [dynamic()] is not compatible with n() - because - [dynamic()] is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/funs.erl:97:14 │ @@ -265,12 +197,6 @@ Because in the expression's type: Here the type is: number() Context expects type: [term()] ------------------------------- Detailed message ------------------------------ - - n() is not compatible with [term()] - because - number() is not compatible with [term()] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/funs.erl:104:7 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/funs2.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/funs2.pretty index 7b20cba52c..f3a82abfd6 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/funs2.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/funs2.pretty @@ -19,14 +19,6 @@ Because in the expression's type: ] ] ------------------------------- Detailed message ------------------------------ - - [[[[]]]] is not compatible with [[[]]] - because - [[[]]] is not compatible with [[]] - because - [[]] is not compatible with [] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/funs2.erl:54:5 │ @@ -66,12 +58,6 @@ Because in the expression's type: Context expects type: atom() ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((atom()) -> atom()) - because - string() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/funs2.erl:101:9 │ @@ -90,14 +76,6 @@ Because in the expression's type: Differs from the expected type: atom() ) -> string()) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((dynamic() | fun((string()) -> atom())) -> dynamic() | fun((string()) -> atom())) - because - dynamic() | fun((string()) -> atom()) is not compatible with atom() - because - fun((string()) -> atom()) is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/funs2.erl:101:9 │ @@ -116,14 +94,6 @@ Because in the expression's type: Differs from the expected type: atom() ) -> string()) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((dynamic() | string() | fun((string()) -> atom())) -> dynamic() | string() | fun((string()) -> atom())) - because - dynamic() | string() | fun((string()) -> atom()) is not compatible with atom() - because - string() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/funs2.erl:152:27 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/generic_fun_application.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/generic_fun_application.pretty index c10af08c9d..ae03bc49c8 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/generic_fun_application.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/generic_fun_application.pretty @@ -31,12 +31,6 @@ Because in the expression's type: Context expects type: atom() ] ------------------------------- Detailed message ------------------------------ - - [number()] is not compatible with [atom()] - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:38:29 │ @@ -54,14 +48,6 @@ Because in the expression's type: Context expects type: atom() ] ------------------------------- Detailed message ------------------------------ - - [number()] is not compatible with [dynamic(atom())] - because - number() is not compatible with dynamic(atom()) - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:43:5 │ @@ -87,12 +73,6 @@ Because in the expression's type: Context expects type: B ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> pid()) is not compatible with fun((number()) -> B) - because - pid() is not compatible with B - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:43:18 │ @@ -111,14 +91,6 @@ Because in the expression's type: Differs from the expected type: atom() ) -> pid()) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> pid()) is not compatible with fun((dynamic() | number()) -> pid()) - because - dynamic() | number() is not compatible with atom() - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:47:33 │ @@ -136,12 +108,6 @@ Because in the expression's type: Context expects type: atom() ] ------------------------------- Detailed message ------------------------------ - - [number()] is not compatible with [atom()] - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:47:33 │ @@ -159,14 +125,6 @@ Because in the expression's type: Context expects type: atom() ] ------------------------------- Detailed message ------------------------------ - - [number()] is not compatible with [dynamic(atom())] - because - number() is not compatible with dynamic(atom()) - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:48:5 │ @@ -192,12 +150,6 @@ Because in the expression's type: Context expects type: B ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> pid()) is not compatible with fun((number()) -> B) - because - pid() is not compatible with B - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:52:22 │ @@ -216,14 +168,6 @@ Because in the expression's type: Differs from the expected type: atom() ) -> pid()) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> pid()) is not compatible with fun((dynamic() | number()) -> pid()) - because - dynamic() | number() is not compatible with atom() - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:53:5 │ @@ -272,10 +216,6 @@ Because in the expression's type: Context expects type: #{...} The expected map has no corresponding key for: true. ------------------------------- Detailed message ------------------------------ - -key `true` is declared in the former but not in the latter and the latter map has no default association - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:99:13 │ @@ -365,15 +305,6 @@ Because in the expression's type: Context expects type: T , ... } ------------------------------- Detailed message ------------------------------ - - #{a => number()} is not compatible with #{atom() => T} - because - #{a => number()} is not compatible with #{atom() => T} - key `a` is declared in the former but not in the latter and key `a` isn't compatible with the default association of the latter map - because - number() is not compatible with T - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:126:5 │ @@ -414,12 +345,6 @@ Because in the expression's type: However the following candidate: 'b' Differs from the expected type: 'a' ------------------------------- Detailed message ------------------------------ - - 'a' | 'b' is not compatible with 'a' - because - 'b' is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:157:5 │ @@ -444,12 +369,6 @@ Because in the expression's type: However the following candidate: 'b' Differs from the expected type: 'a' ------------------------------- Detailed message ------------------------------ - - 'a' | 'b' is not compatible with 'a' - because - 'b' is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:172:5 │ @@ -466,12 +385,6 @@ Because in the expression's type: However the following candidate: 'a' Differs from the expected type: 'b' ------------------------------- Detailed message ------------------------------ - - 'a' | 'b' is not compatible with 'b' - because - 'a' is not compatible with 'b' - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/generic_fun_application.erl:226:1 │ @@ -521,12 +434,6 @@ Because in the expression's type: Context expects type: X ) ------------------------------- Detailed message ------------------------------ - - fun(() -> A) is not compatible with fun(() -> X) - because - A is not compatible with X - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:276:5 │ @@ -567,12 +474,6 @@ Because in the expression's type: However the following candidate: Last Differs from the expected type: 'first' ------------------------------- Detailed message ------------------------------ - - 'first' | Last is not compatible with 'first' - because - Last is not compatible with 'first' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:296:5 │ @@ -589,12 +490,6 @@ Because in the expression's type: However the following candidate: T Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - atom() | T is not compatible with atom() - because - T is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:322:5 │ @@ -612,13 +507,6 @@ Because in the expression's type: Context expects type: pid() , pid()} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {number(), pid()} is not compatible with {pid(), number()} - because - number() is not compatible with pid() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:330:5 │ @@ -677,12 +565,6 @@ Because in the expression's type: Context expects type: fun((A) -> A) with 0 type parameters The number of type parameters doesn't match. ------------------------------- Detailed message ------------------------------ - - fun((Z) -> Z) | fun((dynamic()) -> dynamic()) is not compatible with fun((A) -> A) - because - fun((Z) -> Z) with 1 type parameter is not compatible with fun((A) -> A) with 0 type parameters - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:346:9 │ @@ -701,12 +583,6 @@ Because in the expression's type: Context expects type: fun((dynamic()) -> dynamic()) with 0 type parameters The number of type parameters doesn't match. ------------------------------- Detailed message ------------------------------ - - fun((Z) -> Z) | fun((dynamic()) -> dynamic()) is not compatible with fun((dynamic()) -> dynamic()) - because - fun((Z) -> Z) with 1 type parameter is not compatible with fun((dynamic()) -> dynamic()) with 0 type parameters - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:346:9 │ @@ -725,12 +601,6 @@ Because in the expression's type: Context expects type: fun((A) -> A) with 0 type parameters The number of type parameters doesn't match. ------------------------------- Detailed message ------------------------------ - - fun((Z) -> Z) | fun((dynamic()) -> dynamic()) is not compatible with fun((A) -> A) - because - fun((Z) -> Z) with 1 type parameter is not compatible with fun((A) -> A) with 0 type parameters - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:358:9 │ @@ -781,12 +651,6 @@ Because in the expression's type: Context expects type: fun((Z) -> Z) with 0 type parameters The number of type parameters doesn't match. ------------------------------- Detailed message ------------------------------ - - fun((Z) -> Z) | fun((dynamic()) -> dynamic()) is not compatible with fun((Z) -> Z) - because - fun((Z) -> Z) with 1 type parameter is not compatible with fun((Z) -> Z) with 0 type parameters - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:358:9 │ @@ -805,12 +669,6 @@ Because in the expression's type: Context expects type: fun((dynamic()) -> dynamic()) with 0 type parameters The number of type parameters doesn't match. ------------------------------- Detailed message ------------------------------ - - fun((Z) -> Z) | fun((dynamic()) -> dynamic()) is not compatible with fun((dynamic()) -> dynamic()) - because - fun((Z) -> Z) with 1 type parameter is not compatible with fun((dynamic()) -> dynamic()) with 0 type parameters - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:358:9 │ @@ -829,12 +687,6 @@ Because in the expression's type: Context expects type: fun((Z) -> Z) with 0 type parameters The number of type parameters doesn't match. ------------------------------- Detailed message ------------------------------ - - fun((Z) -> Z) | fun((dynamic()) -> dynamic()) is not compatible with fun((Z) -> Z) - because - fun((Z) -> Z) with 1 type parameter is not compatible with fun((Z) -> Z) with 0 type parameters - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:432:18 │ @@ -875,10 +727,6 @@ Because in the expression's type: Context expects type: #{a := ..., ...} The type of the expression is missing the following required keys: a. ------------------------------- Detailed message ------------------------------ - -key `a` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:441:16 │ @@ -895,10 +743,6 @@ Because in the expression's type: Context expects type: #{a := ..., ...} The type of the expression is missing the following required keys: a. ------------------------------- Detailed message ------------------------------ - -key `a` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:446:15 │ @@ -916,12 +760,6 @@ Because in the expression's type: Context expects type: T ) ------------------------------- Detailed message ------------------------------ - - fun((#{a => number()}) -> number()) is not compatible with fun((#{{T} => T}) -> T) - because - number() is not compatible with T - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:446:15 │ @@ -940,15 +778,6 @@ Because in the expression's type: The expected map has no default association while the type of the expression has one. ) -> number()) ------------------------------- Detailed message ------------------------------ - - fun((#{a => number()}) -> number()) is not compatible with fun((#{{number()} => number()}) -> number()) - because - #{{number()} => number()} is not compatible with #{a => number()} - because - #{{number()} => number()} is not compatible with #{a => number()} - the latter map has no default association while the first map has one - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:471:24 │ @@ -966,12 +795,6 @@ Because in the expression's type: Context expects type: 'c' ) ------------------------------- Detailed message ------------------------------ - - fun(('a') -> 'b') is not compatible with fun(('a') -> 'c') - because - 'b' is not compatible with 'c' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:478:17 │ @@ -988,10 +811,6 @@ Because in the expression's type: Context expects type: #{a := ..., ...} The type of the expression is missing the following required keys: a. ------------------------------- Detailed message ------------------------------ - -key `a` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:478:17 │ @@ -1008,10 +827,6 @@ Because in the expression's type: Context expects type: #{a := ..., ...} The type of the expression is missing the following required keys: a. ------------------------------- Detailed message ------------------------------ - -key `a` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:500:36 │ @@ -1048,16 +863,6 @@ Because in the expression's type: ) -> 'ok') ) -> 'ok') ------------------------------- Detailed message ------------------------------ - - contravar(contravar('a' | 'b')) is not compatible with contravar(contravar('a')) - because - fun((contravar('a' | 'b')) -> 'ok') is not compatible with contravar(contravar('a')) - because - fun((contravar('a' | 'b')) -> 'ok') is not compatible with fun((contravar('a')) -> 'ok') - because - contravar('a') is not compatible with contravar('a' | 'b') - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:563:5 │ @@ -1075,14 +880,6 @@ Because in the expression's type: Context expects type: 'a' ) -> 'a') ------------------------------- Detailed message ------------------------------ - - invar('a') is not compatible with fun((atom()) -> atom()) - because - fun(('a') -> 'a') is not compatible with fun((atom()) -> atom()) - because - atom() is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:569:5 │ @@ -1100,16 +897,6 @@ Because in the expression's type: Context expects type: 'a' ) -> 'a') ------------------------------- Detailed message ------------------------------ - - invar('a') is not compatible with invar(atom()) - because - fun(('a') -> 'a') is not compatible with invar(atom()) - because - fun(('a') -> 'a') is not compatible with fun((atom()) -> atom()) - because - atom() is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:585:5 │ @@ -1125,12 +912,6 @@ Because in the expression's type: Here the type is: 'a' Context expects type: fun(('a') -> 'a') ------------------------------- Detailed message ------------------------------ - - 'a' is not compatible with invar('a') - because - 'a' is not compatible with fun(('a') -> 'a') - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/generic_fun_application.erl:625:1 │ @@ -1175,10 +956,6 @@ Because in the expression's type: Context expects type: #{...} The expected map has no corresponding key for: extra. ------------------------------- Detailed message ------------------------------ - -key `extra` is declared in the former but not in the latter and the latter map has no default association - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:721:25 │ @@ -1205,14 +982,6 @@ Because in the expression's type: Differs from the expected type: 'a' ) -> 'a') ------------------------------- Detailed message ------------------------------ - - fun(('a') -> 'a') is not compatible with fun(('a' | 'b') -> 'a' | 'b') - because - 'a' | 'b' is not compatible with 'a' - because - 'b' is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generic_fun_application.erl:752:32 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/generics_with_unions.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/generics_with_unions.pretty index 53cd1350ec..9c03aa9a60 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/generics_with_unions.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/generics_with_unions.pretty @@ -16,15 +16,6 @@ Because in the expression's type: Differs from the expected type: T , T | U} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {T | U, T | U} is not compatible with {T, U} - because - T | U is not compatible with T - because - U is not compatible with T - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generics_with_unions.erl:70:19 │ @@ -62,12 +53,6 @@ Because in the expression's type: Here the type is: [prop('a', number())] Context expects type: 'wrong_ret' ------------------------------- Detailed message ------------------------------ - - props('a', number()) is not compatible with 'wrong_ret' - because - [prop('a', number())] is not compatible with 'wrong_ret' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generics_with_unions.erl:217:19 │ @@ -92,12 +77,6 @@ Because in the expression's type: However the following candidate: [T] Differs from the expected type: T ------------------------------- Detailed message ------------------------------ - - [T] | T is not compatible with T - because - [T] is not compatible with T - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generics_with_unions.erl:221:17 │ @@ -114,12 +93,6 @@ Because in the expression's type: However the following candidate: [T] Differs from the expected type: T ------------------------------- Detailed message ------------------------------ - - T | [T] is not compatible with T - because - [T] is not compatible with T - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generics_with_unions.erl:222:19 │ @@ -171,14 +144,6 @@ Because in the expression's type: Context expects type: K ] ------------------------------- Detailed message ------------------------------ - - [K] | [[K]] is not compatible with [K] - because - [[K]] is not compatible with [K] - because - [K] is not compatible with K - error: ambiguous_union (See https://fb.me/eqwalizer_errors#ambiguous_union) ┌─ check/src/generics_with_unions.erl:332:31 │ @@ -205,17 +170,6 @@ Because in the expression's type: } ) -> 'ok') ------------------------------- Detailed message ------------------------------ - - query() is not compatible with fun(({'i', 'atom'}) -> 'ok') - because - fun(({'a', atom()} | {'b', binary()} | {'i', number()}) -> 'ok') is not compatible with fun(({'i', 'atom'}) -> 'ok') - because - {'i', 'atom'} is not compatible with {'a', atom()} | {'b', binary()} | {'i', number()} - because - at tuple index 2: - {'i', 'atom'} is not compatible with {'i', number()} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/generics_with_unions.erl:392:17 │ @@ -235,15 +189,4 @@ Because in the expression's type: } ) -> 'ok') ------------------------------- Detailed message ------------------------------ - - query() is not compatible with fun(({'a', number()}) -> 'ok') - because - fun(({'a', atom()} | {'b', binary()} | {'i', number()}) -> 'ok') is not compatible with fun(({'a', number()}) -> 'ok') - because - {'a', number()} is not compatible with {'a', atom()} | {'b', binary()} | {'i', number()} - because - at tuple index 2: - {'a', number()} is not compatible with {'a', atom()} - 16 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/gradual_bounded.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/gradual_bounded.pretty index bbd9109d68..1987f06692 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/gradual_bounded.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/gradual_bounded.pretty @@ -14,12 +14,6 @@ Because in the expression's type: However the following candidate: 'b' Differs from the expected type: 'c' ------------------------------- Detailed message ------------------------------ - - dyn('a') | 'b' is not compatible with 'c' - because - 'b' is not compatible with 'c' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/gradual_bounded.erl:25:26 │ @@ -35,16 +29,6 @@ Because in the expression's type: Here the type is: 'a' Context expects type: 'b' ------------------------------- Detailed message ------------------------------ - - 'a' is not compatible with dyn('b') - because - 'a' is not compatible with dynamic('b') - because - 'a' is not compatible with dynamic('b') - because - 'a' is not compatible with 'b' - error: reveal_type (See https://fb.me/eqwalizer_errors#reveal_type) ┌─ check/src/gradual_bounded.erl:44:27 │ @@ -85,12 +69,6 @@ Because in the expression's type: However the following candidate: 'test' Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - 'test' | number() is not compatible with number() - because - 'test' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/gradual_bounded.erl:89:5 │ @@ -107,10 +85,4 @@ Because in the expression's type: However the following candidate: 'test' Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - 'test' | dyn(number()) is not compatible with number() - because - 'test' is not compatible with number() - 8 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/gradual_complex_types.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/gradual_complex_types.pretty index b7111f9239..a7a81c8fb4 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/gradual_complex_types.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/gradual_complex_types.pretty @@ -13,12 +13,6 @@ Because in the expression's type: Here the type is: 'undefined' Context expects type: #{...} ------------------------------- Detailed message ------------------------------ - - 'undefined' is not compatible with complex_map() - because - 'undefined' is not compatible with #{id := number(), {secret, id} => number(), atom() => term()} - error: reveal_type (See https://fb.me/eqwalizer_errors#reveal_type) ┌─ check/src/gradual_complex_types.erl:29:25 │ @@ -46,12 +40,6 @@ Because in the expression's type: Here the type is: #{...} Context expects type: [T] ------------------------------- Detailed message ------------------------------ - - complex_map() is not compatible with [T] - because - #{id := number(), {secret, id} => number(), atom() => term()} is not compatible with [T] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/gradual_complex_types.erl:48:16 │ @@ -67,12 +55,6 @@ Because in the expression's type: Here the type is: #{...} Context expects type: [dynamic()] ------------------------------- Detailed message ------------------------------ - - complex_map() is not compatible with [dynamic()] - because - #{id := number(), {secret, id} => number(), atom() => term()} is not compatible with [dynamic()] - error: reveal_type (See https://fb.me/eqwalizer_errors#reveal_type) ┌─ check/src/gradual_complex_types.erl:55:25 │ @@ -110,11 +92,4 @@ Because in the expression's type: Context expects type: number() } ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {dyn_map(), 'ok'} is not compatible with {#{a => 'atom'}, number()} - because - 'ok' is not compatible with number() - 9 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/gradual_custom.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/gradual_custom.pretty index 34e16f1479..065a377d0d 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/gradual_custom.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/gradual_custom.pretty @@ -100,12 +100,6 @@ Because in the expression's type: However the following candidate: 'a' Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - dynamic() | 'a' is not compatible with number() - because - 'a' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/gradual_custom.erl:120:3 │ @@ -202,12 +196,6 @@ Because in the expression's type: Context expects type: 'ok' No candidate of the expression's type matches the expected type. ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with 'ok' - because - string() | binary() is not compatible with 'ok' - error: reveal_type (See https://fb.me/eqwalizer_errors#reveal_type) ┌─ check/src/gradual_custom.erl:210:25 │ @@ -230,12 +218,6 @@ Because in the expression's type: Context expects type: 'ok' No candidate of the expression's type matches the expected type. ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with 'ok' - because - string() | binary() is not compatible with 'ok' - error: reveal_type (See https://fb.me/eqwalizer_errors#reveal_type) ┌─ check/src/gradual_custom.erl:217:25 │ @@ -258,12 +240,6 @@ Because in the expression's type: Context expects type: 'ok' No candidate of the expression's type matches the expected type. ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with 'ok' - because - string() | binary() is not compatible with 'ok' - error: reveal_type (See https://fb.me/eqwalizer_errors#reveal_type) ┌─ check/src/gradual_custom.erl:224:25 │ @@ -286,12 +262,6 @@ Because in the expression's type: Context expects type: 'ok' No candidate of the expression's type matches the expected type. ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with 'ok' - because - string() | binary() is not compatible with 'ok' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/gradual_custom.erl:245:19 │ @@ -308,12 +278,6 @@ Because in the expression's type: However the following candidate: 'undefined' Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - number() | 'undefined' is not compatible with number() - because - 'undefined' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/gradual_custom.erl:257:19 │ @@ -330,12 +294,6 @@ Because in the expression's type: However the following candidate: atom() Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - number() | atom() | binary() is not compatible with number() - because - atom() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/gradual_custom.erl:263:19 │ @@ -352,12 +310,6 @@ Because in the expression's type: However the following candidate: atom() Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - number() | dynamic() | number() | atom() | dynamic() is not compatible with number() - because - atom() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/gradual_custom.erl:269:19 │ @@ -374,12 +326,6 @@ Because in the expression's type: However the following candidate: atom() Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - number() | dynamic() | atom() | dynamic() is not compatible with number() - because - atom() is not compatible with number() - error: reveal_type (See https://fb.me/eqwalizer_errors#reveal_type) ┌─ check/src/gradual_custom.erl:277:25 │ @@ -402,12 +348,6 @@ Because in the expression's type: However the following candidate: {none()} Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - dynamic() | {none()} is not compatible with number() - because - {none()} is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/gradual_custom.erl:325:13 │ @@ -424,14 +364,6 @@ Because in the expression's type: However the following candidate: 'undefined' Differs from the expected type: 'foo' | 'bar' | binary() ------------------------------- Detailed message ------------------------------ - - 'bar' | 'undefined' | 'foo' is not compatible with 'foo' | 'bar' | binary() - because - 'undefined' is not compatible with 'foo' | 'bar' | binary() - because - 'undefined' is not compatible with 'foo' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/gradual_custom.erl:343:3 │ @@ -448,10 +380,4 @@ Because in the expression's type: However the following candidate: 'bar' Differs from the expected type: 'foo' ------------------------------- Detailed message ------------------------------ - - 'foo' | 'bar' is not compatible with 'foo' - because - 'bar' is not compatible with 'foo' - 37 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/gradual_maybe.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/gradual_maybe.pretty index 5587da0d7e..35a509f2f6 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/gradual_maybe.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/gradual_maybe.pretty @@ -62,14 +62,6 @@ Because in the expression's type: However the following candidate: 'a' Differs from the expected type: 'b' | 'c' ------------------------------- Detailed message ------------------------------ - - 'a' | 'b' | 'c' is not compatible with 'b' | 'c' - because - 'a' is not compatible with 'b' | 'c' - because - 'a' is not compatible with 'b' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/gradual_maybe.erl:147:14 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/gradual_misc.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/gradual_misc.pretty index 107e1b6e30..28618b5f77 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/gradual_misc.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/gradual_misc.pretty @@ -16,16 +16,6 @@ Because in the expression's type: Differs from the expected type: 'a' ) -> 'ok') ------------------------------- Detailed message ------------------------------ - - opaque:contravariant('a') is not compatible with opaque:contravariant('a' | 'b') - because - fun(('a') -> 'ok') is not compatible with opaque:contravariant('a' | 'b') - because - fun(('a') -> 'ok') is not compatible with fun(('a' | 'b') -> 'ok') - because - 'a' | 'b' is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/gradual_misc.erl:38:5 │ @@ -45,13 +35,8 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/gradual_misc.erl:46:41 │ 46 │ refine_tuple_neg(T) when is_tuple(T) -> T; - │ ^ - │ │ - │ T. + │ ^ T. Expression has type: {'b', 'c'} Context expected type: 'a' | {none()} - │ - -expected union does not contain any tuple type of size 2 4 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/guard_b_connections.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/guard_b_connections.pretty index 88bcfb1267..74cd6b4ca4 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/guard_b_connections.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/guard_b_connections.pretty @@ -14,14 +14,6 @@ Because in the expression's type: However the following candidate: #r2{} Differs from the expected type: #r1{} ------------------------------- Detailed message ------------------------------ - - #r1{} | #r2{} is not compatible with r1() - because - #r1{} | #r2{} is not compatible with #r1{} - because - #r2{} is not compatible with #r1{} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/guard_b_connections.erl:27:3 │ @@ -38,14 +30,6 @@ Because in the expression's type: However the following candidate: #r2{} Differs from the expected type: #r1{} ------------------------------- Detailed message ------------------------------ - - #r1{} | #r2{} is not compatible with r1() - because - #r1{} | #r2{} is not compatible with #r1{} - because - #r2{} is not compatible with #r1{} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/guard_b_connections.erl:32:3 │ @@ -62,14 +46,6 @@ Because in the expression's type: However the following candidate: #r2{} Differs from the expected type: #r1{} ------------------------------- Detailed message ------------------------------ - - #r1{} | #r2{} is not compatible with r1() - because - #r1{} | #r2{} is not compatible with #r1{} - because - #r2{} is not compatible with #r1{} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/guard_b_connections.erl:38:3 │ @@ -86,14 +62,6 @@ Because in the expression's type: However the following candidate: #r2{} Differs from the expected type: #r1{} ------------------------------- Detailed message ------------------------------ - - #r1{} | #r2{} is not compatible with r1() - because - #r1{} | #r2{} is not compatible with #r1{} - because - #r2{} is not compatible with #r1{} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/guard_b_connections.erl:44:3 │ @@ -110,12 +78,4 @@ Because in the expression's type: However the following candidate: #r2{} Differs from the expected type: #r1{} ------------------------------- Detailed message ------------------------------ - - #r1{} | #r2{} is not compatible with r1() - because - #r1{} | #r2{} is not compatible with #r1{} - because - #r2{} is not compatible with #r1{} - 5 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/guards_logic.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/guards_logic.pretty index c287958c68..bc1b2fff4a 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/guards_logic.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/guards_logic.pretty @@ -30,14 +30,6 @@ Because in the expression's type: However the following candidate: pid() Differs from the expected type: number() | atom() ------------------------------- Detailed message ------------------------------ - - number() | atom() | pid() is not compatible with number() | atom() - because - pid() is not compatible with number() | atom() - because - pid() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/guards_logic.erl:73:5 │ @@ -55,13 +47,6 @@ Because in the expression's type: Context expects type: number() , term()} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {term(), term()} is not compatible with {number(), number()} - because - term() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/guards_logic.erl:101:46 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/hints.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/hints.pretty index 2e9f03966d..f990884695 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/hints.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/hints.pretty @@ -38,12 +38,6 @@ Because in the expression's type: However the following candidate: term() Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - term() | 'undefined' is not compatible with atom() - because - term() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/hints.erl:52:5 │ @@ -60,12 +54,6 @@ Because in the expression's type: However the following candidate: term() Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - term() | 'undefined' is not compatible with atom() - because - term() is not compatible with atom() - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/hints.erl:55:1 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/iolists.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/iolists.pretty index bf9d6e219b..00e34f9cb8 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/iolists.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/iolists.pretty @@ -23,14 +23,6 @@ Because in the expression's type: Differs from the expected type: binary() ] ------------------------------- Detailed message ------------------------------ - - [atom() | binary()] is not compatible with [binary()] - because - atom() | binary() is not compatible with binary() - because - atom() is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/iolists.erl:55:22 │ @@ -49,14 +41,6 @@ Because in the expression's type: Differs from the expected type: atom() ] ------------------------------- Detailed message ------------------------------ - - [atom() | binary()] is not compatible with [atom()] - because - atom() | binary() is not compatible with atom() - because - binary() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/iolists.erl:60:27 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/lists_tests.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/lists_tests.pretty index d0768e5669..4b2ec521dd 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/lists_tests.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/lists_tests.pretty @@ -16,16 +16,6 @@ Because in the expression's type: Differs from the expected type: atom() ] ------------------------------- Detailed message ------------------------------ - - [atom() | binary()] is not compatible with [atom()] | [binary()] - because - [atom() | binary()] is not compatible with [atom()] - because - atom() | binary() is not compatible with atom() - because - binary() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/lists_tests.erl:16:28 │ @@ -44,14 +34,4 @@ Because in the expression's type: Differs from the expected type: atom() ] ------------------------------- Detailed message ------------------------------ - - [atom() | binary()] is not compatible with [atom()] | [binary()] - because - [atom() | binary()] is not compatible with [atom()] - because - atom() | binary() is not compatible with atom() - because - binary() is not compatible with atom() - 2 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/misc.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/misc.pretty index 45a1d69e57..ec963a6249 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/misc.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/misc.pretty @@ -127,12 +127,6 @@ Because in the expression's type: Context expects type: atom() ] ------------------------------- Detailed message ------------------------------ - - [number()] is not compatible with [atom()] - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/misc.erl:116:21 │ @@ -150,12 +144,6 @@ Because in the expression's type: Context expects type: atom() ] ------------------------------- Detailed message ------------------------------ - - [number()] is not compatible with [atom()] - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/misc.erl:120:18 │ @@ -207,12 +195,6 @@ Because in the expression's type: Context expects type: number() ] ------------------------------- Detailed message ------------------------------ - - [atom()] is not compatible with [number()] - because - atom() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/misc.erl:143:29 │ @@ -323,31 +305,17 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/misc.erl:352:22 │ 352 │ catch test69_pos(atom). - │ ^^^^ - │ │ - │ 'atom'. + │ ^^^^ 'atom'. Expression has type: 'atom' Context expected type: [atom()] | [number()] - │ - - 'atom' is not compatible with [atom()] | [number()] - because - 'atom' is not compatible with [atom()] error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/misc.erl:357:22 │ 357 │ catch test69_pos(atom). - │ ^^^^ - │ │ - │ 'atom'. + │ ^^^^ 'atom'. Expression has type: 'atom' Context expected type: [atom()] | [number()] - │ - - 'atom' is not compatible with [atom()] | [number()] - because - 'atom' is not compatible with [atom()] error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/misc.erl:404:11 @@ -393,17 +361,6 @@ Because in the expression's type: ] , [atom() | number()]} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {[number() | atom()], [atom() | number()]} is not compatible with {[atom()], [number()]} - because - [number() | atom()] is not compatible with [atom()] - because - number() | atom() is not compatible with atom() - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/misc.erl:461:5 │ @@ -424,17 +381,6 @@ Because in the expression's type: ] , [atom() | number()]} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {[atom() | number()], [atom() | number()]} is not compatible with {[atom()] | [number()], [atom()] | [number()]} - because - [atom() | number()] is not compatible with [atom()] | [number()] - because - [atom() | number()] is not compatible with [atom()] - because - atom() | number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/misc.erl:465:12 │ @@ -509,16 +455,6 @@ Because in the expression's type: Differs from the expected type: atom() | number() ] ------------------------------- Detailed message ------------------------------ - - [atom() | binary()] is not compatible with [atom() | number()] - because - atom() | binary() is not compatible with atom() | number() - because - binary() is not compatible with atom() | number() - because - binary() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/misc.erl:575:14 │ @@ -552,12 +488,6 @@ Because in the expression's type: Context expects type: [A] ] ------------------------------- Detailed message ------------------------------ - - [A] is not compatible with [[A]] - because - A is not compatible with [A] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/misc.erl:611:21 │ @@ -752,13 +682,6 @@ Because in the expression's type: Context expects type: 'ok' } ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {none(), 'err'} is not compatible with {'ok', 'ok'} - because - 'err' is not compatible with 'ok' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/misc.erl:741:5 │ @@ -775,16 +698,6 @@ Because in the expression's type: However the following candidate: 'v2_op' Differs from the expected type: 'stuff1' | 'v0_op2' | 'stuff2' | 'v0_op1' | 'v1_op2' | ... ------------------------------- Detailed message ------------------------------ - - v2_op() is not compatible with v1_op() - because - 'v2_op' | v1_op() is not compatible with v1_op() - because - 'v2_op' | v1_op() is not compatible with 'v1_op1' | 'v1_op2' | stuff() | v0_op() - because - 'v2_op' is not compatible with 'v1_op1' | 'v1_op2' | stuff() | v0_op() - error: type_alias_is_non_productive (See https://fb.me/eqwalizer_errors#type_alias_is_non_productive) ┌─ check/src/misc.erl:760:1 │ @@ -834,16 +747,6 @@ Because in the expression's type: Context expects type: #set{} | #{term() => []} No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - - misc:set() is not compatible with sets:set() - because - [] is not compatible with sets:set() - because - [] is not compatible with sets:set(term()) - because - [] is not compatible with #set{} | #{term() => []} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/misc.erl:850:5 │ @@ -861,13 +764,6 @@ Because in the expression's type: Context expects type: pid() } ------------------------------- Detailed message ------------------------------ - - at tuple index 3: - {'ok', 'lists', number()} is not compatible with {'ok', atom(), pid()} - because - number() is not compatible with pid() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/misc.erl:860:5 │ @@ -907,12 +803,6 @@ Because in the expression's type: Here the type is: {binary()} Context expects type: [binary()] ------------------------------- Detailed message ------------------------------ - - {binary()} is not compatible with erlang:iovec() - because - {binary()} is not compatible with [binary()] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/misc.erl:949:15 │ @@ -939,14 +829,6 @@ Because in the expression's type: No candidate of the expression's type matches the expected type. ] ------------------------------- Detailed message ------------------------------ - - ['MM' | 'MS' | 'EE' | 'MA' | 'GE'] is not compatible with [erlang:priority_level()] - because - 'MM' | 'MS' | 'EE' | 'MA' | 'GE' is not compatible with erlang:priority_level() - because - 'MM' | 'MS' | 'EE' | 'MA' | 'GE' is not compatible with 'low' | 'normal' | 'high' | 'max' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/misc.erl:971:5 │ @@ -962,12 +844,6 @@ Because in the expression's type: Here the type is: {number(), number(), number()} Context expects type: atom() ------------------------------- Detailed message ------------------------------ - - erlang:timestamp() is not compatible with atom() - because - {number(), number(), number()} is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/misc.erl:987:12 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/neg.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/neg.pretty index 55ce8bf6f5..585b5f8642 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/neg.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/neg.pretty @@ -39,13 +39,6 @@ Because in the expression's type: Context expects type: atom() } ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {'ok', term()} is not compatible with {atom(), atom()} - because - term() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/neg.erl:27:21 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/numbers.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/numbers.pretty index 8760657fa4..8c78c1ffbe 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/numbers.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/numbers.pretty @@ -150,12 +150,6 @@ Because in the expression's type: However the following candidate: 'a' Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - 'a' | number() is not compatible with number() - because - 'a' is not compatible with number() - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/numbers.erl:538:1 │ @@ -300,13 +294,6 @@ Because in the expression's type: Context expects type: 'ok' } ------------------------------- Detailed message ------------------------------ - - at tuple index 5: - {number(), number(), number(), number(), 'error'} is not compatible with {number(), number(), number(), number(), 'ok'} - because - 'error' is not compatible with 'ok' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/numbers.erl:658:3 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/opaque.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/opaque.pretty index 765efea8fe..5c207ff164 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/opaque.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/opaque.pretty @@ -16,16 +16,6 @@ Because in the expression's type: Context expects type: #{...} The expected map has no corresponding key for: {ok, error}. ------------------------------- Detailed message ------------------------------ - - sets:set({'ok', 'error'}) is not compatible with sets:set({'ok', 'ok'}) - because - #set{} | #{{ok, error} => []} is not compatible with sets:set({'ok', 'ok'}) - because - #set{} | #{{ok, error} => []} is not compatible with #set{} | #{{ok, ok} => []} - because - #{{ok, error} => []} is not compatible with #set{} | #{{ok, ok} => []} - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/opaque.erl:29:1 │ @@ -47,12 +37,6 @@ Because in the expression's type: Here the type is: {'ok'} Context expects type: none() ------------------------------- Detailed message ------------------------------ - - misc:o() is not compatible with none() - because - {'ok'} is not compatible with none() - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/opaque.erl:86:1 │ @@ -93,14 +77,6 @@ Because in the expression's type: However the following candidate: 'a' Differs from the expected type: {'ok'} ------------------------------- Detailed message ------------------------------ - - misc:o() | 'a' is not compatible with misc:o() - because - misc:o() | 'a' is not compatible with {'ok'} - because - 'a' is not compatible with {'ok'} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/opaque.erl:135:18 │ @@ -117,14 +93,6 @@ Because in the expression's type: However the following candidate: 'a' Differs from the expected type: {'ok'} ------------------------------- Detailed message ------------------------------ - - misc:o() | 'a' is not compatible with misc:o() - because - misc:o() | 'a' is not compatible with {'ok'} - because - 'a' is not compatible with {'ok'} - error: type_alias_is_non_productive (See https://fb.me/eqwalizer_errors#type_alias_is_non_productive) ┌─ check/src/opaque.erl:150:1 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/other.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/other.pretty index b75aabcf84..da1b4e48cb 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/other.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/other.pretty @@ -13,12 +13,6 @@ Because in the expression's type: Here the type is: term() Context expects type: #{...} ------------------------------- Detailed message ------------------------------ - - term() is not compatible with logger:metadata() - because - term() is not compatible with #{domain => [atom()], file => file:filename(), gl => pid(), line => number(), mfa => {atom(), atom(), number()}, pid => pid(), report_cb => logger:report_cb(), time => logger:timestamp(), atom() => term()} - error: reveal_type (See https://fb.me/eqwalizer_errors#reveal_type) ┌─ check/src/other.erl:59:27 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/otp28.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/otp28.pretty index 026d55c435..5113f80c15 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/otp28.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/otp28.pretty @@ -14,14 +14,6 @@ Because in the expression's type: Context expects type: 'ok' | 'error' No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - - 'warning' is not compatible with foo() - because - 'warning' is not compatible with 'ok' | 'error' - because - 'warning' is not compatible with 'ok' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/otp28.erl:28:5 │ @@ -39,12 +31,6 @@ Because in the expression's type: Context expects type: atom() ] ------------------------------- Detailed message ------------------------------ - - [number()] is not compatible with [atom()] - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/otp28.erl:40:5 │ @@ -62,12 +48,6 @@ Because in the expression's type: Context expects type: pid() ] ------------------------------- Detailed message ------------------------------ - - [binary()] is not compatible with [pid()] - because - binary() is not compatible with pid() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/otp28.erl:52:5 │ @@ -95,15 +75,6 @@ Because in the expression's type: } ] ------------------------------- Detailed message ------------------------------ - - [{atom(), binary()}] is not compatible with [{atom(), atom()}] - because - at tuple index 2: - {atom(), binary()} is not compatible with {atom(), atom()} - because - binary() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/otp28.erl:68:5 │ @@ -121,13 +92,6 @@ Because in the expression's type: Context expects type: atom() , ... } ------------------------------- Detailed message ------------------------------ - - #{atom() => binary()} is not compatible with #{atom() => atom()} - the default associations are not compatible - because - binary() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/otp28.erl:76:5 │ @@ -147,13 +111,4 @@ Because in the expression's type: , atom()} ] ------------------------------- Detailed message ------------------------------ - - [{atom(), binary(), binary(), atom()}] is not compatible with [{atom(), binary(), atom(), binary()}] - because - at tuple index 3: - {atom(), binary(), binary(), atom()} is not compatible with {atom(), binary(), atom(), binary()} - because - binary() is not compatible with atom() - 7 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/otp_opaques.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/otp_opaques.pretty index b41039f95b..59bd5e6099 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/otp_opaques.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/otp_opaques.pretty @@ -17,17 +17,6 @@ Because in the expression's type: , term(), term()} } ------------------------------- Detailed message ------------------------------ - - gb_sets:set(atom()) is not compatible with gb_sets:set(number()) - because - {number(), gb_sets:gb_set_node(atom())} is not compatible with gb_sets:set(number()) - because - at tuple index 2: - {number(), gb_sets:gb_set_node(atom())} is not compatible with {number(), gb_sets:gb_set_node(number())} - because - gb_sets:gb_set_node(atom()) is not compatible with gb_sets:gb_set_node(number()) - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/otp_opaques.erl:42:5 │ @@ -45,12 +34,6 @@ Because in the expression's type: Context expects type: A ] ------------------------------- Detailed message ------------------------------ - - [[A]] is not compatible with [A] - because - [A] is not compatible with A - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/otp_opaques.erl:99:17 │ @@ -67,14 +50,6 @@ Because in the expression's type: Context expects type: #set{} | #{a => []} No candidate of the expression's type matches the expected type. ------------------------------- Detailed message ------------------------------ - - maps:iterator('k', 'v') is not compatible with sets:set('a') - because - {'k', 'v', maps:iterator('k', 'v')} | 'none' | [number()] | [['k']] is not compatible with sets:set('a') - because - {'k', 'v', maps:iterator('k', 'v')} | 'none' | [number()] | [['k']] is not compatible with #set{} | #{a => []} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/otp_opaques.erl:102:13 │ @@ -99,14 +74,6 @@ Because in the expression's type: Context expects type: #set{} | #{a => []} No candidate of the expression's type matches the expected type. ------------------------------- Detailed message ------------------------------ - - maps:iterator('k', 'v') is not compatible with sets:set('a') - because - {'k', 'v', maps:iterator('k', 'v')} | 'none' | [number()] | [['k']] is not compatible with sets:set('a') - because - {'k', 'v', maps:iterator('k', 'v')} | 'none' | [number()] | [['k']] is not compatible with #set{} | #{a => []} - error: expected_fun_type (See https://fb.me/eqwalizer_errors#expected_fun_type) ┌─ check/src/otp_opaques.erl:124:5 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/overloaded.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/overloaded.pretty index 117ed8b349..aa87b295ad 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/overloaded.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/overloaded.pretty @@ -30,26 +30,13 @@ Because in the expression's type: Context expects type: 'b' ) ------------------------------- Detailed message ------------------------------ - - fun(('a') -> 'z') is not compatible with fun(('a') -> 'b') | fun(('a') -> 'c') - because - fun(('a') -> 'z') is not compatible with fun(('a') -> 'b') - because - 'z' is not compatible with 'b' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/overloaded.erl:126:15 │ 126 │ Res = bar({fun(a) -> a end}), - │ ^^^^^^^^^^^^^^^^^ - │ │ - │ {fun}. + │ ^^^^^^^^^^^^^^^^^ {fun}. Expression has type: {fun((dynamic()) -> dynamic())} Context expected type: fun(('a') -> 'b') | fun(('a') -> 'c') - │ - -expected union does not contain any tuple type of size 1 error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/overloaded.erl:198:37 @@ -107,12 +94,6 @@ Because in the expression's type: However the following candidate: {} Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - number() | {} is not compatible with number() - because - {} is not compatible with number() - error: unbound_type_var (See https://fb.me/eqwalizer_errors#unbound_type_var) ┌─ check/src/overloaded.erl:261:1 │ @@ -130,16 +111,9 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/overloaded.erl:269:14 │ 269 │ _ = swap(""), - │ ^^ - │ │ - │ string_lit. + │ ^^ string_lit. Expression has type: [] Context expected type: atom() | binary() - │ - - [] is not compatible with atom() | binary() - because - [] is not compatible with atom() error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/overloaded.erl:290:5 diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/overloaded_specs_union.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/overloaded_specs_union.pretty index 3483635724..6687ec3321 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/overloaded_specs_union.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/overloaded_specs_union.pretty @@ -14,12 +14,6 @@ Because in the expression's type: However the following candidate: pid() Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - atom() | pid() is not compatible with atom() - because - pid() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/overloaded_specs_union.erl:19:29 │ @@ -36,12 +30,4 @@ Because in the expression's type: However the following candidate: pid() Differs from the expected type: atom() | binary() ------------------------------- Detailed message ------------------------------ - - atom() | binary() | pid() is not compatible with atom() | binary() - because - pid() is not compatible with atom() | binary() - because - pid() is not compatible with atom() - 2 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/pats.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/pats.pretty index d0d4f19c1a..171c12dc46 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/pats.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/pats.pretty @@ -14,10 +14,4 @@ Because in the expression's type: However the following candidate: 'error' Differs from the expected type: #{term() => term()} ------------------------------- Detailed message ------------------------------ - - #{a => 'ok'} | 'error' is not compatible with #{term() => term()} - because - 'error' is not compatible with #{term() => term()} - 1 ERROR diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/records.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/records.pretty index 5b6854f238..f59eda6cd2 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/records.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/records.pretty @@ -18,16 +18,9 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/records.erl:50:21 │ 50 │ mk_rec_neg(rec2) -> #rec2{}. - │ ^^^^^^^ - │ │ - │ #rec2{...}. + │ ^^^^^^^ #rec2{...}. Expression has type: #rec2{} Context expected type: #rec1{} | #rec3{} - │ - - #rec2{} is not compatible with #rec1{} | #rec3{} - because - #rec2{} is not compatible with #rec1{} error: undefined_field (See https://fb.me/eqwalizer_errors#undefined_field) ┌─ check/src/records.erl:59:11 @@ -108,13 +101,6 @@ Because in the expression's type: Context expects type: number() , number()} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {atom(), number()} is not compatible with {number(), atom()} - because - atom() is not compatible with number() - error: undefined_field (See https://fb.me/eqwalizer_errors#undefined_field) ┌─ check/src/records.erl:144:17 │ @@ -181,14 +167,6 @@ Because in the expression's type: Context expects type: number() } ------------------------------- Detailed message ------------------------------ - - #any_box{inner :: 'ok'} is not compatible with int_box() - because - #any_box{inner :: 'ok'} is not compatible with #any_box{inner :: number()} - because - 'ok' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/records.erl:222:5 │ @@ -206,12 +184,6 @@ Because in the expression's type: Context expects type: number() } ------------------------------- Detailed message ------------------------------ - - #any_box{inner :: 'ok'} is not compatible with #any_box{inner :: number()} - because - 'ok' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/records.erl:282:27 │ @@ -228,12 +200,6 @@ Because in the expression's type: Context expects type: number() | 'false' | 'true' No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - - 'a' is not compatible with number() | boolean() - because - 'a' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/records.erl:287:23 │ @@ -250,12 +216,6 @@ Because in the expression's type: Context expects type: number() | 'false' | 'true' No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - - 'a' is not compatible with number() | boolean() - because - 'a' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/records.erl:292:5 │ @@ -273,14 +233,6 @@ Because in the expression's type: Context expects type: number() } ------------------------------- Detailed message ------------------------------ - - #int_bool_box{inner :: 'true'} is not compatible with only_int_box() - because - #int_bool_box{inner :: 'true'} is not compatible with #int_bool_box{inner :: number()} - because - 'true' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/records.erl:308:28 │ @@ -297,12 +249,6 @@ Because in the expression's type: Context expects type: number() | 'false' | 'true' No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - - 'a' is not compatible with number() | boolean() - because - 'a' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/records.erl:314:5 │ @@ -320,14 +266,6 @@ Because in the expression's type: Context expects type: boolean() } ------------------------------- Detailed message ------------------------------ - - #int_bool_box{inner :: number()} is not compatible with only_bool_box() - because - #int_bool_box{inner :: number()} is not compatible with #int_bool_box{inner :: boolean()} - because - number() is not compatible with boolean() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/records.erl:319:5 │ @@ -343,12 +281,6 @@ Because in the expression's type: Here the type is: #int_bool_box{inner :: number()} Context expects type: #any_box{} ------------------------------- Detailed message ------------------------------ - - only_int_box() is not compatible with #any_box{} - because - #int_bool_box{inner :: number()} is not compatible with #any_box{} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/records.erl:319:5 │ @@ -383,15 +315,6 @@ Because in the expression's type: No candidate matches in the expected union. } ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {'int_bool_box', 'a'} is not compatible with {'int_bool_box', number() | boolean()} - because - 'a' is not compatible with number() | boolean() - because - 'a' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/records.erl:334:5 │ @@ -409,15 +332,6 @@ Because in the expression's type: Context expects type: number() } ------------------------------- Detailed message ------------------------------ - - {'int_bool_box', 'a'} is not compatible with only_int_box() - because - at tuple index 2: - {'int_bool_box', 'a'} is not compatible with {'int_bool_box', number()} - because - 'a' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/records.erl:352:5 │ @@ -434,12 +348,6 @@ Because in the expression's type: However the following candidate: 'false' Differs from the expected type: 'true' ------------------------------- Detailed message ------------------------------ - - boolean() | 'true' is not compatible with 'true' - because - 'false' is not compatible with 'true' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/records.erl:355:13 │ @@ -463,12 +371,6 @@ Because in the expression's type: Here the type is: #bad_default{} Context expects type: #int_bool_box{inner :: number()} ------------------------------- Detailed message ------------------------------ - - #bad_default{} is not compatible with only_int_box() - because - #bad_default{} is not compatible with #int_bool_box{inner :: number()} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/records.erl:372:5 │ @@ -486,13 +388,6 @@ Because in the expression's type: Context expects type: 'bad_default' , number()} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {'int_bool_box', number()} is not compatible with {'bad_default', number()} - because - 'int_bool_box' is not compatible with 'bad_default' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/records.erl:388:31 │ @@ -510,12 +405,6 @@ Because in the expression's type: Context expects type: number() } ------------------------------- Detailed message ------------------------------ - - #refined_two_fields{} is not compatible with #refined_two_fields{inner :: number()} - because - term() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/records.erl:402:5 │ @@ -594,12 +483,6 @@ Because in the expression's type: However the following candidate: 'my_record' Differs from the expected type: binary() ------------------------------- Detailed message ------------------------------ - - 'my_record' | binary() is not compatible with binary() - because - 'my_record' is not compatible with binary() - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/records.erl:517:1 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/recursive_aliases.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/recursive_aliases.pretty index 215d6a54ae..ffd83bc179 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/recursive_aliases.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/recursive_aliases.pretty @@ -17,16 +17,6 @@ Because in the expression's type: Context expects type: 'b' , chainA()} ------------------------------- Detailed message ------------------------------ - - chainA() is not compatible with chainB() - because - 'nil' | {'a', chainA()} is not compatible with chainB() - because - 'nil' | {'a', chainA()} is not compatible with 'nil' | {'b', chainB()} - because - {'a', chainA()} is not compatible with 'nil' | {'b', chainB()} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/recursive_aliases.erl:62:20 │ @@ -46,16 +36,6 @@ Because in the expression's type: Context expects type: 'a' , chainB()} ------------------------------- Detailed message ------------------------------ - - chainB() is not compatible with chainA() - because - 'nil' | {'b', chainB()} is not compatible with chainA() - because - 'nil' | {'b', chainB()} is not compatible with 'nil' | {'a', chainA()} - because - {'b', chainB()} is not compatible with 'nil' | {'a', chainA()} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/recursive_aliases.erl:65:20 │ @@ -75,16 +55,6 @@ Because in the expression's type: Context expects type: 'a' , chainAB()} ------------------------------- Detailed message ------------------------------ - - chainAB() is not compatible with chainA() - because - 'nil' | {'a', chainAB()} | {'b', chainAB()} is not compatible with chainA() - because - 'nil' | {'a', chainAB()} | {'b', chainAB()} is not compatible with 'nil' | {'a', chainA()} - because - {'b', chainAB()} is not compatible with 'nil' | {'a', chainA()} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/recursive_aliases.erl:68:20 │ @@ -104,16 +74,6 @@ Because in the expression's type: Context expects type: 'b' , chainAB()} ------------------------------- Detailed message ------------------------------ - - chainAB() is not compatible with chainB() - because - 'nil' | {'a', chainAB()} | {'b', chainAB()} is not compatible with chainB() - because - 'nil' | {'a', chainAB()} | {'b', chainAB()} is not compatible with 'nil' | {'b', chainB()} - because - {'a', chainAB()} is not compatible with 'nil' | {'b', chainB()} - error: type_alias_is_non_productive (See https://fb.me/eqwalizer_errors#type_alias_is_non_productive) ┌─ check/src/recursive_aliases.erl:70:1 │ @@ -143,17 +103,6 @@ Because in the expression's type: However the following candidate: {'a', atom(), pchainA(atom())} Differs from the expected type: 'nil' | {'a', pchainAB(atom())} | {'b', pchainAB(atom())} ------------------------------- Detailed message ------------------------------ - - pchainA(atom()) is not compatible with pchainAB(atom()) - because - 'nil' | {'a', atom(), pchainA(atom())} is not compatible with pchainAB(atom()) - because - 'nil' | {'a', atom(), pchainA(atom())} is not compatible with 'nil' | {'a', pchainAB(atom())} | {'b', pchainAB(atom())} - because - {'a', atom(), pchainA(atom())} is not compatible with 'nil' | {'a', pchainAB(atom())} | {'b', pchainAB(atom())} - expected union does not contain any tuple type of size 3 - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/recursive_aliases.erl:113:23 │ @@ -172,16 +121,6 @@ Because in the expression's type: Context expects type: #{a := ..., ...} The type of the expression is missing the following required keys: a. ------------------------------- Detailed message ------------------------------ - - mChainB() is not compatible with mChainA() - because - 'nil' | #{b := mChainB()} is not compatible with mChainA() - because - 'nil' | #{b := mChainB()} is not compatible with 'nil' | #{a := mChainA()} - because - #{b := mChainB()} is not compatible with 'nil' | #{a := mChainA()} - error: expected_fun_type (See https://fb.me/eqwalizer_errors#expected_fun_type) ┌─ check/src/recursive_aliases.erl:138:15 │ @@ -210,16 +149,6 @@ Because in the expression's type: No candidate matches in the expected union. , pchainA('a')} ------------------------------- Detailed message ------------------------------ - - pchainA('a') is not compatible with pchainA('b' | 'c') - because - 'nil' | {'a', 'a', pchainA('a')} is not compatible with pchainA('b' | 'c') - because - 'nil' | {'a', 'a', pchainA('a')} is not compatible with 'nil' | {'a', 'b' | 'c', pchainA('b' | 'c')} - because - {'a', 'a', pchainA('a')} is not compatible with 'nil' | {'a', 'b' | 'c', pchainA('b' | 'c')} - error: type_alias_is_non_productive (See https://fb.me/eqwalizer_errors#type_alias_is_non_productive) ┌─ check/src/recursive_aliases.erl:202:1 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/refine.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/refine.pretty index b443f787c6..711e1dc2e9 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/refine.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/refine.pretty @@ -23,13 +23,6 @@ Because in the expression's type: Context expects type: none() , A} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {B, A} is not compatible with {none(), none()} - because - B is not compatible with none() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/refine.erl:22:15 │ @@ -55,12 +48,6 @@ Because in the expression's type: Context expects type: none() ) ------------------------------- Detailed message ------------------------------ - - fun((B | A) -> A) is not compatible with fun((A | B) -> none()) - because - A is not compatible with none() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/refine.erl:34:16 │ @@ -78,12 +65,6 @@ Because in the expression's type: Context expects type: none() ) ------------------------------- Detailed message ------------------------------ - - fun((atom() | A) -> A) is not compatible with fun((A | atom()) -> none()) - because - A is not compatible with none() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/refine.erl:84:27 │ @@ -133,13 +114,6 @@ Because in the expression's type: Context expects type: none() , binary()} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {number(), binary()} is not compatible with {none(), none()} - because - number() is not compatible with none() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/refine.erl:126:32 │ @@ -157,13 +131,6 @@ Because in the expression's type: Context expects type: none() , atom()} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {number(), atom()} is not compatible with {none(), none()} - because - number() is not compatible with none() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/refine.erl:139:28 │ @@ -190,14 +157,6 @@ Because in the expression's type: Differs from the expected type: 'a' ] ------------------------------- Detailed message ------------------------------ - - ['a' | 'b'] is not compatible with ['a'] - because - 'a' | 'b' is not compatible with 'a' - because - 'b' is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/refine.erl:151:28 │ @@ -224,14 +183,6 @@ Because in the expression's type: Differs from the expected type: 'a' ] ------------------------------- Detailed message ------------------------------ - - ['a' | 'b'] is not compatible with ['a'] - because - 'a' | 'b' is not compatible with 'a' - because - 'b' is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/refine.erl:166:5 │ @@ -273,13 +224,6 @@ Because in the expression's type: Context expects type: 'not_my_rec' , number(), atom()} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {'my_rec', number(), atom()} is not compatible with {'not_my_rec', term(), term()} - because - 'my_rec' is not compatible with 'not_my_rec' - error: unknown_id (See https://fb.me/eqwalizer_errors#unknown_id) ┌─ check/src/refine.erl:199:10 │ @@ -310,13 +254,6 @@ Because in the expression's type: Context expects type: number() , number()} ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {'my_rec', atom(), number()} is not compatible with {'my_rec', number(), atom()} - because - atom() is not compatible with number() - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/refine.erl:218:1 │ @@ -374,13 +311,6 @@ Because in the expression's type: Context expects type: number() , number()} ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {'my_rec', atom(), number()} is not compatible with {'my_rec', number(), atom()} - because - atom() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/refine.erl:250:26 │ @@ -398,15 +328,6 @@ Because in the expression's type: Context expects type: number() , number()} ------------------------------- Detailed message ------------------------------ - - {'my_rec', atom(), number()} is not compatible with dynamic(#my_rec{}) - because - at tuple index 2: - {'my_rec', atom(), number()} is not compatible with {'my_rec', number(), atom()} - because - atom() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/refine.erl:262:26 │ @@ -424,13 +345,6 @@ Because in the expression's type: Context expects type: atom() , atom()} ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {'my_rec', number(), atom()} is not compatible with {'my_rec', atom(), number()} - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/refine.erl:262:26 │ @@ -448,15 +362,6 @@ Because in the expression's type: Context expects type: atom() , atom()} ------------------------------- Detailed message ------------------------------ - - #my_rec{} is not compatible with dynamic({'my_rec', atom(), number()}) - because - at tuple index 2: - {'my_rec', number(), atom()} is not compatible with {'my_rec', atom(), number()} - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/refine.erl:278:21 │ @@ -474,13 +379,6 @@ Because in the expression's type: Context expects type: atom() , atom()} ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {'my_rec', number(), atom()} is not compatible with {'my_rec', atom(), atom()} - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/refine.erl:282:16 │ @@ -497,12 +395,6 @@ Because in the expression's type: However the following candidate: atom() Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - atom() | number() is not compatible with number() - because - atom() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/refine.erl:286:13 │ @@ -519,12 +411,6 @@ Because in the expression's type: However the following candidate: 'a' Differs from the expected type: {'my_rec', term(), term()} ------------------------------- Detailed message ------------------------------ - - #my_rec{} | 'a' is not compatible with {'my_rec', term(), term()} - because - 'a' is not compatible with {'my_rec', term(), term()} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/refine.erl:296:5 │ @@ -541,12 +427,6 @@ Because in the expression's type: However the following candidate: 'undefined' Differs from the expected type: binary() ------------------------------- Detailed message ------------------------------ - - 'undefined' | binary() is not compatible with binary() - because - 'undefined' is not compatible with binary() - error: reveal_type (See https://fb.me/eqwalizer_errors#reveal_type) ┌─ check/src/refine.erl:378:27 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/strict_complex_types.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/strict_complex_types.pretty index cc9f9d0634..8943deb90a 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/strict_complex_types.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/strict_complex_types.pretty @@ -13,12 +13,6 @@ Because in the expression's type: Here the type is: 'undefined' Context expects type: #{...} ------------------------------- Detailed message ------------------------------ - - 'undefined' is not compatible with complex_map() - because - 'undefined' is not compatible with #{id := number(), {secret, id} => number(), atom() => term()} - error: reveal_type (See https://fb.me/eqwalizer_errors#reveal_type) ┌─ check/src/strict_complex_types.erl:29:25 │ @@ -46,12 +40,6 @@ Because in the expression's type: Here the type is: #{...} Context expects type: [T] ------------------------------- Detailed message ------------------------------ - - complex_map() is not compatible with [T] - because - #{id := number(), {secret, id} => number(), atom() => term()} is not compatible with [T] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/strict_complex_types.erl:48:16 │ @@ -67,10 +55,4 @@ Because in the expression's type: Here the type is: #{...} Context expects type: [dynamic()] ------------------------------- Detailed message ------------------------------ - - complex_map() is not compatible with [dynamic()] - because - #{id := number(), {secret, id} => number(), atom() => term()} is not compatible with [dynamic()] - 5 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/subtype_neg.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/subtype_neg.pretty index 1ee53764d2..4413a8fee8 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/subtype_neg.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/subtype_neg.pretty @@ -31,12 +31,6 @@ Because in the expression's type: Context expects type: atom() ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> term()) is not compatible with fun((term()) -> atom()) - because - term() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/subtype_neg.erl:27:11 │ @@ -53,14 +47,6 @@ Because in the expression's type: However the following candidate: 'c' Differs from the expected type: 'a' | 'b' ------------------------------- Detailed message ------------------------------ - - 'a' | 'b' | 'c' is not compatible with 'a' | 'b' - because - 'c' is not compatible with 'a' | 'b' - because - 'c' is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/subtype_neg.erl:30:11 │ @@ -87,17 +73,6 @@ Because in the expression's type: Differs from the expected type: 'a' , 'a' | 'b'} ------------------------------- Detailed message ------------------------------ - - {'a' | 'b', 'a' | 'b'} is not compatible with {'a', 'b'} | {'b', 'a'} - because - at tuple index 1: - {'a' | 'b', 'a' | 'b'} is not compatible with {'a', 'b'} - because - 'a' | 'b' is not compatible with 'a' - because - 'b' is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/subtype_neg.erl:38:11 │ @@ -116,17 +91,6 @@ Because in the expression's type: Differs from the expected type: 'a' , ab()} ------------------------------- Detailed message ------------------------------ - - pair_ab() is not compatible with pair_diff_elems() - because - {ab(), ab()} is not compatible with pair_diff_elems() - because - {ab(), ab()} is not compatible with {'a', 'b'} | {'b', 'a'} - because - at tuple index 1: - {ab(), ab()} is not compatible with {'a', 'b'} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/subtype_neg.erl:41:11 │ @@ -151,10 +115,6 @@ Because in the expression's type: Context expects type: #{a := ..., ...} The type of the expression is missing the following required keys: a. ------------------------------- Detailed message ------------------------------ - -key `a` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/subtype_neg.erl:52:13 │ @@ -171,10 +131,6 @@ Because in the expression's type: Context expects type: #{...} The expected map has no corresponding key for: b. ------------------------------- Detailed message ------------------------------ - -key `b` is declared in the former but not in the latter and the latter map has no default association - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/subtype_neg.erl:60:13 │ @@ -191,10 +147,6 @@ Because in the expression's type: Context expects type: #{a := ..., ...} The type of the expression is missing the following required keys: a. ------------------------------- Detailed message ------------------------------ - -key `a` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/subtype_neg.erl:64:13 │ @@ -212,13 +164,6 @@ Because in the expression's type: Context expects type: atom() , ... } ------------------------------- Detailed message ------------------------------ - - #{term() => number()} is not compatible with #{atom() => number()} - the default associations are not compatible - because - term() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/subtype_neg.erl:68:13 │ @@ -236,13 +181,6 @@ Because in the expression's type: Context expects type: number() , ... } ------------------------------- Detailed message ------------------------------ - - #{atom() => term()} is not compatible with #{atom() => number()} - the default associations are not compatible - because - term() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/subtype_neg.erl:72:13 │ @@ -259,13 +197,6 @@ Because in the expression's type: Context expects type: #{...} (no default association) The expected map has no default association while the type of the expression has one. ------------------------------- Detailed message ------------------------------ - - #{atom() => term()} is not compatible with #{} - because - #{atom() => term()} is not compatible with #{} - the latter map has no default association while the first map has one - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/subtype_neg.erl:79:15 │ @@ -291,13 +222,6 @@ Because in the expression's type: Context expects type: 'ok' } ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {{}, 'error'} is not compatible with {tuple(), 'ok'} - because - 'error' is not compatible with 'ok' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/subtype_neg.erl:87:5 │ @@ -315,13 +239,6 @@ Because in the expression's type: Context expects type: 'ok' } ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {[], 'error'} is not compatible with {[pid()], 'ok'} - because - 'error' is not compatible with 'ok' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/subtype_neg.erl:91:5 │ @@ -339,13 +256,6 @@ Because in the expression's type: Context expects type: 'ok' } ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {[], 'error'} is not compatible with {iolist(), 'ok'} - because - 'error' is not compatible with 'ok' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/subtype_neg.erl:94:13 │ @@ -382,10 +292,4 @@ Because in the expression's type: Context expects type: none() ] ------------------------------- Detailed message ------------------------------ - - ['a'] | [none()] is not compatible with [] - because - ['a'] is not compatible with [] - 20 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/t_maps.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/t_maps.pretty index d0e08f9ac4..5c2b07ae96 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/t_maps.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/t_maps.pretty @@ -15,13 +15,6 @@ Because in the expression's type: Context expects type: atom() , ... } ------------------------------- Detailed message ------------------------------ - - #{number() => number()} is not compatible with #{number() => atom()} - the default associations are not compatible - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:41:5 │ @@ -40,15 +33,6 @@ Because in the expression's type: Differs from the expected type: atom() , ... } ------------------------------- Detailed message ------------------------------ - - #{number() => 'zero' | number()} is not compatible with #{number() => atom()} - the default associations are not compatible - because - 'zero' | number() is not compatible with atom() - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:56:5 │ @@ -66,15 +50,6 @@ Because in the expression's type: Context expects type: number() , ... } ------------------------------- Detailed message ------------------------------ - - #{one := 'one', zero := number()} is not compatible with #{one => number(), zero := number()} - because - at key `one`: - #{one := 'one', zero := number()} is not compatible with #{one => number(), zero := number()} - because - 'one' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:80:5 │ @@ -108,15 +83,6 @@ Because in the expression's type: Context expects type: n() , ... } ------------------------------- Detailed message ------------------------------ - - #{b() => term()} is not compatible with #{n() => term()} - the default associations are not compatible - because - b() is not compatible with n() - because - boolean() is not compatible with n() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:140:26 │ @@ -134,17 +100,6 @@ Because in the expression's type: Context expects type: n() , ... } ------------------------------- Detailed message ------------------------------ - - #{a := b()} is not compatible with #{a := n()} - because - at key `a`: - #{a := b()} is not compatible with #{a := n()} - because - b() is not compatible with n() - because - boolean() is not compatible with n() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:145:26 │ @@ -162,17 +117,6 @@ Because in the expression's type: Context expects type: n() , ... } ------------------------------- Detailed message ------------------------------ - - #{a := b()} is not compatible with #{a => n()} - because - at key `a`: - #{a := b()} is not compatible with #{a => n()} - because - b() is not compatible with n() - because - boolean() is not compatible with n() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:155:26 │ @@ -189,10 +133,6 @@ Because in the expression's type: Context expects type: #{a := ..., ...} The type of the expression is missing the following required keys: a. ------------------------------- Detailed message ------------------------------ - -key `a` is declared as required in the latter but not in the former - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/t_maps.erl:165:1 │ @@ -216,17 +156,6 @@ Because in the expression's type: Context expects type: atom() , ... } ------------------------------- Detailed message ------------------------------ - - #{a := a(), n() => a()} is not compatible with #{a() => a()} - the default associations are not compatible - because - n() is not compatible with a() - because - number() is not compatible with a() - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:208:5 │ @@ -244,17 +173,6 @@ Because in the expression's type: Context expects type: number() , ... } ------------------------------- Detailed message ------------------------------ - - #{a := a(), n() => a()} is not compatible with #{n() => a()} - because - #{a := a(), n() => a()} is not compatible with #{n() => a()} - key `a` is declared in the former but not in the latter and key `a` isn't compatible with the default association of the latter map - because - 'a' is not compatible with n() - because - 'a' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:216:5 │ @@ -272,17 +190,6 @@ Because in the expression's type: Context expects type: number() , ... } ------------------------------- Detailed message ------------------------------ - - #{a := a(), n() => a()} is not compatible with #{n() => a()} - because - #{a := a(), n() => a()} is not compatible with #{n() => a()} - key `a` is declared in the former but not in the latter and key `a` isn't compatible with the default association of the latter map - because - 'a' is not compatible with n() - because - 'a' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:224:5 │ @@ -300,17 +207,6 @@ Because in the expression's type: Context expects type: number() , ... } ------------------------------- Detailed message ------------------------------ - - #{a => a(), n() => a()} is not compatible with #{n() => a()} - because - #{a => a(), n() => a()} is not compatible with #{n() => a()} - key `a` is declared in the former but not in the latter and key `a` isn't compatible with the default association of the latter map - because - 'a' is not compatible with n() - because - 'a' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:242:5 │ @@ -328,17 +224,6 @@ Because in the expression's type: Context expects type: F1 , ... } ------------------------------- Detailed message ------------------------------ - - #{bar := B1, foo := F1} is not compatible with foo_bar(B1, F1) - because - #{bar := B1, foo := F1} is not compatible with #{bar := F1, foo := B1} - because - at key `bar`: - #{bar := B1, foo := F1} is not compatible with #{bar := F1, foo := B1} - because - B1 is not compatible with F1 - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:254:5 │ @@ -356,17 +241,6 @@ Because in the expression's type: Context expects type: F1 , ... } ------------------------------- Detailed message ------------------------------ - - #{bar := B1, foo := F1} is not compatible with foo_bar_opt(B1, F1) - because - #{bar := B1, foo := F1} is not compatible with #{bar => F1, foo => B1} - because - at key `bar`: - #{bar := B1, foo := F1} is not compatible with #{bar => F1, foo => B1} - because - B1 is not compatible with F1 - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:266:5 │ @@ -385,17 +259,6 @@ Because in the expression's type: Differs from the expected type: K1 | K2 , ... } ------------------------------- Detailed message ------------------------------ - - #{K1 | V2 => V1 | K2} is not compatible with kv(K1 | K2, V1 | V2) - because - #{K1 | V2 => V1 | K2} is not compatible with #{K1 | K2 => V1 | V2} - the default associations are not compatible - because - K1 | V2 is not compatible with K1 | K2 - because - V2 is not compatible with K1 | K2 - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:283:19 │ @@ -412,10 +275,6 @@ Because in the expression's type: Context expects type: #{...} The expected map has no corresponding key for: b. ------------------------------- Detailed message ------------------------------ - -key `b` is declared in the former but not in the latter and the latter map has no default association - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:307:22 │ @@ -433,17 +292,6 @@ Because in the expression's type: Context expects type: number() , n()} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {a(), n()} is not compatible with {n(), a()} - because - a() is not compatible with n() - because - atom() is not compatible with n() - because - atom() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:321:5 │ @@ -461,17 +309,6 @@ Because in the expression's type: Context expects type: number() , n()} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {a(), n()} is not compatible with {n(), a()} - because - a() is not compatible with n() - because - atom() is not compatible with n() - because - atom() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:334:25 │ @@ -489,13 +326,6 @@ Because in the expression's type: Context expects type: K , ... } ------------------------------- Detailed message ------------------------------ - - #{V => K} is not compatible with #{K => V} - the default associations are not compatible - because - V is not compatible with K - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:351:25 │ @@ -515,17 +345,6 @@ Because in the expression's type: Context expects type: atom() , ... } ------------------------------- Detailed message ------------------------------ - - #{a() => n()} | #{n() => a()} | #{id => 'id' | 'no_id'} is not compatible with #{a() | n() => a()} - because - #{a() => n()} is not compatible with #{a() | n() => a()} - the default associations are not compatible - because - n() is not compatible with a() - because - number() is not compatible with a() - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/t_maps.erl:356:1 │ @@ -556,15 +375,6 @@ Because in the expression's type: No candidate of the expression's type matches the expected type. , a()} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {'a' | 'b', a()} is not compatible with {n(), a()} - because - 'a' | 'b' is not compatible with n() - because - 'a' | 'b' is not compatible with number() - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/t_maps.erl:429:1 │ @@ -611,10 +421,6 @@ Because in the expression's type: Context expects type: #{n := ..., ...} The type of the expression is missing the following required keys: n. ------------------------------- Detailed message ------------------------------ - -key `n` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:536:5 │ @@ -633,17 +439,6 @@ Because in the expression's type: Differs from the expected type: 'b' | 'c' , ... } ------------------------------- Detailed message ------------------------------ - - #{a => 'b' | 'c' | 'a'} is not compatible with #{a => 'b' | 'c'} - because - at key `a`: - #{a => 'b' | 'c' | 'a'} is not compatible with #{a => 'b' | 'c'} - because - 'b' | 'c' | 'a' is not compatible with 'b' | 'c' - because - 'a' is not compatible with 'b' | 'c' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:571:5 │ @@ -660,15 +455,6 @@ Because in the expression's type: Context expects type: #{item_v2 := ..., ...} The type of the expression is missing the following required keys: item_v2. ------------------------------- Detailed message ------------------------------ - - rec_shape() is not compatible with rec_shape_v2() - because - #{item := rec_shape() | 'undefined'} is not compatible with rec_shape_v2() - because - #{item := rec_shape() | 'undefined'} is not compatible with #{item_v2 := rec_shape_v2() | 'undefined'} - key `item_v2` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:629:5 │ @@ -687,14 +473,6 @@ Because in the expression's type: Differs from the expected type: 'a' | #{item := 'a' | gen_shape('a')} , ... } ------------------------------- Detailed message ------------------------------ - - gen_shape('a' | 'b') is not compatible with 'a' | #{item := 'a' | #{item := 'a' | gen_shape('a')}} - because - #{item := 'a' | 'b' | gen_shape('a' | 'b')} is not compatible with 'a' | #{item := 'a' | #{item := 'a' | gen_shape('a')}} - because - #{item := 'a' | 'b' | gen_shape('a' | 'b')} is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:641:5 │ @@ -711,15 +489,6 @@ Because in the expression's type: Context expects type: #{item := ..., ...} The type of the expression is missing the following required keys: item. ------------------------------- Detailed message ------------------------------ - - gen_shape_v2('a') is not compatible with gen_shape('a') - because - #{item_v2 := 'a' | gen_shape_v2('a')} is not compatible with gen_shape('a') - because - #{item_v2 := 'a' | gen_shape_v2('a')} is not compatible with #{item := 'a' | gen_shape('a')} - key `item` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:648:5 │ @@ -736,10 +505,6 @@ Because in the expression's type: Context expects type: #{...} The expected map has no corresponding key for: d. ------------------------------- Detailed message ------------------------------ - -key `d` is declared in the former but not in the latter and the latter map has no default association - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:661:5 │ @@ -757,15 +522,6 @@ Because in the expression's type: Context expects type: 'ka' , ... } ------------------------------- Detailed message ------------------------------ - - #{a => 'va', b => 'vb', c => 'vc', d => 'vd', e => 've'} is not compatible with #{a => 'ka', b => 'kb', c => 'kc'} - because - at key `a`: - #{a => 'va', b => 'vb', c => 'vc', d => 'vd', e => 've'} is not compatible with #{a => 'ka', b => 'kb', c => 'kc'} - because - 'va' is not compatible with 'ka' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:675:5 │ @@ -782,10 +538,6 @@ Because in the expression's type: Context expects type: #{c := ..., ...} The type of the expression is missing the following required keys: c. ------------------------------- Detailed message ------------------------------ - -key `c` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:682:5 │ @@ -802,10 +554,6 @@ Because in the expression's type: Context expects type: #{b := ..., ...} The type of the expression is missing the following required keys: b. ------------------------------- Detailed message ------------------------------ - -key `b` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:689:5 │ @@ -822,10 +570,6 @@ Because in the expression's type: Context expects type: #{b := ..., ...} The type of the expression is missing the following required keys: b. ------------------------------- Detailed message ------------------------------ - -key `b` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:710:5 │ @@ -842,10 +586,6 @@ Because in the expression's type: Context expects type: #{k_req3 := ..., k_req2 := ..., k_req1 := ..., ...} The type of the expression is missing the following required keys: k_req3, k_req2, k_req1. ------------------------------- Detailed message ------------------------------ - -keys `k_req1`, `k_req2`, `k_req3` are declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:732:27 │ @@ -862,13 +602,6 @@ Because in the expression's type: Context expects type: #{...} (no default association) The expected map has no default association while the type of the expression has one. ------------------------------- Detailed message ------------------------------ - - #{'a' | 'b' => boolean()} is not compatible with #{a => 'true', b => boolean()} - key a is not present in the former map but is incompatible with its default association - because - boolean() is not compatible with 'true' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:737:27 │ @@ -885,10 +618,6 @@ Because in the expression's type: Context expects type: #{b := ..., ...} The type of the expression is missing the following required keys: b. ------------------------------- Detailed message ------------------------------ - -key `b` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/t_maps.erl:742:27 │ @@ -905,11 +634,4 @@ Because in the expression's type: Context expects type: #{...} (no default association) The expected map has no default association while the type of the expression has one. ------------------------------- Detailed message ------------------------------ - - #{'a' | 'b' => boolean()} is not compatible with #{a => boolean()} - because - #{'a' | 'b' => boolean()} is not compatible with #{a => boolean()} - the latter map has no default association while the first map has one - 44 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/tries.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/tries.pretty index 471b5c8ebe..5f9c8914f4 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/tries.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/tries.pretty @@ -22,12 +22,6 @@ Because in the expression's type: However the following candidate: [] Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - [] | 'error' is not compatible with atom() - because - [] is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/tries.erl:75:16 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/tuple_union.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/tuple_union.pretty index e02af30869..5b2cca1631 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/tuple_union.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/tuple_union.pretty @@ -16,17 +16,6 @@ Because in the expression's type: Differs from the expected type: 'ok' , 'arg' | 'nil'} ------------------------------- Detailed message ------------------------------ - - t4() is not compatible with t5() - because - {'msg', 'ok' | 'err', 'arg' | 'nil'} is not compatible with t5() - because - {'msg', 'ok' | 'err', 'arg' | 'nil'} is not compatible with {'msg', 'ok', 'arg'} | {'msg', 'err', 'arg'} | {'msg', 'ok', 'nil'} | {'msg', 'err', 'nil'} - because - at tuple index 2: - {'msg', 'ok' | 'err', 'arg' | 'nil'} is not compatible with {'msg', 'ok', 'arg'} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/tuple_union.erl:66:26 │ @@ -47,14 +36,4 @@ Because in the expression's type: No candidate of the expression's type matches the expected type. , tree2()} ------------------------------- Detailed message ------------------------------ - - tree3() is not compatible with tree1() - because - {'leaf', atom()} | {'b1' | 'b2' | 'b3', tree2()} is not compatible with tree1() - because - {'leaf', atom()} | {'b1' | 'b2' | 'b3', tree2()} is not compatible with {'leaf', atom()} | {'b1', tree1()} | {'b2', tree1()} - because - {'b1' | 'b2' | 'b3', tree2()} is not compatible with {'leaf', atom()} | {'b1', tree1()} | {'b2', tree1()} - 2 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/type_asserts.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/type_asserts.pretty index c5fb63ed41..069950f003 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/type_asserts.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/type_asserts.pretty @@ -22,12 +22,6 @@ Because in the expression's type: However the following candidate: string() Differs from the expected type: binary() ------------------------------- Detailed message ------------------------------ - - string() | binary() is not compatible with binary() - because - string() is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/type_asserts.erl:63:26 │ @@ -63,17 +57,6 @@ Because in the expression's type: Context expects type: atom() , atom()} ------------------------------- Detailed message ------------------------------ - - 'false' | {number(), atom()} is not compatible with 'false' | {atom(), number()} - because - {number(), atom()} is not compatible with 'false' | {atom(), number()} - because - at tuple index 1: - {number(), atom()} is not compatible with {atom(), number()} - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/type_asserts.erl:92:3 │ @@ -91,13 +74,6 @@ Because in the expression's type: Context expects type: number() } ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {'false' | number(), term()} is not compatible with {'false' | number(), number()} - because - term() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/type_asserts.erl:105:39 │ @@ -150,17 +126,6 @@ Because in the expression's type: The expected map has no corresponding key for: kb. , ... } ------------------------------- Detailed message ------------------------------ - - #{dynamic() => #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kb := 'vb', kc := 'vc'} | #{ka := 'va', kc := 'vc'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb', kc := 'vc'} | #{kb := 'vb'}} is not compatible with #{dynamic() => #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kc := 'vc'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb'}} | #{dynamic() => #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb', kc := 'vc'} | #{kb := 'vb'}} | #{dynamic() => #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kc := 'vc'} | #{kb := 'vb', kc := 'vc'} | #{kb := 'vb'}} - because - #{dynamic() => #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kb := 'vb', kc := 'vc'} | #{ka := 'va', kc := 'vc'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb', kc := 'vc'} | #{kb := 'vb'}} is not compatible with #{dynamic() => #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kc := 'vc'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb'}} - the default associations are not compatible - because - #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kb := 'vb', kc := 'vc'} | #{ka := 'va', kc := 'vc'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb', kc := 'vc'} | #{kb := 'vb'} is not compatible with #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kc := 'vc'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb'} - because - #{ka := 'va', kb := 'vb', kc := 'vc'} is not compatible with #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kc := 'vc'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb'} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/type_asserts.erl:129:9 │ @@ -179,17 +144,6 @@ Because in the expression's type: The expected map has no corresponding key for: kb. , ... } ------------------------------- Detailed message ------------------------------ - - #{dynamic() => #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kb := 'vb', kc := 'vc'} | #{ka := 'va', kc := 'vc'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb', kc := 'vc'} | #{kb := 'vb'}} is not compatible with #{dynamic() => #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kc := 'vc'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb'}} | #{dynamic() => #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb', kc := 'vc'} | #{kb := 'vb'}} | #{dynamic() => #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kc := 'vc'} | #{kb := 'vb', kc := 'vc'} | #{kb := 'vb'}} - because - #{dynamic() => #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kb := 'vb', kc := 'vc'} | #{ka := 'va', kc := 'vc'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb', kc := 'vc'} | #{kb := 'vb'}} is not compatible with #{dynamic() => #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kc := 'vc'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb'}} - the default associations are not compatible - because - #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kb := 'vb', kc := 'vc'} | #{ka := 'va', kc := 'vc'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb', kc := 'vc'} | #{kb := 'vb'} is not compatible with #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kc := 'vc'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb'} - because - #{ka := 'va', kb := 'vb', kc := 'vc'} is not compatible with #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kc := 'vc'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb'} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/type_asserts.erl:132:9 │ @@ -208,17 +162,6 @@ Because in the expression's type: The expected map has no corresponding key for: kb. , ... } ------------------------------- Detailed message ------------------------------ - - #{dynamic() => #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kb := 'vb', kc := 'vc'} | #{ka := 'va', kc := 'vc'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb', kc := 'vc'} | #{kb := 'vb'}} is not compatible with #{dynamic() => #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kc := 'vc'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb'}} | #{dynamic() => #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb', kc := 'vc'} | #{kb := 'vb'}} | #{dynamic() => #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kc := 'vc'} | #{kb := 'vb', kc := 'vc'} | #{kb := 'vb'}} - because - #{dynamic() => #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kb := 'vb', kc := 'vc'} | #{ka := 'va', kc := 'vc'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb', kc := 'vc'} | #{kb := 'vb'}} is not compatible with #{dynamic() => #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kc := 'vc'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb'}} - the default associations are not compatible - because - #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kb := 'vb', kc := 'vc'} | #{ka := 'va', kc := 'vc'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb', kc := 'vc'} | #{kb := 'vb'} is not compatible with #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kc := 'vc'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb'} - because - #{ka := 'va', kb := 'vb', kc := 'vc'} is not compatible with #{kc := 'vc'} | #{ka := 'va'} | #{ka := 'va', kc := 'vc'} | #{ka := 'va', kb := 'vb'} | #{kb := 'vb'} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/type_asserts.erl:156:24 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/use_dynamic_gradual.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/use_dynamic_gradual.pretty index d03afc8cf8..b0892292bd 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/use_dynamic_gradual.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/use_dynamic_gradual.pretty @@ -15,11 +15,4 @@ Because in the expression's type: Context expects type: atom() } ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {binary()} is not compatible with {atom()} - because - binary() is not compatible with atom() - 1 ERROR diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/use_dynamic_strict.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/use_dynamic_strict.pretty index 12f1d81948..034dbd779d 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/use_dynamic_strict.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/use_dynamic_strict.pretty @@ -15,11 +15,4 @@ Because in the expression's type: Context expects type: atom() } ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {binary()} is not compatible with {atom()} - because - binary() is not compatible with atom() - 1 ERROR diff --git a/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater.pretty b/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater.pretty index ab0cebee3f..3c6de10f94 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater.pretty @@ -14,12 +14,6 @@ Because in the expression's type: However the following candidate: number() Differs from the expected type: binary() ------------------------------- Detailed message ------------------------------ - - number() | binary() is not compatible with binary() - because - number() is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:172:22 │ @@ -36,12 +30,6 @@ Because in the expression's type: However the following candidate: number() Differs from the expected type: binary() ------------------------------- Detailed message ------------------------------ - - number() | binary() is not compatible with binary() - because - number() is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:183:7 │ @@ -58,12 +46,6 @@ Because in the expression's type: However the following candidate: number() Differs from the expected type: binary() ------------------------------- Detailed message ------------------------------ - - number() | binary() is not compatible with binary() - because - number() is not compatible with binary() - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ eqwater/src/eqwater.erl:228:1 │ @@ -86,12 +68,6 @@ Because in the expression's type: However the following candidate: #ab_rec{} Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - #ab_rec{} | atom() is not compatible with atom() - because - #ab_rec{} is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:356:16 │ @@ -108,12 +84,6 @@ Because in the expression's type: However the following candidate: #ab_rec{} Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - #ab_rec{} | atom() is not compatible with atom() - because - #ab_rec{} is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:404:25 │ @@ -152,12 +122,6 @@ Because in the expression's type: However the following candidate: atom() Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - number() | atom() is not compatible with number() - because - atom() is not compatible with number() - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ eqwater/src/eqwater.erl:419:1 │ @@ -182,12 +146,6 @@ Because in the expression's type: However the following candidate: atom() Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - number() | atom() is not compatible with number() - because - atom() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:429:25 │ @@ -204,12 +162,6 @@ Because in the expression's type: However the following candidate: atom() Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - number() | atom() is not compatible with number() - because - atom() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:496:3 │ @@ -235,13 +187,6 @@ Because in the expression's type: Context expects type: atom() } ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {'union_field', binary()} is not compatible with {'union_field', atom()} - because - binary() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:559:50 │ @@ -258,12 +203,6 @@ Because in the expression's type: However the following candidate: 'ok' Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - number() | 'ok' is not compatible with number() - because - 'ok' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:590:8 │ @@ -280,12 +219,6 @@ Because in the expression's type: However the following candidate: #c{} Differs from the expected type: #b{} ------------------------------- Detailed message ------------------------------ - - #b{} | #c{} is not compatible with #b{} - because - #c{} is not compatible with #b{} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:631:27 │ @@ -302,12 +235,6 @@ Because in the expression's type: However the following candidate: A Differs from the expected type: B ------------------------------- Detailed message ------------------------------ - - A | B is not compatible with B - because - A is not compatible with B - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:649:17 │ @@ -324,12 +251,6 @@ Because in the expression's type: However the following candidate: fun() Differs from the expected type: {term()} ------------------------------- Detailed message ------------------------------ - - fun() | {term()} is not compatible with {term()} - because - fun() is not compatible with {term()} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:660:17 │ @@ -346,12 +267,6 @@ Because in the expression's type: However the following candidate: tuple() Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - tuple() | atom() is not compatible with atom() - because - tuple() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:839:27 │ @@ -368,12 +283,6 @@ Because in the expression's type: However the following candidate: atom() Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - number() | atom() is not compatible with number() - because - atom() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:997:31 │ @@ -412,12 +321,6 @@ Because in the expression's type: However the following candidate: 'undefined' Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - number() | 'undefined' is not compatible with number() - because - 'undefined' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:1122:14 │ @@ -437,14 +340,6 @@ Because in the expression's type: Context expects type: atom() ] ------------------------------- Detailed message ------------------------------ - - [[atom()]] | [atom()] is not compatible with [atom()] - because - [[atom()]] is not compatible with [atom()] - because - [atom()] is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:1131:15 │ @@ -464,14 +359,6 @@ Because in the expression's type: Context expects type: atom() ] ------------------------------- Detailed message ------------------------------ - - [[atom()]] | [atom()] is not compatible with [atom()] - because - [[atom()]] is not compatible with [atom()] - because - [atom()] is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:1155:7 │ @@ -488,12 +375,6 @@ Because in the expression's type: However the following candidate: string() Differs from the expected type: binary() ------------------------------- Detailed message ------------------------------ - - binary() | string() is not compatible with binary() - because - string() is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:1181:7 │ @@ -510,12 +391,6 @@ Because in the expression's type: However the following candidate: atom() Differs from the expected type: binary() ------------------------------- Detailed message ------------------------------ - - binary() | atom() is not compatible with binary() - because - atom() is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:1189:60 │ @@ -532,12 +407,6 @@ Because in the expression's type: However the following candidate: 'ok' Differs from the expected type: binary() ------------------------------- Detailed message ------------------------------ - - 'ok' | binary() is not compatible with binary() - because - 'ok' is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:1194:30 │ @@ -554,12 +423,6 @@ Because in the expression's type: However the following candidate: binary() Differs from the expected type: 'ok' ------------------------------- Detailed message ------------------------------ - - binary() | 'ok' is not compatible with 'ok' - because - binary() is not compatible with 'ok' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:1225:14 │ @@ -576,12 +439,6 @@ Because in the expression's type: However the following candidate: {term(), my_list()} Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - {term(), my_list()} | number() is not compatible with number() - because - {term(), my_list()} is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:1231:31 │ @@ -606,12 +463,6 @@ Because in the expression's type: However the following candidate: 'a' Differs from the expected type: 'b' ------------------------------- Detailed message ------------------------------ - - 'a' | 'b' is not compatible with 'b' - because - 'a' is not compatible with 'b' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:1269:5 │ @@ -624,16 +475,9 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ eqwater/src/eqwater.erl:1286:60 │ 1286 │ negate_atoms_neg(A) when not ((A == a) orelse (A == b)) -> A; - │ ^ - │ │ - │ A. + │ ^ A. Expression has type: 'c' Context expected type: 'a' | 'b' - │ - - 'c' is not compatible with 'a' | 'b' - because - 'c' is not compatible with 'a' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater.erl:1291:43 diff --git a/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater_lists.pretty b/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater_lists.pretty index c5cd5a3b25..bfb8103b16 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater_lists.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater_lists.pretty @@ -16,14 +16,6 @@ Because in the expression's type: Differs from the expected type: binary() ] ------------------------------- Detailed message ------------------------------ - - [binary() | atom()] is not compatible with [binary()] - because - binary() | atom() is not compatible with binary() - because - atom() is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater_lists.erl:48:23 │ @@ -57,12 +49,6 @@ Because in the expression's type: However the following candidate: [] Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - atom() | [] is not compatible with atom() - because - [] is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater_lists.erl:61:19 │ @@ -79,12 +65,6 @@ Because in the expression's type: However the following candidate: [] Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - atom() | [] is not compatible with atom() - because - [] is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater_lists.erl:69:19 │ @@ -121,14 +101,6 @@ Because in the expression's type: Context expects type: binary() ] ------------------------------- Detailed message ------------------------------ - - [atom()] | [binary()] is not compatible with [binary()] - because - [atom()] is not compatible with [binary()] - because - atom() is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater_lists.erl:97:23 │ @@ -155,14 +127,4 @@ Because in the expression's type: Differs from the expected type: atom() ] ------------------------------- Detailed message ------------------------------ - - [atom() | binary()] is not compatible with [atom()] | [binary()] - because - [atom() | binary()] is not compatible with [atom()] - because - atom() | binary() is not compatible with atom() - because - binary() is not compatible with atom() - 8 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater_maps.pretty b/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater_maps.pretty index 847411f766..037e6b14a6 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater_maps.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater_maps.pretty @@ -14,10 +14,6 @@ Because in the expression's type: Context expects type: #{b := ..., ...} The type of the expression is missing the following required keys: b. ------------------------------- Detailed message ------------------------------ - -key `b` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater_maps.erl:27:22 │ @@ -34,10 +30,6 @@ Because in the expression's type: Context expects type: #{b := ..., ...} The type of the expression is missing the following required keys: b. ------------------------------- Detailed message ------------------------------ - -key `b` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater_maps.erl:36:22 │ @@ -54,12 +46,6 @@ Because in the expression's type: However the following candidate: #{} Differs from the expected type: 'ok' ------------------------------- Detailed message ------------------------------ - - #{} | 'ok' is not compatible with 'ok' - because - #{} is not compatible with 'ok' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater_maps.erl:42:22 │ @@ -76,10 +62,6 @@ Because in the expression's type: Context expects type: #{b := ..., ...} The type of the expression is missing the following required keys: b. ------------------------------- Detailed message ------------------------------ - -key `b` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater_maps.erl:55:34 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater_records.pretty b/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater_records.pretty index 5a01001d2e..245813d1e8 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater_records.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater_records.pretty @@ -14,14 +14,6 @@ Because in the expression's type: However the following candidate: #rec1{} Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - user() is not compatible with number() - because - #rec1{} | number() is not compatible with number() - because - #rec1{} is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater_records.erl:40:16 │ @@ -38,14 +30,6 @@ Because in the expression's type: However the following candidate: #rec1{} Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - user() is not compatible with number() - because - #rec1{} | number() is not compatible with number() - because - #rec1{} is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater_records.erl:47:11 │ @@ -62,14 +46,6 @@ Because in the expression's type: However the following candidate: #rec1{} Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - user() is not compatible with number() - because - #rec1{} | number() is not compatible with number() - because - #rec1{} is not compatible with number() - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ eqwater/src/eqwater_records.erl:104:1 │ @@ -96,15 +72,6 @@ Because in the expression's type: Context expects type: #rec1{} , #rec1{}} ------------------------------- Detailed message ------------------------------ - - {#rec1{}, #rec2{}} | {#rec2{}, #rec1{}} is not compatible with {#rec1{}, #rec2{}} - because - at tuple index 1: - {#rec2{}, #rec1{}} is not compatible with {#rec1{}, #rec2{}} - because - #rec2{} is not compatible with #rec1{} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater_records.erl:115:25 │ @@ -124,13 +91,4 @@ Because in the expression's type: Context expects type: #rec1{} , #rec1{}} ------------------------------- Detailed message ------------------------------ - - {#rec1{}, #rec2{}} | {#rec2{}, #rec1{}} is not compatible with {#rec1{}, #rec2{}} - because - at tuple index 1: - {#rec2{}, #rec1{}} is not compatible with {#rec1{}, #rec2{}} - because - #rec2{} is not compatible with #rec1{} - 6 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater_sound.pretty b/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater_sound.pretty index 4ee35cc78d..332e999cda 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater_sound.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater_sound.pretty @@ -14,14 +14,6 @@ Because in the expression's type: However the following candidate: binary() Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - ab() is not compatible with atom() - because - atom() | binary() is not compatible with atom() - because - binary() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater_sound.erl:50:14 │ @@ -38,14 +30,6 @@ Because in the expression's type: However the following candidate: atom() Differs from the expected type: binary() ------------------------------- Detailed message ------------------------------ - - ab() is not compatible with binary() - because - atom() | binary() is not compatible with binary() - because - atom() is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/eqwater_sound.erl:53:28 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/eqwater/unlimited_refinement.pretty b/crates/elp/src/resources/test/eqwalizer_tests/eqwater/unlimited_refinement.pretty index 8b70994f10..c0c48c5e3c 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/eqwater/unlimited_refinement.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/eqwater/unlimited_refinement.pretty @@ -14,12 +14,6 @@ Because in the expression's type: However the following candidate: binary() Differs from the expected type: number() ------------------------------- Detailed message ------------------------------ - - binary() | number() is not compatible with number() - because - binary() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ eqwater/src/unlimited_refinement.erl:94:14 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/fault_tolerance/fault_tolerance.pretty b/crates/elp/src/resources/test/eqwalizer_tests/fault_tolerance/fault_tolerance.pretty index 803ac23f35..554970c163 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/fault_tolerance/fault_tolerance.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/fault_tolerance/fault_tolerance.pretty @@ -416,12 +416,6 @@ Because in the expression's type: Context expects type: atom() ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with [atom()] - because - term() is not compatible with atom() - error: expected_fun_type (See https://fb.me/eqwalizer_errors#expected_fun_type) ┌─ fault_tolerance/src/fault_tolerance.erl:169:8 │ @@ -672,12 +666,6 @@ Because in the expression's type: However the following candidate: [] Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - dynamic() | [] is not compatible with atom() - because - [] is not compatible with atom() - error: expected_fun_type (See https://fb.me/eqwalizer_errors#expected_fun_type) ┌─ fault_tolerance/src/fault_tolerance.erl:254:11 │ diff --git a/crates/elp/src/resources/test/standard/eqwalize_all_bail_on_error_failure.pretty b/crates/elp/src/resources/test/standard/eqwalize_all_bail_on_error_failure.pretty index 08361b2a89..bfbe45f508 100644 --- a/crates/elp/src/resources/test/standard/eqwalize_all_bail_on_error_failure.pretty +++ b/crates/elp/src/resources/test/standard/eqwalize_all_bail_on_error_failure.pretty @@ -44,10 +44,6 @@ Because in the expression's type: Context expects type: #{k_req3 := ..., k_req2 := ..., k_req1 := ..., ...} The type of the expression is missing the following required keys: k_req3, k_req2, k_req1. ------------------------------- Detailed message ------------------------------ - -keys `k_req1`, `k_req2`, `k_req3` are declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a.erl:102:5 │ @@ -68,17 +64,6 @@ Because in the expression's type: , ... } , ... } ------------------------------- Detailed message ------------------------------ - - id(#{a := 'va', b := #{c := #{d => atom()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - at key `b`: - #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{c := #{d => atom()}} is not compatible with #{c := id(#{d := atom(), e := atom()})} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a.erl:125:5 │ @@ -100,17 +85,6 @@ Because in the expression's type: , ... } , ... } ------------------------------- Detailed message ------------------------------ - - id(#{a := 'va', b := #{c := #{d := pid(), e := pid()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - at key `b`: - #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{c := #{d := pid(), e := pid()}} is not compatible with #{c := id(#{d := atom(), e := atom()})} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/test/app_a_SUITE.erl:18:5 │ @@ -179,14 +153,6 @@ Because in the expression's type: No candidate matches in the expected union. ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with [T | X] - because - term() is not compatible with T | X - because - term() is not compatible with T - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a_lists.erl:1305:15 │ @@ -204,12 +170,6 @@ Because in the expression's type: Context expects type: T ) -> boolean() | {'true', X}) ------------------------------- Detailed message ------------------------------ - - fun((T) -> boolean() | {'true', X}) is not compatible with fun((term()) -> boolean() | {'true', term()}) - because - term() is not compatible with T - error: type_alias_is_non_productive (See https://fb.me/eqwalizer_errors#type_alias_is_non_productive) ┌─ app_a/src/app_a_mod2.erl:22:1 │ diff --git a/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics.jsonl b/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics.jsonl index 536452ef3a..8636389098 100644 --- a/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics.jsonl +++ b/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics.jsonl @@ -2,9 +2,9 @@ {"path":"app_a/src/app_a.erl","line":14,"char":5,"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_a/src/app_a.erl","line":18,"char":13,"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/src/app_a.erl","line":56,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: redundant_fixme","original":null,"replacement":null,"description":"```lang=error,counterexample\n\nredundant fixme\n```\n\n> [docs on `redundant_fixme`](https://fb.me/eqwalizer_errors#redundant_fixme)","docPath":null} -{"path":"app_a/src/app_a.erl","line":78,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"X","replacement":null,"description":"```lang=error,counterexample\n`X`.\n\nExpression has type: #{k_extra => term(), k_ok => term(), k_req1 => term(), k_req2 => term(), k_wrong1 => pid(), k_wrong2 => pid()}\nContext expected type: #{k_ok => term(), k_req1 := atom(), k_req2 := atom(), k_req3 := atom(), k_wrong1 => atom(), k_wrong2 => atom()}\n```\n```\nBecause in the expression's type:\n Here the type is: #{k_req2 => ..., k_req1 => ..., ...}\n Context expects type: #{k_req3 := ..., k_req2 := ..., k_req1 := ..., ...}\n The type of the expression is missing the following required keys: k_req3, k_req2, k_req1.\n\n------------------------------ Detailed message ------------------------------\n\nkeys `k_req1`, `k_req2`, `k_req3` are declared as required in the latter but not in the former\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} -{"path":"app_a/src/app_a.erl","line":102,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"X","replacement":null,"description":"```lang=error,counterexample\n`X`.\n\nExpression has type: id(#{a := 'va', b := #{c := #{d => atom()}}})\nContext expected type: #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n```\n```\nBecause in the expression's type:\n #{ b =>\n #{ c =>\n Here the type is: #{d => ..., ...}\n Context expects type: #{d := ..., e := ..., ...}\n The type of the expression is missing the following required keys: d, e.\n , ... }\n , ... }\n\n------------------------------ Detailed message ------------------------------\n\n id(#{a := 'va', b := #{c := #{d => atom()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n at key `b`:\n #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n #{c := #{d => atom()}} is not compatible with #{c := id(#{d := atom(), e := atom()})}\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} -{"path":"app_a/src/app_a.erl","line":125,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"X","replacement":null,"description":"```lang=error,counterexample\n`X`.\n\nExpression has type: id(#{a := 'va', b := #{c := #{d := pid(), e := pid()}}})\nContext expected type: #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n```\n```\nBecause in the expression's type:\n #{ b =>\n #{ c =>\n #{ d =>\n Here the type is: pid()\n Context expects type: atom()\n , ... }\n , ... }\n , ... }\n\n------------------------------ Detailed message ------------------------------\n\n id(#{a := 'va', b := #{c := #{d := pid(), e := pid()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n at key `b`:\n #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n #{c := #{d := pid(), e := pid()}} is not compatible with #{c := id(#{d := atom(), e := atom()})}\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} +{"path":"app_a/src/app_a.erl","line":78,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"X","replacement":null,"description":"```lang=error,counterexample\n`X`.\n\nExpression has type: #{k_extra => term(), k_ok => term(), k_req1 => term(), k_req2 => term(), k_wrong1 => pid(), k_wrong2 => pid()}\nContext expected type: #{k_ok => term(), k_req1 := atom(), k_req2 := atom(), k_req3 := atom(), k_wrong1 => atom(), k_wrong2 => atom()}\n```\n```\nBecause in the expression's type:\n Here the type is: #{k_req2 => ..., k_req1 => ..., ...}\n Context expects type: #{k_req3 := ..., k_req2 := ..., k_req1 := ..., ...}\n The type of the expression is missing the following required keys: k_req3, k_req2, k_req1.\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} +{"path":"app_a/src/app_a.erl","line":102,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"X","replacement":null,"description":"```lang=error,counterexample\n`X`.\n\nExpression has type: id(#{a := 'va', b := #{c := #{d => atom()}}})\nContext expected type: #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n```\n```\nBecause in the expression's type:\n #{ b =>\n #{ c =>\n Here the type is: #{d => ..., ...}\n Context expects type: #{d := ..., e := ..., ...}\n The type of the expression is missing the following required keys: d, e.\n , ... }\n , ... }\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} +{"path":"app_a/src/app_a.erl","line":125,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"X","replacement":null,"description":"```lang=error,counterexample\n`X`.\n\nExpression has type: id(#{a := 'va', b := #{c := #{d := pid(), e := pid()}}})\nContext expected type: #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n```\n```\nBecause in the expression's type:\n #{ b =>\n #{ c =>\n #{ d =>\n Here the type is: pid()\n Context expects type: atom()\n , ... }\n , ... }\n , ... }\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} {"path":"app_a/test/app_a_SUITE.erl","line":18,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"app_a_test_helpers:fail()","replacement":null,"description":"```lang=error,counterexample\n`app_a_test_helpers:fail()`.\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_a/src/app_a_errors_generated.erl","line":8,"char":10,"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: 'foo'\n```\n\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} {"path":"app_a/src/app_a_lists.erl","line":576,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"flatmap(thing_to_list/1, List)","replacement":null,"description":"```lang=error,counterexample\n`flatmap(thing_to_list/1, List)`.\n\nExpression has type: [term()]\nContext expected type: string()\n```\n\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} @@ -12,8 +12,8 @@ {"path":"app_a/src/app_a_lists.erl","line":595,"char":29,"code":"ELP","severity":"error","name":"eqWAlizer: recursive_constraint","original":null,"replacement":null,"description":"```lang=error,counterexample\n\nRecursive constraint: DeepList\n```\n\n> [docs on `recursive_constraint`](https://fb.me/eqwalizer_errors#recursive_constraint)","docPath":null} {"path":"app_a/src/app_a_lists.erl","line":613,"char":29,"code":"ELP","severity":"error","name":"eqWAlizer: recursive_constraint","original":null,"replacement":null,"description":"```lang=error,counterexample\n\nRecursive constraint: DeepList\n```\n\n> [docs on `recursive_constraint`](https://fb.me/eqwalizer_errors#recursive_constraint)","docPath":null} {"path":"app_a/src/app_a_lists.erl","line":1114,"char":36,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"H3","replacement":null,"description":"```lang=error,counterexample\n`H3`.\n\nExpression has type: term()\nContext expected type: [term()]\n```\n\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} -{"path":"app_a/src/app_a_lists.erl","line":1305,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"filtermap(F, L)","replacement":null,"description":"```lang=error,counterexample\n`filtermap(F, L)`.\n\nExpression has type: [term()]\nContext expected type: [T | X]\n```\n```\nBecause in the expression's type:\n [\n Here the type is: term()\n Context expects type: T | X\n No candidate matches in the expected union.\n ]\n\n------------------------------ Detailed message ------------------------------\n\n [term()] is not compatible with [T | X]\n because\n term() is not compatible with T | X\n because\n term() is not compatible with T\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} -{"path":"app_a/src/app_a_lists.erl","line":1305,"char":15,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"F","replacement":null,"description":"```lang=error,counterexample\n`F`.\n\nExpression has type: fun((T) -> boolean() | {'true', X})\nContext expected type: fun((term()) -> boolean() | {'true', term()})\n```\n```\nBecause in the expression's type:\n fun((\n Here the type is: term()\n Context expects type: T\n ) -> boolean() | {'true', X})\n\n------------------------------ Detailed message ------------------------------\n\n fun((T) -> boolean() | {'true', X}) is not compatible with fun((term()) -> boolean() | {'true', term()})\n because\n term() is not compatible with T\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} +{"path":"app_a/src/app_a_lists.erl","line":1305,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"filtermap(F, L)","replacement":null,"description":"```lang=error,counterexample\n`filtermap(F, L)`.\n\nExpression has type: [term()]\nContext expected type: [T | X]\n```\n```\nBecause in the expression's type:\n [\n Here the type is: term()\n Context expects type: T | X\n No candidate matches in the expected union.\n ]\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} +{"path":"app_a/src/app_a_lists.erl","line":1305,"char":15,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"F","replacement":null,"description":"```lang=error,counterexample\n`F`.\n\nExpression has type: fun((T) -> boolean() | {'true', X})\nContext expected type: fun((term()) -> boolean() | {'true', term()})\n```\n```\nBecause in the expression's type:\n fun((\n Here the type is: term()\n Context expects type: T\n ) -> boolean() | {'true', X})\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} {"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} diff --git a/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics.pretty b/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics.pretty index 08361b2a89..bfbe45f508 100644 --- a/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics.pretty +++ b/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics.pretty @@ -44,10 +44,6 @@ Because in the expression's type: Context expects type: #{k_req3 := ..., k_req2 := ..., k_req1 := ..., ...} The type of the expression is missing the following required keys: k_req3, k_req2, k_req1. ------------------------------- Detailed message ------------------------------ - -keys `k_req1`, `k_req2`, `k_req3` are declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a.erl:102:5 │ @@ -68,17 +64,6 @@ Because in the expression's type: , ... } , ... } ------------------------------- Detailed message ------------------------------ - - id(#{a := 'va', b := #{c := #{d => atom()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - at key `b`: - #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{c := #{d => atom()}} is not compatible with #{c := id(#{d := atom(), e := atom()})} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a.erl:125:5 │ @@ -100,17 +85,6 @@ Because in the expression's type: , ... } , ... } ------------------------------- Detailed message ------------------------------ - - id(#{a := 'va', b := #{c := #{d := pid(), e := pid()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - at key `b`: - #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{c := #{d := pid(), e := pid()}} is not compatible with #{c := id(#{d := atom(), e := atom()})} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/test/app_a_SUITE.erl:18:5 │ @@ -179,14 +153,6 @@ Because in the expression's type: No candidate matches in the expected union. ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with [T | X] - because - term() is not compatible with T | X - because - term() is not compatible with T - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a_lists.erl:1305:15 │ @@ -204,12 +170,6 @@ Because in the expression's type: Context expects type: T ) -> boolean() | {'true', X}) ------------------------------- Detailed message ------------------------------ - - fun((T) -> boolean() | {'true', X}) is not compatible with fun((term()) -> boolean() | {'true', term()}) - because - term() is not compatible with T - error: type_alias_is_non_productive (See https://fb.me/eqwalizer_errors#type_alias_is_non_productive) ┌─ app_a/src/app_a_mod2.erl:22:1 │ diff --git a/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics_gen.jsonl b/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics_gen.jsonl index 536452ef3a..8636389098 100644 --- a/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics_gen.jsonl +++ b/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics_gen.jsonl @@ -2,9 +2,9 @@ {"path":"app_a/src/app_a.erl","line":14,"char":5,"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_a/src/app_a.erl","line":18,"char":13,"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/src/app_a.erl","line":56,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: redundant_fixme","original":null,"replacement":null,"description":"```lang=error,counterexample\n\nredundant fixme\n```\n\n> [docs on `redundant_fixme`](https://fb.me/eqwalizer_errors#redundant_fixme)","docPath":null} -{"path":"app_a/src/app_a.erl","line":78,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"X","replacement":null,"description":"```lang=error,counterexample\n`X`.\n\nExpression has type: #{k_extra => term(), k_ok => term(), k_req1 => term(), k_req2 => term(), k_wrong1 => pid(), k_wrong2 => pid()}\nContext expected type: #{k_ok => term(), k_req1 := atom(), k_req2 := atom(), k_req3 := atom(), k_wrong1 => atom(), k_wrong2 => atom()}\n```\n```\nBecause in the expression's type:\n Here the type is: #{k_req2 => ..., k_req1 => ..., ...}\n Context expects type: #{k_req3 := ..., k_req2 := ..., k_req1 := ..., ...}\n The type of the expression is missing the following required keys: k_req3, k_req2, k_req1.\n\n------------------------------ Detailed message ------------------------------\n\nkeys `k_req1`, `k_req2`, `k_req3` are declared as required in the latter but not in the former\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} -{"path":"app_a/src/app_a.erl","line":102,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"X","replacement":null,"description":"```lang=error,counterexample\n`X`.\n\nExpression has type: id(#{a := 'va', b := #{c := #{d => atom()}}})\nContext expected type: #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n```\n```\nBecause in the expression's type:\n #{ b =>\n #{ c =>\n Here the type is: #{d => ..., ...}\n Context expects type: #{d := ..., e := ..., ...}\n The type of the expression is missing the following required keys: d, e.\n , ... }\n , ... }\n\n------------------------------ Detailed message ------------------------------\n\n id(#{a := 'va', b := #{c := #{d => atom()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n at key `b`:\n #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n #{c := #{d => atom()}} is not compatible with #{c := id(#{d := atom(), e := atom()})}\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} -{"path":"app_a/src/app_a.erl","line":125,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"X","replacement":null,"description":"```lang=error,counterexample\n`X`.\n\nExpression has type: id(#{a := 'va', b := #{c := #{d := pid(), e := pid()}}})\nContext expected type: #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n```\n```\nBecause in the expression's type:\n #{ b =>\n #{ c =>\n #{ d =>\n Here the type is: pid()\n Context expects type: atom()\n , ... }\n , ... }\n , ... }\n\n------------------------------ Detailed message ------------------------------\n\n id(#{a := 'va', b := #{c := #{d := pid(), e := pid()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n at key `b`:\n #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n #{c := #{d := pid(), e := pid()}} is not compatible with #{c := id(#{d := atom(), e := atom()})}\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} +{"path":"app_a/src/app_a.erl","line":78,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"X","replacement":null,"description":"```lang=error,counterexample\n`X`.\n\nExpression has type: #{k_extra => term(), k_ok => term(), k_req1 => term(), k_req2 => term(), k_wrong1 => pid(), k_wrong2 => pid()}\nContext expected type: #{k_ok => term(), k_req1 := atom(), k_req2 := atom(), k_req3 := atom(), k_wrong1 => atom(), k_wrong2 => atom()}\n```\n```\nBecause in the expression's type:\n Here the type is: #{k_req2 => ..., k_req1 => ..., ...}\n Context expects type: #{k_req3 := ..., k_req2 := ..., k_req1 := ..., ...}\n The type of the expression is missing the following required keys: k_req3, k_req2, k_req1.\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} +{"path":"app_a/src/app_a.erl","line":102,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"X","replacement":null,"description":"```lang=error,counterexample\n`X`.\n\nExpression has type: id(#{a := 'va', b := #{c := #{d => atom()}}})\nContext expected type: #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n```\n```\nBecause in the expression's type:\n #{ b =>\n #{ c =>\n Here the type is: #{d => ..., ...}\n Context expects type: #{d := ..., e := ..., ...}\n The type of the expression is missing the following required keys: d, e.\n , ... }\n , ... }\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} +{"path":"app_a/src/app_a.erl","line":125,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"X","replacement":null,"description":"```lang=error,counterexample\n`X`.\n\nExpression has type: id(#{a := 'va', b := #{c := #{d := pid(), e := pid()}}})\nContext expected type: #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n```\n```\nBecause in the expression's type:\n #{ b =>\n #{ c =>\n #{ d =>\n Here the type is: pid()\n Context expects type: atom()\n , ... }\n , ... }\n , ... }\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} {"path":"app_a/test/app_a_SUITE.erl","line":18,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"app_a_test_helpers:fail()","replacement":null,"description":"```lang=error,counterexample\n`app_a_test_helpers:fail()`.\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_a/src/app_a_errors_generated.erl","line":8,"char":10,"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: 'foo'\n```\n\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} {"path":"app_a/src/app_a_lists.erl","line":576,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"flatmap(thing_to_list/1, List)","replacement":null,"description":"```lang=error,counterexample\n`flatmap(thing_to_list/1, List)`.\n\nExpression has type: [term()]\nContext expected type: string()\n```\n\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} @@ -12,8 +12,8 @@ {"path":"app_a/src/app_a_lists.erl","line":595,"char":29,"code":"ELP","severity":"error","name":"eqWAlizer: recursive_constraint","original":null,"replacement":null,"description":"```lang=error,counterexample\n\nRecursive constraint: DeepList\n```\n\n> [docs on `recursive_constraint`](https://fb.me/eqwalizer_errors#recursive_constraint)","docPath":null} {"path":"app_a/src/app_a_lists.erl","line":613,"char":29,"code":"ELP","severity":"error","name":"eqWAlizer: recursive_constraint","original":null,"replacement":null,"description":"```lang=error,counterexample\n\nRecursive constraint: DeepList\n```\n\n> [docs on `recursive_constraint`](https://fb.me/eqwalizer_errors#recursive_constraint)","docPath":null} {"path":"app_a/src/app_a_lists.erl","line":1114,"char":36,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"H3","replacement":null,"description":"```lang=error,counterexample\n`H3`.\n\nExpression has type: term()\nContext expected type: [term()]\n```\n\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} -{"path":"app_a/src/app_a_lists.erl","line":1305,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"filtermap(F, L)","replacement":null,"description":"```lang=error,counterexample\n`filtermap(F, L)`.\n\nExpression has type: [term()]\nContext expected type: [T | X]\n```\n```\nBecause in the expression's type:\n [\n Here the type is: term()\n Context expects type: T | X\n No candidate matches in the expected union.\n ]\n\n------------------------------ Detailed message ------------------------------\n\n [term()] is not compatible with [T | X]\n because\n term() is not compatible with T | X\n because\n term() is not compatible with T\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} -{"path":"app_a/src/app_a_lists.erl","line":1305,"char":15,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"F","replacement":null,"description":"```lang=error,counterexample\n`F`.\n\nExpression has type: fun((T) -> boolean() | {'true', X})\nContext expected type: fun((term()) -> boolean() | {'true', term()})\n```\n```\nBecause in the expression's type:\n fun((\n Here the type is: term()\n Context expects type: T\n ) -> boolean() | {'true', X})\n\n------------------------------ Detailed message ------------------------------\n\n fun((T) -> boolean() | {'true', X}) is not compatible with fun((term()) -> boolean() | {'true', term()})\n because\n term() is not compatible with T\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} +{"path":"app_a/src/app_a_lists.erl","line":1305,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"filtermap(F, L)","replacement":null,"description":"```lang=error,counterexample\n`filtermap(F, L)`.\n\nExpression has type: [term()]\nContext expected type: [T | X]\n```\n```\nBecause in the expression's type:\n [\n Here the type is: term()\n Context expects type: T | X\n No candidate matches in the expected union.\n ]\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} +{"path":"app_a/src/app_a_lists.erl","line":1305,"char":15,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"F","replacement":null,"description":"```lang=error,counterexample\n`F`.\n\nExpression has type: fun((T) -> boolean() | {'true', X})\nContext expected type: fun((term()) -> boolean() | {'true', term()})\n```\n```\nBecause in the expression's type:\n fun((\n Here the type is: term()\n Context expects type: T\n ) -> boolean() | {'true', X})\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} {"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} diff --git a/crates/elp/src/resources/test/standard/eqwalize_app_a.jsonl b/crates/elp/src/resources/test/standard/eqwalize_app_a.jsonl index f56ea404dc..943c4a231d 100644 --- a/crates/elp/src/resources/test/standard/eqwalize_app_a.jsonl +++ b/crates/elp/src/resources/test/standard/eqwalize_app_a.jsonl @@ -2,6 +2,6 @@ {"path":"app_a/src/app_a.erl","line":14,"char":5,"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_a/src/app_a.erl","line":18,"char":13,"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/src/app_a.erl","line":56,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: redundant_fixme","original":null,"replacement":null,"description":"```lang=error,counterexample\n\nredundant fixme\n```\n\n> [docs on `redundant_fixme`](https://fb.me/eqwalizer_errors#redundant_fixme)","docPath":null} -{"path":"app_a/src/app_a.erl","line":78,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"X","replacement":null,"description":"```lang=error,counterexample\n`X`.\n\nExpression has type: #{k_extra => term(), k_ok => term(), k_req1 => term(), k_req2 => term(), k_wrong1 => pid(), k_wrong2 => pid()}\nContext expected type: #{k_ok => term(), k_req1 := atom(), k_req2 := atom(), k_req3 := atom(), k_wrong1 => atom(), k_wrong2 => atom()}\n```\n```\nBecause in the expression's type:\n Here the type is: #{k_req2 => ..., k_req1 => ..., ...}\n Context expects type: #{k_req3 := ..., k_req2 := ..., k_req1 := ..., ...}\n The type of the expression is missing the following required keys: k_req3, k_req2, k_req1.\n\n------------------------------ Detailed message ------------------------------\n\nkeys `k_req1`, `k_req2`, `k_req3` are declared as required in the latter but not in the former\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} -{"path":"app_a/src/app_a.erl","line":102,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"X","replacement":null,"description":"```lang=error,counterexample\n`X`.\n\nExpression has type: id(#{a := 'va', b := #{c := #{d => atom()}}})\nContext expected type: #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n```\n```\nBecause in the expression's type:\n #{ b =>\n #{ c =>\n Here the type is: #{d => ..., ...}\n Context expects type: #{d := ..., e := ..., ...}\n The type of the expression is missing the following required keys: d, e.\n , ... }\n , ... }\n\n------------------------------ Detailed message ------------------------------\n\n id(#{a := 'va', b := #{c := #{d => atom()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n at key `b`:\n #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n #{c := #{d => atom()}} is not compatible with #{c := id(#{d := atom(), e := atom()})}\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} -{"path":"app_a/src/app_a.erl","line":125,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"X","replacement":null,"description":"```lang=error,counterexample\n`X`.\n\nExpression has type: id(#{a := 'va', b := #{c := #{d := pid(), e := pid()}}})\nContext expected type: #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n```\n```\nBecause in the expression's type:\n #{ b =>\n #{ c =>\n #{ d =>\n Here the type is: pid()\n Context expects type: atom()\n , ... }\n , ... }\n , ... }\n\n------------------------------ Detailed message ------------------------------\n\n id(#{a := 'va', b := #{c := #{d := pid(), e := pid()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n at key `b`:\n #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n #{c := #{d := pid(), e := pid()}} is not compatible with #{c := id(#{d := atom(), e := atom()})}\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} +{"path":"app_a/src/app_a.erl","line":78,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"X","replacement":null,"description":"```lang=error,counterexample\n`X`.\n\nExpression has type: #{k_extra => term(), k_ok => term(), k_req1 => term(), k_req2 => term(), k_wrong1 => pid(), k_wrong2 => pid()}\nContext expected type: #{k_ok => term(), k_req1 := atom(), k_req2 := atom(), k_req3 := atom(), k_wrong1 => atom(), k_wrong2 => atom()}\n```\n```\nBecause in the expression's type:\n Here the type is: #{k_req2 => ..., k_req1 => ..., ...}\n Context expects type: #{k_req3 := ..., k_req2 := ..., k_req1 := ..., ...}\n The type of the expression is missing the following required keys: k_req3, k_req2, k_req1.\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} +{"path":"app_a/src/app_a.erl","line":102,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"X","replacement":null,"description":"```lang=error,counterexample\n`X`.\n\nExpression has type: id(#{a := 'va', b := #{c := #{d => atom()}}})\nContext expected type: #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n```\n```\nBecause in the expression's type:\n #{ b =>\n #{ c =>\n Here the type is: #{d => ..., ...}\n Context expects type: #{d := ..., e := ..., ...}\n The type of the expression is missing the following required keys: d, e.\n , ... }\n , ... }\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} +{"path":"app_a/src/app_a.erl","line":125,"char":5,"code":"ELP","severity":"error","name":"eqWAlizer: incompatible_types","original":"X","replacement":null,"description":"```lang=error,counterexample\n`X`.\n\nExpression has type: id(#{a := 'va', b := #{c := #{d := pid(), e := pid()}}})\nContext expected type: #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n```\n```\nBecause in the expression's type:\n #{ b =>\n #{ c =>\n #{ d =>\n Here the type is: pid()\n Context expects type: atom()\n , ... }\n , ... }\n , ... }\n```\n> [docs on `incompatible_types`](https://fb.me/eqwalizer_errors#incompatible_types)","docPath":null} diff --git a/crates/elp/src/resources/test/standard/eqwalize_app_a.pretty b/crates/elp/src/resources/test/standard/eqwalize_app_a.pretty index 3cfdbd0dc2..f5f3065414 100644 --- a/crates/elp/src/resources/test/standard/eqwalize_app_a.pretty +++ b/crates/elp/src/resources/test/standard/eqwalize_app_a.pretty @@ -44,10 +44,6 @@ Because in the expression's type: Context expects type: #{k_req3 := ..., k_req2 := ..., k_req1 := ..., ...} The type of the expression is missing the following required keys: k_req3, k_req2, k_req1. ------------------------------- Detailed message ------------------------------ - -keys `k_req1`, `k_req2`, `k_req3` are declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a.erl:102:5 │ @@ -68,17 +64,6 @@ Because in the expression's type: , ... } , ... } ------------------------------- Detailed message ------------------------------ - - id(#{a := 'va', b := #{c := #{d => atom()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - at key `b`: - #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{c := #{d => atom()}} is not compatible with #{c := id(#{d := atom(), e := atom()})} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a.erl:125:5 │ @@ -100,15 +85,4 @@ Because in the expression's type: , ... } , ... } ------------------------------- Detailed message ------------------------------ - - id(#{a := 'va', b := #{c := #{d := pid(), e := pid()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - at key `b`: - #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{c := #{d := pid(), e := pid()}} is not compatible with #{c := id(#{d := atom(), e := atom()})} - 7 ERRORS diff --git a/crates/elp/src/resources/test/standard/eqwalize_app_a_lists_fast.pretty b/crates/elp/src/resources/test/standard/eqwalize_app_a_lists_fast.pretty index f61a47edfe..8c93bd01fe 100644 --- a/crates/elp/src/resources/test/standard/eqwalize_app_a_lists_fast.pretty +++ b/crates/elp/src/resources/test/standard/eqwalize_app_a_lists_fast.pretty @@ -50,14 +50,6 @@ Because in the expression's type: No candidate matches in the expected union. ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with [T | X] - because - term() is not compatible with T | X - because - term() is not compatible with T - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a_lists.erl:1305:15 │ @@ -75,10 +67,4 @@ Because in the expression's type: Context expects type: T ) -> boolean() | {'true', X}) ------------------------------- Detailed message ------------------------------ - - fun((T) -> boolean() | {'true', X}) is not compatible with fun((term()) -> boolean() | {'true', term()}) - because - term() is not compatible with T - 7 ERRORS diff --git a/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics.pretty b/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics.pretty index 472665ffe8..482195c7ab 100644 --- a/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics.pretty +++ b/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics.pretty @@ -44,10 +44,6 @@ Because in the expression's type: Context expects type: #{k_req3 := ..., k_req2 := ..., k_req1 := ..., ...} The type of the expression is missing the following required keys: k_req3, k_req2, k_req1. ------------------------------- Detailed message ------------------------------ - -keys `k_req1`, `k_req2`, `k_req3` are declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a.erl:102:5 │ @@ -68,17 +64,6 @@ Because in the expression's type: , ... } , ... } ------------------------------- Detailed message ------------------------------ - - id(#{a := 'va', b := #{c := #{d => atom()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - at key `b`: - #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{c := #{d => atom()}} is not compatible with #{c := id(#{d := atom(), e := atom()})} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a.erl:125:5 │ @@ -100,17 +85,6 @@ Because in the expression's type: , ... } , ... } ------------------------------- Detailed message ------------------------------ - - id(#{a := 'va', b := #{c := #{d := pid(), e := pid()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - at key `b`: - #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{c := #{d := pid(), e := pid()}} is not compatible with #{c := id(#{d := atom(), e := atom()})} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a_errors_generated.erl:8:10 │ @@ -171,14 +145,6 @@ Because in the expression's type: No candidate matches in the expected union. ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with [T | X] - because - term() is not compatible with T | X - because - term() is not compatible with T - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a_lists.erl:1305:15 │ @@ -196,12 +162,6 @@ Because in the expression's type: Context expects type: T ) -> boolean() | {'true', X}) ------------------------------- Detailed message ------------------------------ - - fun((T) -> boolean() | {'true', X}) is not compatible with fun((term()) -> boolean() | {'true', term()}) - because - term() is not compatible with T - error: type_alias_is_non_productive (See https://fb.me/eqwalizer_errors#type_alias_is_non_productive) ┌─ app_a/src/app_a_mod2.erl:22:1 │ diff --git a/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics_gen.pretty b/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics_gen.pretty index 472665ffe8..482195c7ab 100644 --- a/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics_gen.pretty +++ b/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics_gen.pretty @@ -44,10 +44,6 @@ Because in the expression's type: Context expects type: #{k_req3 := ..., k_req2 := ..., k_req1 := ..., ...} The type of the expression is missing the following required keys: k_req3, k_req2, k_req1. ------------------------------- Detailed message ------------------------------ - -keys `k_req1`, `k_req2`, `k_req3` are declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a.erl:102:5 │ @@ -68,17 +64,6 @@ Because in the expression's type: , ... } , ... } ------------------------------- Detailed message ------------------------------ - - id(#{a := 'va', b := #{c := #{d => atom()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - at key `b`: - #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{c := #{d => atom()}} is not compatible with #{c := id(#{d := atom(), e := atom()})} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a.erl:125:5 │ @@ -100,17 +85,6 @@ Because in the expression's type: , ... } , ... } ------------------------------- Detailed message ------------------------------ - - id(#{a := 'va', b := #{c := #{d := pid(), e := pid()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - at key `b`: - #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{c := #{d := pid(), e := pid()}} is not compatible with #{c := id(#{d := atom(), e := atom()})} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a_errors_generated.erl:8:10 │ @@ -171,14 +145,6 @@ Because in the expression's type: No candidate matches in the expected union. ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with [T | X] - because - term() is not compatible with T | X - because - term() is not compatible with T - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a_lists.erl:1305:15 │ @@ -196,12 +162,6 @@ Because in the expression's type: Context expects type: T ) -> boolean() | {'true', X}) ------------------------------- Detailed message ------------------------------ - - fun((T) -> boolean() | {'true', X}) is not compatible with fun((term()) -> boolean() | {'true', term()}) - because - term() is not compatible with T - error: type_alias_is_non_productive (See https://fb.me/eqwalizer_errors#type_alias_is_non_productive) ┌─ app_a/src/app_a_mod2.erl:22:1 │ diff --git a/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics_gen_rebar.pretty b/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics_gen_rebar.pretty index 6782313a4a..67d78913b6 100644 --- a/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics_gen_rebar.pretty +++ b/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics_gen_rebar.pretty @@ -44,10 +44,6 @@ Because in the expression's type: Context expects type: #{k_req3 := ..., k_req2 := ..., k_req1 := ..., ...} The type of the expression is missing the following required keys: k_req3, k_req2, k_req1. ------------------------------- Detailed message ------------------------------ - -keys `k_req1`, `k_req2`, `k_req3` are declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a.erl:102:5 │ @@ -68,17 +64,6 @@ Because in the expression's type: , ... } , ... } ------------------------------- Detailed message ------------------------------ - - id(#{a := 'va', b := #{c := #{d => atom()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - at key `b`: - #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{c := #{d => atom()}} is not compatible with #{c := id(#{d := atom(), e := atom()})} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a.erl:125:5 │ @@ -100,17 +85,6 @@ Because in the expression's type: , ... } , ... } ------------------------------- Detailed message ------------------------------ - - id(#{a := 'va', b := #{c := #{d := pid(), e := pid()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - at key `b`: - #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{c := #{d := pid(), e := pid()}} is not compatible with #{c := id(#{d := atom(), e := atom()})} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/test/app_a_SUITE.erl:18:5 │ @@ -179,14 +153,6 @@ Because in the expression's type: No candidate matches in the expected union. ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with [T | X] - because - term() is not compatible with T | X - because - term() is not compatible with T - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a_lists.erl:1305:15 │ @@ -204,12 +170,6 @@ Because in the expression's type: Context expects type: T ) -> boolean() | {'true', X}) ------------------------------- Detailed message ------------------------------ - - fun((T) -> boolean() | {'true', X}) is not compatible with fun((term()) -> boolean() | {'true', term()}) - because - term() is not compatible with T - error: type_alias_is_non_productive (See https://fb.me/eqwalizer_errors#type_alias_is_non_productive) ┌─ app_a/src/app_a_mod2.erl:22:1 │ diff --git a/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics_rebar.pretty b/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics_rebar.pretty index 6782313a4a..67d78913b6 100644 --- a/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics_rebar.pretty +++ b/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics_rebar.pretty @@ -44,10 +44,6 @@ Because in the expression's type: Context expects type: #{k_req3 := ..., k_req2 := ..., k_req1 := ..., ...} The type of the expression is missing the following required keys: k_req3, k_req2, k_req1. ------------------------------- Detailed message ------------------------------ - -keys `k_req1`, `k_req2`, `k_req3` are declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a.erl:102:5 │ @@ -68,17 +64,6 @@ Because in the expression's type: , ... } , ... } ------------------------------- Detailed message ------------------------------ - - id(#{a := 'va', b := #{c := #{d => atom()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - at key `b`: - #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{c := #{d => atom()}} is not compatible with #{c := id(#{d := atom(), e := atom()})} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a.erl:125:5 │ @@ -100,17 +85,6 @@ Because in the expression's type: , ... } , ... } ------------------------------- Detailed message ------------------------------ - - id(#{a := 'va', b := #{c := #{d := pid(), e := pid()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - at key `b`: - #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{c := #{d := pid(), e := pid()}} is not compatible with #{c := id(#{d := atom(), e := atom()})} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/test/app_a_SUITE.erl:18:5 │ @@ -179,14 +153,6 @@ Because in the expression's type: No candidate matches in the expected union. ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with [T | X] - because - term() is not compatible with T | X - because - term() is not compatible with T - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a_lists.erl:1305:15 │ @@ -204,12 +170,6 @@ Because in the expression's type: Context expects type: T ) -> boolean() | {'true', X}) ------------------------------- Detailed message ------------------------------ - - fun((T) -> boolean() | {'true', X}) is not compatible with fun((term()) -> boolean() | {'true', term()}) - because - term() is not compatible with T - error: type_alias_is_non_productive (See https://fb.me/eqwalizer_errors#type_alias_is_non_productive) ┌─ app_a/src/app_a_mod2.erl:22:1 │ diff --git a/crates/elp/src/resources/test/standard/eqwalize_target_diagnostics.pretty b/crates/elp/src/resources/test/standard/eqwalize_target_diagnostics.pretty index 472665ffe8..482195c7ab 100644 --- a/crates/elp/src/resources/test/standard/eqwalize_target_diagnostics.pretty +++ b/crates/elp/src/resources/test/standard/eqwalize_target_diagnostics.pretty @@ -44,10 +44,6 @@ Because in the expression's type: Context expects type: #{k_req3 := ..., k_req2 := ..., k_req1 := ..., ...} The type of the expression is missing the following required keys: k_req3, k_req2, k_req1. ------------------------------- Detailed message ------------------------------ - -keys `k_req1`, `k_req2`, `k_req3` are declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a.erl:102:5 │ @@ -68,17 +64,6 @@ Because in the expression's type: , ... } , ... } ------------------------------- Detailed message ------------------------------ - - id(#{a := 'va', b := #{c := #{d => atom()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - at key `b`: - #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{c := #{d => atom()}} is not compatible with #{c := id(#{d := atom(), e := atom()})} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a.erl:125:5 │ @@ -100,17 +85,6 @@ Because in the expression's type: , ... } , ... } ------------------------------- Detailed message ------------------------------ - - id(#{a := 'va', b := #{c := #{d := pid(), e := pid()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - at key `b`: - #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}} - because - #{c := #{d := pid(), e := pid()}} is not compatible with #{c := id(#{d := atom(), e := atom()})} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a_errors_generated.erl:8:10 │ @@ -171,14 +145,6 @@ Because in the expression's type: No candidate matches in the expected union. ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with [T | X] - because - term() is not compatible with T | X - because - term() is not compatible with T - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ app_a/src/app_a_lists.erl:1305:15 │ @@ -196,12 +162,6 @@ Because in the expression's type: Context expects type: T ) -> boolean() | {'true', X}) ------------------------------- Detailed message ------------------------------ - - fun((T) -> boolean() | {'true', X}) is not compatible with fun((term()) -> boolean() | {'true', term()}) - because - term() is not compatible with T - error: type_alias_is_non_productive (See https://fb.me/eqwalizer_errors#type_alias_is_non_productive) ┌─ app_a/src/app_a_mod2.erl:22:1 │ diff --git a/crates/elp/tests/slow-tests/main.rs b/crates/elp/tests/slow-tests/main.rs index 98455e8850..60167593fd 100644 --- a/crates/elp/tests/slow-tests/main.rs +++ b/crates/elp/tests/slow-tests/main.rs @@ -268,7 +268,7 @@ fn test_e2e_eqwalizer_module() { "codeDescription": { "href": "https://fb.me/eqwalizer_errors#incompatible_types" }, - "message": "`X`.\nExpression has type: #{k_extra => term(), k_ok => term(), k_req1 => term(), k_req2 => term(), k_wrong1 => pid(), k_wrong2 => pid()}\nContext expected type: #{k_ok => term(), k_req1 := atom(), k_req2 := atom(), k_req3 := atom(), k_wrong1 => atom(), k_wrong2 => atom()}\n\nBecause in the expression's type:\n Here the type is: #{k_req2 => ..., k_req1 => ..., ...}\n Context expects type: #{k_req3 := ..., k_req2 := ..., k_req1 := ..., ...}\n The type of the expression is missing the following required keys: k_req3, k_req2, k_req1.\n\n------------------------------ Detailed message ------------------------------\n\nkeys `k_req1`, `k_req2`, `k_req3` are declared as required in the latter but not in the former\n See https://fb.me/eqwalizer_errors#incompatible_types", + "message": "`X`.\nExpression has type: #{k_extra => term(), k_ok => term(), k_req1 => term(), k_req2 => term(), k_wrong1 => pid(), k_wrong2 => pid()}\nContext expected type: #{k_ok => term(), k_req1 := atom(), k_req2 := atom(), k_req3 := atom(), k_wrong1 => atom(), k_wrong2 => atom()}\n\nBecause in the expression's type:\n Here the type is: #{k_req2 => ..., k_req1 => ..., ...}\n Context expects type: #{k_req3 := ..., k_req2 := ..., k_req1 := ..., ...}\n The type of the expression is missing the following required keys: k_req3, k_req2, k_req1.\n See https://fb.me/eqwalizer_errors#incompatible_types", "range": { "end": { "character": 5, @@ -287,7 +287,7 @@ fn test_e2e_eqwalizer_module() { "codeDescription": { "href": "https://fb.me/eqwalizer_errors#incompatible_types" }, - "message": "`X`.\nExpression has type: id(#{a := 'va', b := #{c := #{d => atom()}}})\nContext expected type: #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n\nBecause in the expression's type:\n #{ b =>\n #{ c =>\n Here the type is: #{d => ..., ...}\n Context expects type: #{d := ..., e := ..., ...}\n The type of the expression is missing the following required keys: d, e.\n , ... }\n , ... }\n\n------------------------------ Detailed message ------------------------------\n\n id(#{a := 'va', b := #{c := #{d => atom()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n at key `b`:\n #{a := 'va', b := #{c := #{d => atom()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n #{c := #{d => atom()}} is not compatible with #{c := id(#{d := atom(), e := atom()})}\n See https://fb.me/eqwalizer_errors#incompatible_types", + "message": "`X`.\nExpression has type: id(#{a := 'va', b := #{c := #{d => atom()}}})\nContext expected type: #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n\nBecause in the expression's type:\n #{ b =>\n #{ c =>\n Here the type is: #{d => ..., ...}\n Context expects type: #{d := ..., e := ..., ...}\n The type of the expression is missing the following required keys: d, e.\n , ... }\n , ... }\n See https://fb.me/eqwalizer_errors#incompatible_types", "range": { "end": { "character": 5, @@ -306,7 +306,7 @@ fn test_e2e_eqwalizer_module() { "codeDescription": { "href": "https://fb.me/eqwalizer_errors#incompatible_types" }, - "message": "`X`.\nExpression has type: id(#{a := 'va', b := #{c := #{d := pid(), e := pid()}}})\nContext expected type: #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n\nBecause in the expression's type:\n #{ b =>\n #{ c =>\n #{ d =>\n Here the type is: pid()\n Context expects type: atom()\n , ... }\n , ... }\n , ... }\n\n------------------------------ Detailed message ------------------------------\n\n id(#{a := 'va', b := #{c := #{d := pid(), e := pid()}}}) is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n at key `b`:\n #{a := 'va', b := #{c := #{d := pid(), e := pid()}}} is not compatible with #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n because\n #{c := #{d := pid(), e := pid()}} is not compatible with #{c := id(#{d := atom(), e := atom()})}\n See https://fb.me/eqwalizer_errors#incompatible_types", + "message": "`X`.\nExpression has type: id(#{a := 'va', b := #{c := #{d := pid(), e := pid()}}})\nContext expected type: #{a := 'va', b := #{c := id(#{d := atom(), e := atom()})}}\n\nBecause in the expression's type:\n #{ b =>\n #{ c =>\n #{ d =>\n Here the type is: pid()\n Context expects type: atom()\n , ... }\n , ... }\n , ... }\n See https://fb.me/eqwalizer_errors#incompatible_types", "range": { "end": { "character": 5, From c404d942df248cd99cb2c2a8537c2f306006512f Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Tue, 9 Dec 2025 07:58:59 -0800 Subject: [PATCH 080/142] Update emacs eglot instructions for semantic tokens (#138) Summary: Pull Request resolved: https://github.com/whatsapp/erlang-language-platform/pull/138 Reviewed By: michalmuskala Differential Revision: D88746400 Pulled By: alanz fbshipit-source-id: 0bfda35e25ebf62b62ec7f8209d1787fdbb58e80 --- website/docs/get-started/editors/emacs.md | 59 +++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/website/docs/get-started/editors/emacs.md b/website/docs/get-started/editors/emacs.md index 28b1c63e24..072e6902da 100644 --- a/website/docs/get-started/editors/emacs.md +++ b/website/docs/get-started/editors/emacs.md @@ -18,6 +18,7 @@ installed with the `eglot` package. ```elisp (use-package eglot + :ensure t :hook ((erlang-mode . eglot-ensure)) :config @@ -34,6 +35,64 @@ Refer to the [manual](https://elpa.gnu.org/devel/doc/eglot.html#Customization-Variables) for additional configuration options. +### Semantic tokens + +Semantic token support has been added to eglot, but is not yet in the +released version. But it is possible to install the updated version +of eglot. + +To do so, add + +```elisp +(add-to-list 'package-archives '("gnu-devel" . "https://elpa.gnu.org/devel/")) +``` + +to your `init.el`, then run `M-x eglot-upgrade-eglot` + +Once upgraded, add the following to the `(use-package` entry for `eglot` + +```elisp + (setq eglot-semantic-token-modifiers ' + ("bound" "exported_function" "exported_type" "deprecated_function" "type_dynamic")) + + ;; Each face name arises as a template from the modifiers as + ;; "eglot-semantic-%s-face" + (defface eglot-semantic-bound-face + '((t :underline t)) + "The face modification to use for bound variables in patterns." + :group 'eglot-semantic-semantic-tokens) + + (defface eglot-semantic-exported_function-face + '((t :underline t)) + "The face modification to use for exported functions." + :group 'eglot-semantic-semantic-tokens) + + (defface eglot-semantic-exported_type-face + '((t :underline t)) + "The face modification to use for exported types." + :group 'eglot-semantic-semantic-tokens) + + (defface eglot-semantic-deprecated_function-face + '((t :strike-through t)) + "The face modification to use for deprecated functions." + :group 'eglot-semantic-semantic-tokens) + + (defface eglot-semantic-type_dynamic-face + '((t (:weight bold))) + "The face modification to use for dynamic types." + :group 'eglot-semantic-semantic-tokens) + + ;; Bare eglot makes the refresh a no-op. Provide our own version for + ;; when Eqwalizer gets its results. + (cl-defmethod eglot-handle-request + (server (_method (eql workspace/semanticTokens/refresh)) &rest args) + "Handle workspace/semanticTokens/refresh by refreshing font-lock." + (dolist (buffer (eglot--managed-buffers server)) + (eglot--when-live-buffer buffer + (eglot--widening (font-lock-flush))))) +``` + + ## lsp-mode Install the `lsp-mode` package, which is a generic Emacs client for LSP servers. From debed39b4261b4d7380e3a32d8d9b4ecf2c59120 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Tue, 9 Dec 2025 08:47:59 -0800 Subject: [PATCH 081/142] streaming results for elp lint Summary: At the moment the `elp lint` command processes all the files, and then renders the diagnostics at the end. This can be annoying if you are wanting to just check for occurrences of a diagnostic. This diff changes the behaviour to stream the results instead as they arrive. This behaviour is turned off if - the new `--no-stream` argument is given - `elp lint` emits json formatted diagnostics - `elp lint` applies fixes Reviewed By: TheGeorge Differential Revision: D88010824 fbshipit-source-id: dc725c1d503f56da6bb1c392cd88f635832320dc --- crates/elp/src/bin/args.rs | 8 + crates/elp/src/bin/lint_cli.rs | 531 ++++++++++++++---- crates/elp/src/bin/main.rs | 139 ++++- .../resolves_generated_includes.stdout | 17 +- .../diagnostics/lint_report_suppressed.stdout | 5 +- .../diagnostics/parse_elp_lint_fix.stdout | 5 +- .../parse_elp_lint_recursive.stdout | 7 +- .../test/hierarchical_config/basic.stdout | 11 +- .../elp/src/resources/test/lint_help.stdout | 3 +- .../linter/custom_function_matches.stdout | 5 +- .../resources/test/linter/elp_lint_ct.stdout | 5 +- .../test/linter/elp_lint_edoc.stdout | 5 +- .../test/linter/parse_elp_lint2.stdout | 5 +- .../linter/parse_elp_lint_adhoc_output.stdout | 7 +- .../test/linter/parse_elp_lint_app.stdout | 5 +- .../parse_elp_lint_config_output.stdout | 22 +- ...parse_elp_lint_custom_config_output.stdout | 14 +- .../parse_elp_lint_erlang_service.stdout | 5 +- ...rse_elp_lint_explicit_enable_output.stdout | 31 +- .../linter/parse_elp_lint_fix_ignore.stdout | 5 +- .../parse_elp_lint_fixme_spelling.stdout | 5 +- .../test/linter/parse_elp_lint_ignore.stdout | 15 +- .../linter/parse_elp_lint_ignore_apps.stdout | 5 +- .../parse_elp_lint_ignore_apps_b.stdout | 8 +- .../linter/parse_elp_lint_json_output.stdout | 2 +- ...e_elp_no_lint_specified_json_output.stdout | 12 +- .../parse_elp_no_lint_specified_output.stdout | 43 +- .../resources/test/linter/ssr_ad_hoc.stdout | 7 +- .../test/linter/warnings_as_errors.stdout | 43 +- .../test/xref/unavailable_type.stdout | 7 +- 30 files changed, 663 insertions(+), 319 deletions(-) diff --git a/crates/elp/src/bin/args.rs b/crates/elp/src/bin/args.rs index f63d569b52..906ebf09f0 100644 --- a/crates/elp/src/bin/args.rs +++ b/crates/elp/src/bin/args.rs @@ -330,6 +330,9 @@ pub struct Lint { #[bpaf(long("report-system-stats"))] pub report_system_stats: bool, + /// Disable streaming of diagnostics when applying fixes (collect all before printing) + pub no_stream: bool, + /// Rest of args are space separated list of apps to ignore #[bpaf(positional("IGNORED_APPS"))] pub ignore_apps: Vec, @@ -868,6 +871,11 @@ impl Lint { pub fn is_format_json(&self) -> bool { self.format == Some("json".to_string()) } + + /// 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 + } } fn parse_macro_strategy(macro_strategy: &Option) -> Result { diff --git a/crates/elp/src/bin/lint_cli.rs b/crates/elp/src/bin/lint_cli.rs index 8450d7b28b..f7ac4ff114 100644 --- a/crates/elp/src/bin/lint_cli.rs +++ b/crates/elp/src/bin/lint_cli.rs @@ -15,10 +15,12 @@ use std::io::Write; use std::path::Path; use std::str; use std::sync::Arc; +use std::thread; use std::time::SystemTime; use anyhow::Result; use anyhow::bail; +use crossbeam_channel::unbounded; use elp::build::load; use elp::build::types::LoadResult; use elp::cli::Cli; @@ -61,7 +63,6 @@ use fxhash::FxHashMap; use fxhash::FxHashSet; use hir::FormIdx; use hir::InFile; -use indicatif::ParallelProgressIterator; use itertools::Itertools; use paths::Utf8PathBuf; use rayon::prelude::ParallelBridge; @@ -131,47 +132,101 @@ pub fn load_project( ) } -fn do_parse_all( - cli: &dyn Cli, +fn do_diagnostics_all( + cli: &mut dyn Cli, analysis: &Analysis, project_id: &ProjectId, config: &DiagnosticsConfig, args: &Lint, -) -> Result> { + loaded: &LoadResult, + module: &Option, +) -> Result<(Vec<(String, FileId, DiagnosticCollection)>, bool, bool)> { let module_index = analysis.module_index(*project_id).unwrap(); - let module_iter = module_index.iter_own(); let ignored_apps: FxHashSet>> = args .ignore_apps .iter() .map(|name| Some(Some(AppName(name.to_string())))) .collect(); - let pb = cli.progress(module_iter.len() as u64, "Parsing modules"); let app_name = args.app.as_ref().map(|name| AppName(name.to_string())); - Ok(module_iter - .par_bridge() - .progress_with(pb) - .map_with( - analysis.clone(), - |db, (module_name, _file_source, file_id)| { - if !otp_file_to_ignore(db, file_id) - && db.file_app_type(file_id).ok() != Some(Some(AppType::Dep)) - && !ignored_apps.contains(&db.file_app_name(file_id).ok()) - && (app_name.is_none() - || db.file_app_name(file_id).ok().as_ref() == Some(&app_name)) - { - do_parse_one(db, config, file_id, module_name.as_str(), args).unwrap() - } else { - None - } - }, - ) - .flatten() - .collect()) + // Create a channel for streaming results + let (tx, rx) = unbounded(); + + // Collect modules into an owned vector + let modules: Vec<_> = module_index + .iter_own() + .map(|(name, source, file_id)| (name.as_str().to_string(), source, file_id)) + .collect(); + + let analysis_clone = analysis.clone(); + let config_clone = config.clone(); + let args_clone = args.clone(); + + let join_handle = thread::spawn(move || { + modules + .into_iter() + .par_bridge() + .map_with( + (analysis_clone, tx), + |(db, tx), (module_name, _file_source, file_id)| { + if !otp_file_to_ignore(db, file_id) + && db.file_app_type(file_id).ok() != Some(Some(AppType::Dep)) + && !ignored_apps.contains(&db.file_app_name(file_id).ok()) + && (app_name.is_none() + || db.file_app_name(file_id).ok().as_ref() == Some(&app_name)) + && let Ok(Some(result)) = do_diagnostics_one( + db, + &config_clone, + file_id, + &module_name, + &args_clone, + ) + { + // Send result through channel + let _ = tx.send(result); + } + }, + ) + .for_each(|_| {}); // Consume the iterator + }); + + // Collect results as they arrive from the channel + let mut results = Vec::new(); + let mut err_in_diag = false; + let mut module_count = 0; + let mut any_diagnostics_printed = false; + + for result in rx { + let printed = if args.skip_stream_print() { + false + } else { + print_diagnostic_result( + cli, + analysis, + config, + args, + loaded, + module, + &mut err_in_diag, + &mut module_count, + &result, + )? + }; + any_diagnostics_printed = any_diagnostics_printed || printed; + results.push(result); + } + + // Wait for the thread to complete before returning + // This ensures that analysis_clone is dropped and its read lock is released + join_handle + .join() + .expect("Failed to join diagnostics thread"); + + Ok((results, err_in_diag, any_diagnostics_printed)) } -fn do_parse_one( +fn do_diagnostics_one( db: &Analysis, config: &DiagnosticsConfig, file_id: FileId, @@ -238,7 +293,9 @@ pub fn do_codemod( ) -> Result<()> { // Declare outside the block so it has the right lifetime for filter_diagnostics let res; - let mut initial_diags = { + let streamed_err_in_diag; + let mut any_diagnostics_printed = false; + let 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 @@ -278,7 +335,18 @@ pub fn do_codemod( res = match (file_id, name) { (None, _) => { - do_parse_all(cli, &analysis, &loaded.project_id, diagnostics_config, args)? + let (results, err_in_diag, any_printed) = do_diagnostics_all( + cli, + &analysis, + &loaded.project_id, + diagnostics_config, + args, + loaded, + &args.module, + )?; + streamed_err_in_diag = err_in_diag; + any_diagnostics_printed = any_printed; + results } (Some(file_id), Some(name)) => { if let Some(app) = &args.app @@ -287,87 +355,100 @@ pub fn do_codemod( { panic!("Module {} does not belong to app {}", name.as_str(), app) } - do_parse_one(&analysis, diagnostics_config, file_id, &name, args)? - .map_or(vec![], |x| vec![x]) + let result = + do_diagnostics_one(&analysis, diagnostics_config, file_id, &name, args)? + .map_or(vec![], |x| vec![x]); + + // Print diagnostics for the single file + let mut err_in_diag = false; + let mut module_count = 0; + if args.skip_stream_print() { + any_diagnostics_printed = false; + } else { + for r in &result { + let printed = print_diagnostic_result( + cli, + &analysis, + diagnostics_config, + args, + loaded, + &args.module, + &mut err_in_diag, + &mut module_count, + r, + )?; + any_diagnostics_printed = any_diagnostics_printed || printed; + } + } + + streamed_err_in_diag = err_in_diag; + result } (Some(file_id), _) => { panic!("Could not get name from file_id for {file_id:?}") } }; - filter_diagnostics( - &analysis, - &args.module, - Some(&diagnostics_config.enabled), - &res, - &FxHashSet::default(), - )? + res }; - if initial_diags.is_empty() { + 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")?; } } else { - initial_diags.sort_by(|(a, _, _), (b, _, _)| a.cmp(b)); - let mut err_in_diag = false; - if args.is_format_json() { - for (_name, file_id, diags) in &initial_diags { - if args.print_diags { - for diag in diags { - // We use JSON output for CI, and want to see warnings too. - // So do not filter on errors only - err_in_diag = true; - let vfs_path = loaded.vfs.file_path(*file_id); - let analysis = loaded.analysis(); - let root_path = &analysis - .project_data(*file_id) - .unwrap_or_else(|_err| panic!("could not find project data")) - .unwrap_or_else(|| panic!("could not find project data")) - .root_dir; - let relative_path = reporting::get_relative_path(root_path, vfs_path); - print_diagnostic_json( - diag, - &analysis, - *file_id, - relative_path, - args.use_cli_severity, - cli, - )?; - } - } - } - } else { - writeln!( - cli, - "Diagnostics reported in {} modules:", - initial_diags.len() - )?; - - for (name, file_id, diags) in &initial_diags { - writeln!(cli, " {}: {}", name, diags.len())?; - if args.print_diags { - for diag in diags { - if let diagnostics::Severity::Error = diag.severity { - err_in_diag = true; - }; - print_diagnostic( - diag, - &loaded.analysis(), - &loaded.vfs, - *file_id, - args.use_cli_severity, - cli, - )?; - } - } - } - } if args.apply_fix && 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(), + )? + }; + if args.skip_stream_print() { + initial_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() { + do_print_diagnostics_json_filtered( + cli, + args, + loaded, + &mut err_in_diag, + module_count, + has_diagnostics, + &initial_diags, + )?; + } else { + { + // Scope the analysis instance to ensure it's dropped before creating Lints + let analysis = loaded.analysis(); + do_print_diagnostics_filtered( + cli, + &analysis, + args, + loaded, + &mut err_in_diag, + module_count, + has_diagnostics, + &initial_diags, + )?; + // Analysis is dropped here + } + } + } + let mut changed_files = FxHashSet::default(); let mut lints = Lints::new( &mut loaded.analysis_host, @@ -395,6 +476,206 @@ pub fn do_codemod( Ok(()) } +#[allow(clippy::too_many_arguments)] +fn print_diagnostic_result( + cli: &mut dyn Cli, + analysis: &Analysis, + config: &DiagnosticsConfig, + args: &Lint, + loaded: &LoadResult, + module: &Option, + err_in_diag: &mut bool, + module_count: &mut i32, + result: &(String, FileId, DiagnosticCollection), +) -> Result { + if args.is_format_json() { + do_print_diagnostic_collection_json( + cli, + analysis, + config, + args, + loaded, + module, + err_in_diag, + module_count, + result, + ) + } else { + do_print_diagnostic_collection( + cli, + analysis, + config, + args, + loaded, + module, + err_in_diag, + module_count, + result, + ) + } +} + +#[allow(clippy::too_many_arguments)] +fn do_print_diagnostic_collection( + cli: &mut dyn Cli, + analysis: &Analysis, + config: &DiagnosticsConfig, + args: &Lint, + loaded: &LoadResult, + module: &Option, + err_in_diag: &mut bool, + module_count: &mut i32, + result: &(String, FileId, DiagnosticCollection), +) -> Result { + let single_result = vec![result.clone()]; + let mut has_diagnostics = false; + if let Ok(filtered) = filter_diagnostics( + analysis, + module, + Some(&config.enabled), + &single_result, + &FxHashSet::default(), + ) { + do_print_diagnostics_filtered( + cli, + analysis, + args, + loaded, + err_in_diag, + module_count, + &mut has_diagnostics, + &filtered, + )?; + } + Ok(has_diagnostics) +} + +#[allow(clippy::too_many_arguments)] +fn do_print_diagnostics_filtered( + cli: &mut dyn Cli, + analysis: &Analysis, + args: &Lint, + loaded: &LoadResult, + err_in_diag: &mut bool, + module_count: &mut i32, + has_diagnostics: &mut bool, + filtered: &[(String, FileId, Vec)], +) -> Result<(), anyhow::Error> { + let _: () = for (name, file_id, diags) in filtered { + if !diags.is_empty() { + *has_diagnostics = true; + if *module_count == 0 { + writeln!(cli, "Diagnostics reported:")?; + } + *module_count += 1; + if !args.print_diags { + writeln!(cli, " {}: {}", name, diags.len())?; + } else { + for diag in diags { + if let diagnostics::Severity::Error = diag.severity { + *err_in_diag = true; + }; + // Get relative path for diagnostic output + let vfs_path = loaded.vfs.file_path(*file_id); + let root_path = &analysis + .project_data(*file_id) + .unwrap_or_else(|_err| panic!("could not find project data")) + .unwrap_or_else(|| panic!("could not find project data")) + .root_dir; + let relative_path = reporting::get_relative_path(root_path, vfs_path); + print_diagnostic( + diag, + analysis, + &loaded.vfs, + *file_id, + Some(relative_path), + args.use_cli_severity, + cli, + )?; + } + } + } + }; + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn do_print_diagnostic_collection_json( + cli: &mut dyn Cli, + analysis: &Analysis, + config: &DiagnosticsConfig, + args: &Lint, + loaded: &LoadResult, + module: &Option, + err_in_diag: &mut bool, + module_count: &mut i32, + result: &(String, FileId, DiagnosticCollection), +) -> Result { + let single_result = vec![result.clone()]; + let mut has_diagnostics = false; + if let Ok(filtered) = filter_diagnostics( + analysis, + module, + Some(&config.enabled), + &single_result, + &FxHashSet::default(), + ) { + do_print_diagnostics_json_filtered( + cli, + args, + loaded, + err_in_diag, + module_count, + &mut has_diagnostics, + &filtered, + )?; + } + Ok(has_diagnostics) +} + +fn do_print_diagnostics_json_filtered( + cli: &mut dyn Cli, + args: &Lint, + loaded: &LoadResult, + err_in_diag: &mut bool, + module_count: &mut i32, + has_diagnostics: &mut bool, + filtered: &[(String, FileId, Vec)], +) -> Result<(), anyhow::Error> { + let _: () = for (name, file_id, diags) in filtered { + if !diags.is_empty() { + *has_diagnostics = true; + *module_count += 1; + if !args.print_diags { + writeln!(cli, " {}: {}", name, diags.len())?; + } else { + for diag in diags { + *err_in_diag = true; + + // Get relative path for diagnostic output + let vfs_path = loaded.vfs.file_path(*file_id); + let analysis = loaded.analysis(); + let root_path = &analysis + .project_data(*file_id) + .unwrap_or_else(|_err| panic!("could not find project data")) + .unwrap_or_else(|| panic!("could not find project data")) + .root_dir; + let relative_path = reporting::get_relative_path(root_path, vfs_path); + print_diagnostic_json( + diag, + &analysis, + *file_id, + relative_path, + args.use_cli_severity, + cli, + )?; + } + } + } + }; + Ok(()) +} + fn get_diagnostics_config(args: &Lint) -> Result { let cfg_from_file = if args.read_config || args.config_file.is_some() { read_lint_config_file(&args.project, &args.config_file)? @@ -422,11 +703,17 @@ fn print_diagnostic( analysis: &Analysis, vfs: &Vfs, file_id: FileId, + path: Option<&Path>, use_cli_severity: bool, cli: &mut dyn Cli, ) -> Result<(), anyhow::Error> { let line_index = analysis.line_index(file_id)?; - writeln!(cli, " {}", diag.print(&line_index, use_cli_severity))?; + let diag_str = diag.print(&line_index, use_cli_severity); + if let Some(path) = path { + writeln!(cli, "{}:{}", path.display(), diag_str)?; + } else { + writeln!(cli, " {}", diag_str)?; + } // Print any related information, indented if let Some(related_info) = &diag.related_info { @@ -674,13 +961,10 @@ impl<'a> Lints<'a> { if self.args.check_eqwalize_all { writeln!(cli, "Running eqwalize-all to check for knock-on problems.")?; } - let diags = do_parse_one( - &self.analysis_host.analysis(), - self.cfg, - file_id, - &name, - self.args, - )?; + let diags = { + let analysis = self.analysis_host.analysis(); + do_diagnostics_one(&analysis, self.cfg, file_id, &name, self.args)? + }; let err_in_diags = diags.iter().any(|(_, file_id, diags)| { let diags = diags.diagnostics_for(*file_id); diags @@ -691,14 +975,15 @@ impl<'a> Lints<'a> { bail!("Applying change introduces an error diagnostic"); } else { self.changed_files.insert((file_id, name.clone())); - let changes = changes - .iter() - .filter_map(|d| { - form_from_diff(&self.analysis_host.analysis(), file_id, d) - }) - .collect::>(); + let changed_forms = { + let analysis = self.analysis_host.analysis(); + changes + .iter() + .filter_map(|d| form_from_diff(&analysis, file_id, d)) + .collect::>() + }; - for form_id in &changes { + for form_id in &changed_forms { self.changed_forms.insert(InFile::new(file_id, *form_id)); } @@ -711,25 +996,24 @@ impl<'a> Lints<'a> { .flatten() .collect::>(); - let new_diagnostics = filter_diagnostics( - &self.analysis_host.analysis(), - &None, - None, - &new_diags, - &self.changed_forms, - )?; + let new_diagnostics = { + let analysis = self.analysis_host.analysis(); + filter_diagnostics(&analysis, &None, None, &new_diags, &self.changed_forms)? + }; self.diags = diagnostics_by_file_id(&new_diagnostics); if !self.diags.is_empty() { writeln!(cli, "---------------------------------------------\n")?; writeln!(cli, "New filtered diagnostics")?; + let analysis = self.analysis_host.analysis(); for (file_id, (name, diags)) in &self.diags { writeln!(cli, " {}: {}", name, diags.len())?; for diag in diags.iter() { print_diagnostic( diag, - &self.analysis_host.analysis(), + &analysis, self.vfs, *file_id, + None, self.args.use_cli_severity, cli, )?; @@ -801,11 +1085,13 @@ impl<'a> Lints<'a> { if format_normal { writeln!(cli, "---------------------------------------------\n")?; writeln!(cli, "Applying fix in module '{name}' for")?; + let analysis = self.analysis_host.analysis(); print_diagnostic( diagnostic, - &self.analysis_host.analysis(), + &analysis, self.vfs, file_id, + None, self.args.use_cli_severity, cli, )?; @@ -874,6 +1160,7 @@ impl<'a> Lints<'a> { &self.analysis_host.analysis(), self.vfs, file_id, + None, self.args.use_cli_severity, cli, )?; @@ -1148,9 +1435,8 @@ mod tests { "#, expect![[r#" module specified: lints - Diagnostics reported in 1 modules: - lints: 1 - 5:3-5:16::[Error] [P1700] head mismatch 'head_mismatcX' vs 'head_mismatch' + Diagnostics reported: + app_a/src/lints.erl:5:3-5:16::[Error] [P1700] head mismatch 'head_mismatcX' vs 'head_mismatch' 4:3-4:16: Mismatched clause name "#]], expect![""], @@ -1169,9 +1455,8 @@ mod tests { "#, expect![[r#" module specified: lints - Diagnostics reported in 1 modules: - lints: 1 - 3:3-3:6::[Warning] [L1230] function foo/0 is unused + Diagnostics reported: + app_a/src/lints.erl:3:3-3:6::[Warning] [L1230] function foo/0 is unused "#]], expect![""], ); diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index cc4c6b8332..59089b1c6e 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -1347,6 +1347,7 @@ mod tests { check_lint_fix( args_vec![ "lint", + "--no-stream", "--diagnostic-filter", "W0010", "--experimental", @@ -1374,6 +1375,7 @@ mod tests { check_lint_fix( args_vec![ "lint", + "--no-stream", "--diagnostic-filter", "W0010", "--experimental", @@ -1399,7 +1401,7 @@ mod tests { fn lint_config_file_used(buck: bool) { let tmp_dir = make_tmp_dir(); let tmp_path = tmp_dir.path(); - check_lint_fix_stderr( + check_lint_fix_stderr_sorted( args_vec![ "lint", "--diagnostic-filter", @@ -1456,7 +1458,7 @@ mod tests { fn lint_custom_config_file_used(buck: bool) { let tmp_dir = make_tmp_dir(); let tmp_path = tmp_dir.path(); - check_lint_fix( + check_lint_fix_stderr_sorted( args_vec![ "lint", "--experimental", @@ -1472,6 +1474,7 @@ mod tests { Path::new("../resources/test/lint/lint_recursive"), &[], false, + None, ) .expect("bad test"); } @@ -1510,7 +1513,7 @@ mod tests { #[test_case(false ; "rebar")] #[test_case(true ; "buck")] fn lint_diagnostic_ignore(buck: bool) { - simple_snapshot( + simple_snapshot_sorted( args_vec![ "lint", "--experimental", @@ -1570,8 +1573,8 @@ mod tests { #[test_case(false ; "rebar")] #[test_case(true ; "buck")] fn lint_no_diagnostics_filter_all_enabled(buck: bool) { - simple_snapshot_expect_error( - args_vec!["lint",], + simple_snapshot_expect_error_sorted( + args_vec!["lint"], "linter", expect_file!("../resources/test/linter/parse_elp_no_lint_specified_output.stdout"), buck, @@ -1582,7 +1585,7 @@ mod tests { #[test_case(false ; "rebar")] #[test_case(true ; "buck")] fn lint_no_diagnostics_filter_all_enabled_json(buck: bool) { - simple_snapshot_expect_error( + simple_snapshot_expect_error_sorted( args_vec!["lint", "--format", "json"], "linter", expect_file!("../resources/test/linter/parse_elp_no_lint_specified_json_output.stdout"), @@ -1609,7 +1612,7 @@ mod tests { fn lint_explicit_enable_diagnostic(buck: bool) { let tmp_dir = make_tmp_dir(); let tmp_path = tmp_dir.path(); - check_lint_fix_stderr( + check_lint_fix_stderr_sorted( args_vec![ "lint", "--config-file", @@ -1636,7 +1639,7 @@ mod tests { fn lint_json_output(buck: bool) { let tmp_dir = make_tmp_dir(); let tmp_path = tmp_dir.path(); - check_lint_fix_stderr( + check_lint_fix_stderr_sorted( args_vec![ "lint", "--diagnostic-filter", @@ -1848,7 +1851,7 @@ mod tests { #[test_case(false ; "rebar")] #[test_case(true ; "buck")] fn lint_edoc(buck: bool) { - simple_snapshot( + simple_snapshot_sorted( args_vec![ "lint", "--include-edoc-diagnostics", @@ -1882,7 +1885,7 @@ mod tests { #[test_case(false ; "rebar")] #[test_case(true ; "buck")] fn lint_ct_include_tests(buck: bool) { - simple_snapshot_expect_error( + simple_snapshot_expect_error_sorted( args_vec![ "lint", "--include-ct-diagnostics", @@ -1900,8 +1903,8 @@ mod tests { #[test] fn lint_resolves_generated_includes() { if cfg!(feature = "buck") { - simple_snapshot_expect_error( - args_vec!["lint"], + simple_snapshot_expect_error_sorted( + args_vec!["lint", "--module", "top_includer",], "buck_tests_2", expect_file!("../resources/test/buck_tests_2/resolves_generated_includes.stdout"), true, @@ -1926,9 +1929,10 @@ mod tests { #[test] fn lint_warnings_as_errors() { - simple_snapshot_expect_error( + simple_snapshot_expect_error_sorted( args_vec![ "lint", + "--no-stream" "--config-file", "../../test_projects/linter/elp_lint_warnings_as_errors.toml" ], @@ -1975,7 +1979,7 @@ mod tests { #[test] fn lint_ssr_from_config() { - simple_snapshot( + simple_snapshot_sorted( args_vec![ "lint", "--config-file", @@ -2180,9 +2184,8 @@ mod tests { #[test_case(false ; "rebar")] #[test_case(true ; "buck")] - #[should_panic] // Support for hierarchical config is not implemented yet fn lint_hierarchical_config_basic(buck: bool) { - simple_snapshot( + simple_snapshot_sorted( args_vec!["lint", "--read-config"], "hierarchical_config", expect_file!("../resources/test/hierarchical_config/basic.stdout"), @@ -2761,6 +2764,61 @@ mod tests { } } + fn simple_snapshot_expect_error_sorted( + args: Vec, + project: &str, + expected: ExpectFile, + buck: bool, + file: Option<&str>, + ) { + if !buck || cfg!(feature = "buck") { + let (mut args, path) = add_project(args, project, file, None); + if !buck { + args.push("--rebar".into()); + } + let (stdout, stderr, code) = elp(args); + assert_eq!( + code, 101, + "Expected exit code 101, got: {code}\nstdout:\n{stdout}\nstderr:\n{stderr}" + ); + let sorted_stdout = sort_lines(&stdout); + assert_normalised_file(expected, &sorted_stdout, path, false); + } + } + + fn sort_lines(s: &str) -> String { + let mut lines: Vec<&str> = s.lines().collect(); + lines.sort(); + lines.join("\n") + } + + #[track_caller] + fn simple_snapshot_sorted( + args: Vec, + project: &str, + expected: ExpectFile, + buck: bool, + file: Option<&str>, + ) { + if !buck || cfg!(feature = "buck") { + let (mut args, path) = add_project(args, project, file, None); + if !buck { + args.push("--rebar".into()); + } + let (stdout, stderr, code) = elp(args); + assert_eq!( + code, 0, + "failed with unexpected exit code: got {code} not 0\nstdout:\n{stdout}\nstderr:\n{stderr}" + ); + let sorted_stdout = sort_lines(&stdout); + assert_normalised_file(expected, &sorted_stdout, path, false); + assert!( + stderr.is_empty(), + "expected stderr to be empty, got:\n{stderr}" + ) + } + } + fn simple_snapshot_expect_stderror( args: Vec, project: &str, @@ -2894,6 +2952,55 @@ mod tests { Ok(()) } + #[allow(clippy::too_many_arguments)] + fn check_lint_fix_stderr_sorted( + args: Vec, + project: &str, + expected: ExpectFile, + expected_code: i32, + buck: bool, + file: Option<&str>, + actual_dir: &Path, + expected_dir: &Path, + files: &[(&str, &str)], + backup_files: bool, + expected_stderr: Option, + ) -> Result<()> { + if !buck || cfg!(feature = "buck") { + let (mut args, path) = add_project(args, project, file, None); + if !buck { + args.push("--rebar".into()); + } + let orig_files = files.iter().map(|x| x.0).collect::>(); + // Take a backup. The Drop instance will restore at the end + let _backup = if backup_files { + BackupFiles::save_files(project, &orig_files) + } else { + BackupFiles::save_files(project, &[]) + }; + let (stdout, stderr, code) = elp(args); + assert_eq!( + code, expected_code, + "Expected exit code {expected_code}, got: {code}\nstdout:\n{stdout}\nstderr:\n{stderr}" + ); + if let Some(expected_stderr) = expected_stderr { + expected_stderr.assert_eq(&stderr); + } else { + expect![[""]].assert_eq(&stderr); + } + let sorted_stdout = sort_lines(&stdout); + assert_normalised_file(expected, &sorted_stdout, path, false); + for (expected_file, file) in files { + let expected = expect_file!(expected_dir.join(expected_file)); + let actual = actual_dir.join(file); + assert!(actual.exists()); + let content = fs::read_to_string(actual).unwrap(); + expected.assert_eq(content.as_str()); + } + } + Ok(()) + } + fn assert_normalised_file( expected: ExpectFile, actual: &str, diff --git a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout index b1773fd731..d4954b84ee 100644 --- a/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout +++ b/crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout @@ -1,14 +1,7 @@ -Reporting all diagnostics codes -Diagnostics reported in 3 modules: - app_a: 1 - 6:10-6:32::[WeakWarning] [W0037] Unspecific include. - top_includer: 2 - 6:1-6:67::[Error] [L0000] Issue in included file [check_include_separate_1/include/include_with_bug.hrl] 5:14-5:18: E1507: undefined macro 'FOO' [check_include_separate_1/include/top_includer.hrl] 3:10-3:30: E1516: can't find include file "does_not_exist.hrl" - 14:5-14:11::[Error] [E1508] undefined macro 'THIRD/2' - wa_buck2_module_search: 4 - 55:19-55:32::[WeakWarning] [W0051] Binary string can be written using sigil syntax. - 57:19-57:29::[WeakWarning] [W0051] Binary string can be written using sigil syntax. - 57:39-57:52::[WeakWarning] [W0051] Binary string can be written using sigil syntax. - 61:31-61:41::[WeakWarning] [W0051] Binary string can be written using sigil syntax. +Diagnostics reported: +Reporting all diagnostics codes +check_include/src/top_includer.erl:14:5-14:11::[Error] [E1508] undefined macro 'THIRD/2' +check_include/src/top_includer.erl:6:1-6:67::[Error] [L0000] Issue in included file +module specified: top_includer \ No newline at end of file diff --git a/crates/elp/src/resources/test/diagnostics/lint_report_suppressed.stdout b/crates/elp/src/resources/test/diagnostics/lint_report_suppressed.stdout index cda6044675..523b8d4bc7 100644 --- a/crates/elp/src/resources/test/diagnostics/lint_report_suppressed.stdout +++ b/crates/elp/src/resources/test/diagnostics/lint_report_suppressed.stdout @@ -1,4 +1,3 @@ module specified: suppressed -Diagnostics reported in 1 modules: - suppressed: 1 - 8:5-8:9::[Warning] [W0007] match is redundant +Diagnostics reported: +app_a/src/suppressed.erl:8:5-8:9::[Warning] [W0007] match is redundant diff --git a/crates/elp/src/resources/test/diagnostics/parse_elp_lint_fix.stdout b/crates/elp/src/resources/test/diagnostics/parse_elp_lint_fix.stdout index 27caa7b1d7..f040cee98e 100644 --- a/crates/elp/src/resources/test/diagnostics/parse_elp_lint_fix.stdout +++ b/crates/elp/src/resources/test/diagnostics/parse_elp_lint_fix.stdout @@ -1,7 +1,6 @@ module specified: lints -Diagnostics reported in 1 modules: - lints: 1 - 5:1-5:14::[Error] [P1700] head mismatch 'head_mismatcX' vs 'head_mismatch' +Diagnostics reported: +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 --------------------------------------------- diff --git a/crates/elp/src/resources/test/diagnostics/parse_elp_lint_recursive.stdout b/crates/elp/src/resources/test/diagnostics/parse_elp_lint_recursive.stdout index d947376488..a88f567370 100644 --- a/crates/elp/src/resources/test/diagnostics/parse_elp_lint_recursive.stdout +++ b/crates/elp/src/resources/test/diagnostics/parse_elp_lint_recursive.stdout @@ -1,8 +1,7 @@ module specified: lint_recursive -Diagnostics reported in 1 modules: - lint_recursive: 2 - 19:5-19:12::[Warning] [W0007] match is redundant - 14:5-14:12::[Warning] [W0007] match is redundant +Diagnostics reported: +app_a/src/lint_recursive.erl:19:5-19:12::[Warning] [W0007] match is redundant +app_a/src/lint_recursive.erl:14:5-14:12::[Warning] [W0007] match is redundant --------------------------------------------- Applying fix in module 'lint_recursive' for diff --git a/crates/elp/src/resources/test/hierarchical_config/basic.stdout b/crates/elp/src/resources/test/hierarchical_config/basic.stdout index 851813b00c..b0ccc8796b 100644 --- a/crates/elp/src/resources/test/hierarchical_config/basic.stdout +++ b/crates/elp/src/resources/test/hierarchical_config/basic.stdout @@ -1,7 +1,6 @@ +Diagnostics reported: Reporting all diagnostics codes -Diagnostics reported in 2 modules: - app_a: 1 - 6:1-6:5::[Warning] [L1230] function main/0 is unused - app_b: 2 - 4:9-4:16::[Warning] [W0002] Unused macro (MACRO_B) - 6:1-6:5::[Warning] [L1230] function main/0 is unused +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 \ No newline at end of file diff --git a/crates/elp/src/resources/test/lint_help.stdout b/crates/elp/src/resources/test/lint_help.stdout index ec4c25c70a..bffdd1fe91 100644 --- a/crates/elp/src/resources/test/lint_help.stdout +++ b/crates/elp/src/resources/test/lint_help.stdout @@ -1,4 +1,4 @@ -Usage: [--project PROJECT] [--module MODULE] [--app APP] [--file FILE] [--rebar] [--as PROFILE] [--include-generated] [--include-tests] [--no-diags] [[--format FORMAT]] [--include-erlc-diagnostics] [--include-ct-diagnostics] [--include-edoc-diagnostics] [--include-eqwalizer-diagnostics] [--include-suppressed] [--use-cli-severity] [--diagnostic-ignore CODE] [--diagnostic-filter CODE] [--experimental] [--read-config] [--config-file CONFIG_FILE] [--apply-fix] [--ignore-fix-only] [--in-place] [--to TO] [--recursive] [--with-check] [--check-eqwalize-all] [--one-shot] [--report-system-stats] ... +Usage: [--project PROJECT] [--module MODULE] [--app APP] [--file FILE] [--rebar] [--as PROFILE] [--include-generated] [--include-tests] [--no-diags] [[--format FORMAT]] [--include-erlc-diagnostics] [--include-ct-diagnostics] [--include-edoc-diagnostics] [--include-eqwalizer-diagnostics] [--include-suppressed] [--use-cli-severity] [--diagnostic-ignore CODE] [--diagnostic-filter CODE] [--experimental] [--read-config] [--config-file CONFIG_FILE] [--apply-fix] [--ignore-fix-only] [--in-place] [--to TO] [--recursive] [--with-check] [--check-eqwalize-all] [--one-shot] [--report-system-stats] [--no-stream] ... Available positional items: Rest of args are space separated list of apps to ignore @@ -37,4 +37,5 @@ Available options: --one-shot Apply to all matching diagnostic occurrences at once, rather than one at a time. --report-system-stats Report system memory usage and other statistics + --no-stream Disable streaming of diagnostics when applying fixes (collect all before printing) -h, --help Prints help information diff --git a/crates/elp/src/resources/test/linter/custom_function_matches.stdout b/crates/elp/src/resources/test/linter/custom_function_matches.stdout index 961154e550..c552fa7dfa 100644 --- a/crates/elp/src/resources/test/linter/custom_function_matches.stdout +++ b/crates/elp/src/resources/test/linter/custom_function_matches.stdout @@ -1,5 +1,4 @@ Reporting all diagnostics codes module specified: custom_function_matches -Diagnostics reported in 1 modules: - custom_function_matches: 1 - 14:5-14:26::[Warning] [W0017] Function 'not_excluded:function/0' is undefined. +Diagnostics reported: +app_a/src/custom_function_matches.erl:14:5-14:26::[Warning] [W0017] Function 'not_excluded:function/0' is undefined. diff --git a/crates/elp/src/resources/test/linter/elp_lint_ct.stdout b/crates/elp/src/resources/test/linter/elp_lint_ct.stdout index 773b1f4d9c..471efc7900 100644 --- a/crates/elp/src/resources/test/linter/elp_lint_ct.stdout +++ b/crates/elp/src/resources/test/linter/elp_lint_ct.stdout @@ -1,3 +1,2 @@ -Diagnostics reported in 1 modules: - app_a_unreachable_test_SUITE: 1 - 8:1-8:2::[Error] [W0008] Unreachable test (b/1) +Diagnostics reported: +app_a/test/app_a_unreachable_test_SUITE.erl:8:1-8:2::[Error] [W0008] Unreachable test (b/1) \ No newline at end of file diff --git a/crates/elp/src/resources/test/linter/elp_lint_edoc.stdout b/crates/elp/src/resources/test/linter/elp_lint_edoc.stdout index 8b60108476..a667df3930 100644 --- a/crates/elp/src/resources/test/linter/elp_lint_edoc.stdout +++ b/crates/elp/src/resources/test/linter/elp_lint_edoc.stdout @@ -1,3 +1,2 @@ -Diagnostics reported in 1 modules: - app_a_edoc: 1 - 5:1-6:1::[Warning] [O0039] tag @docc not recognized. +Diagnostics reported: +app_a/src/app_a_edoc.erl:5:1-6:1::[Warning] [O0039] tag @docc not recognized. \ No newline at end of file diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint2.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint2.stdout index 6d3ab47e91..47dc6d4a5e 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint2.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint2.stdout @@ -1,5 +1,4 @@ module specified: app_a -Diagnostics reported in 1 modules: - app_a: 1 - 9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food' +Diagnostics reported: +app_a/src/app_a.erl:9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food' 7:1-7:5: Mismatched clause name diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_adhoc_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_adhoc_output.stdout index 97e3ef307f..0d0d56491d 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_adhoc_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_adhoc_output.stdout @@ -1,8 +1,7 @@ module specified: app_b -Diagnostics reported in 1 modules: - app_b: 2 - 8:5-8:36::[WeakWarning] [ad-hoc: application:get_env/2] 'application:get_env/2' called - 5:5-5:35::[WeakWarning] [ad-hoc: application:get_env/2] 'application:get_env/2' called +Diagnostics reported: +app_b/src/app_b.erl:8:5-8:36::[WeakWarning] [ad-hoc: application:get_env/2] 'application:get_env/2' called +app_b/src/app_b.erl:5:5-5:35::[WeakWarning] [ad-hoc: application:get_env/2] 'application:get_env/2' called --------------------------------------------- Applying fixes in module 'app_b' for diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_app.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_app.stdout index 0432896183..edd7c5cf1c 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_app.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_app.stdout @@ -1,4 +1,3 @@ -Diagnostics reported in 1 modules: - app_a: 1 - 9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food' +Diagnostics reported: +app_a/src/app_a.erl:9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food' 7:1-7:5: Mismatched clause name diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_config_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_config_output.stdout index 285ca216f3..6daa076a31 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_config_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_config_output.stdout @@ -1,14 +1,10 @@ -Diagnostics reported in 4 modules: - app_a: 3 - 9:6-9:7::[Warning] [W0010] this variable is unused - 5:5-5:35::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` - 9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food' 7:1-7:5: Mismatched clause name - app_a_unused_param: 2 - 5:5-5:6::[Warning] [W0010] this variable is unused - 5:5-5:6::[Warning] [L1268] variable 'X' is unused - app_b: 1 - 5:5-5:35::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` - app_b_unused_param: 2 - 5:5-5:6::[Warning] [W0010] this variable is unused - 5:5-5:6::[Warning] [L1268] variable 'X' is unused +Diagnostics reported: +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:9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food' +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] [L1268] variable 'X' is unused +app_a/src/app_a_unused_param.erl:5:5-5:6::[Warning] [W0010] this variable is unused +app_b/src/app_b.erl:5:5-5:35::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` +app_b/src/app_b_unused_param.erl:5:5-5:6::[Warning] [L1268] variable 'X' is unused +app_b/src/app_b_unused_param.erl:5:5-5:6::[Warning] [W0010] this variable is unused \ No newline at end of file diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_custom_config_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_custom_config_output.stdout index 3927f2c196..90ccbcb5c0 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_custom_config_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_custom_config_output.stdout @@ -1,9 +1,5 @@ -Diagnostics reported in 4 modules: - app_a: 1 - 5:5-5:35::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` - app_a_unused_param: 1 - 5:5-5:6::[Warning] [L1268] variable 'X' is unused - app_b: 1 - 5:5-5:35::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` - app_b_unused_param: 1 - 5:5-5:6::[Warning] [L1268] variable 'X' is unused +Diagnostics reported: +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_unused_param.erl:5:5-5:6::[Warning] [L1268] variable 'X' is unused +app_b/src/app_b.erl:5:5-5:35::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` +app_b/src/app_b_unused_param.erl:5:5-5:6::[Warning] [L1268] variable 'X' is unused \ No newline at end of file diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_erlang_service.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_erlang_service.stdout index 74b091100b..eeb41e5adf 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_erlang_service.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_erlang_service.stdout @@ -1,4 +1,3 @@ module specified: expression_updates_literal -Diagnostics reported in 1 modules: - expression_updates_literal: 1 - 8:7-9:16::[Warning] [L1318] expression updates a literal +Diagnostics reported: +app_a/src/expression_updates_literal.erl:8:7-9:16::[Warning] [L1318] expression updates a literal diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_explicit_enable_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_explicit_enable_output.stdout index d051fc19fc..1d7771b3b9 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_explicit_enable_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_explicit_enable_output.stdout @@ -1,19 +1,12 @@ -Diagnostics reported in 7 modules: - app_a: 2 - 5:5-5:35::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` - 1:1-1:1::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. - app_a_edoc: 1 - 1:1-1:1::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. - app_a_ssr: 1 - 1:1-1:1::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. - app_a_unused_param: 2 - 1:1-1:1::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. - 5:5-5:6::[Warning] [L1268] variable 'X' is unused - app_b: 2 - 5:5-5:35::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` - 1:1-1:1::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. - app_b_unused_param: 2 - 1:1-1:1::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. - 5:5-5:6::[Warning] [L1268] variable 'X' is unused - spelling: 1 - 1:1-1:1::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. +Diagnostics reported: +app_a/src/app_a.erl:1:1-1:1::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. +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_edoc.erl:1:1-1:1::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. +app_a/src/app_a_ssr.erl:1:1-1:1::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. +app_a/src/app_a_unused_param.erl:1:1-1:1::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. +app_a/src/app_a_unused_param.erl:5:5-5:6::[Warning] [L1268] variable 'X' is unused +app_a/src/spelling.erl:1:1-1:1::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. +app_b/src/app_b.erl:1:1-1:1::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. +app_b/src/app_b.erl:5:5-5:35::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` +app_b/src/app_b_unused_param.erl:1:1-1:1::[Error] [W0012] Please add "-compile(warn_missing_spec_all)." to the module. If exported functions are not all specced, they need to be specced. +app_b/src/app_b_unused_param.erl:5:5-5:6::[Warning] [L1268] variable 'X' is unused \ No newline at end of file diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_fix_ignore.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_fix_ignore.stdout index c573704321..3e4a25f329 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_fix_ignore.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_fix_ignore.stdout @@ -1,7 +1,6 @@ module specified: app_b -Diagnostics reported in 1 modules: - app_b: 1 - 5:5-5:35::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` +Diagnostics reported: +app_b/src/app_b.erl:5:5-5:35::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` --------------------------------------------- Applying fix in module 'app_b' for diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_fixme_spelling.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_fixme_spelling.stdout index b8c9d1896e..2cec0678d2 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_fixme_spelling.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_fixme_spelling.stdout @@ -1,7 +1,6 @@ module specified: spelling -Diagnostics reported in 1 modules: - spelling: 1 - 2:2-2:10::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' +Diagnostics reported: +app_a/src/spelling.erl:2:2-2:10::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' --------------------------------------------- Applying fix in module 'spelling' for diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_ignore.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_ignore.stdout index dbdb7db051..1e7e049554 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_ignore.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_ignore.stdout @@ -1,9 +1,6 @@ -Diagnostics reported in 3 modules: - app_a: 1 - 9:6-9:7::[Warning] [W0010] this variable is unused - app_a_unused_param: 2 - 5:5-5:6::[Warning] [W0010] this variable is unused - 5:5-5:6::[Warning] [L1268] variable 'X' is unused - app_b_unused_param: 2 - 5:5-5:6::[Warning] [W0010] this variable is unused - 5:5-5:6::[Warning] [L1268] variable 'X' is unused +Diagnostics reported: +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] [L1268] variable 'X' is unused +app_a/src/app_a_unused_param.erl:5:5-5:6::[Warning] [W0010] this variable is unused +app_b/src/app_b_unused_param.erl:5:5-5:6::[Warning] [L1268] variable 'X' is unused +app_b/src/app_b_unused_param.erl:5:5-5:6::[Warning] [W0010] this variable is unused \ No newline at end of file diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_ignore_apps.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_ignore_apps.stdout index 58caba3339..4b8d3cc466 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_ignore_apps.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_ignore_apps.stdout @@ -1,3 +1,2 @@ -Diagnostics reported in 1 modules: - app_b_unused_param: 1 - 5:5-5:6::[Warning] [W0010] this variable is unused +Diagnostics reported: +app_b/src/app_b_unused_param.erl:5:5-5:6::[Warning] [W0010] this variable is unused diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_ignore_apps_b.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_ignore_apps_b.stdout index 963181a031..3d59c453e3 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_ignore_apps_b.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_ignore_apps_b.stdout @@ -1,5 +1,3 @@ -Diagnostics reported in 2 modules: - app_a: 1 - 9:6-9:7::[Warning] [W0010] this variable is unused - app_a_unused_param: 1 - 5:5-5:6::[Warning] [W0010] this variable is unused +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 diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_json_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_json_output.stdout index 4fb3478926..5d6b5e78bc 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_json_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_json_output.stdout @@ -1,3 +1,3 @@ {"path":"app_a/src/app_a.erl","line":9,"char":6,"code":"ELP","severity":"warning","name":"W0010 (unused_function_arg)","original":null,"replacement":null,"description":"this variable is unused\n\nFor more information see: /erlang-error-index/w/W0010","docPath":"website/docs/erlang-error-index/w/W0010.md"} {"path":"app_a/src/app_a_unused_param.erl","line":5,"char":5,"code":"ELP","severity":"warning","name":"W0010 (unused_function_arg)","original":null,"replacement":null,"description":"this variable is unused\n\nFor more information see: /erlang-error-index/w/W0010","docPath":"website/docs/erlang-error-index/w/W0010.md"} -{"path":"app_b/src/app_b_unused_param.erl","line":5,"char":5,"code":"ELP","severity":"warning","name":"W0010 (unused_function_arg)","original":null,"replacement":null,"description":"this variable is unused\n\nFor more information see: /erlang-error-index/w/W0010","docPath":"website/docs/erlang-error-index/w/W0010.md"} +{"path":"app_b/src/app_b_unused_param.erl","line":5,"char":5,"code":"ELP","severity":"warning","name":"W0010 (unused_function_arg)","original":null,"replacement":null,"description":"this variable is unused\n\nFor more information see: /erlang-error-index/w/W0010","docPath":"website/docs/erlang-error-index/w/W0010.md"} \ No newline at end of file diff --git a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_json_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_json_output.stdout index 7608728f1e..6e07ea2bba 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_json_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_json_output.stdout @@ -1,16 +1,16 @@ -{"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":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.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":13,"char":5,"code":"ELP","severity":"warning","name":"W0026 (unexported_function)","original":null,"replacement":null,"description":"Function 'app_a:baz/2' is not exported.\n\nFor more information see: /erlang-error-index/w/W0026","docPath":"website/docs/erlang-error-index/w/W0026.md"} {"path":"app_a/src/app_a.erl","line":12,"char":1,"code":"ELP","severity":"warning","name":"L1230 (L1230)","original":null,"replacement":null,"description":"function bar/0 is unused\n\nFor more information see: /erlang-error-index/l/L1230","docPath":null} +{"path":"app_a/src/app_a.erl","line":13,"char":5,"code":"ELP","severity":"warning","name":"W0026 (unexported_function)","original":null,"replacement":null,"description":"Function 'app_a:baz/2' is not exported.\n\nFor more information see: /erlang-error-index/w/W0026","docPath":"website/docs/erlang-error-index/w/W0026.md"} {"path":"app_a/src/app_a.erl","line":16,"char":1,"code":"ELP","severity":"warning","name":"L1230 (L1230)","original":null,"replacement":null,"description":"function baz/2 is unused\n\nFor more information see: /erlang-error-index/l/L1230","docPath":null} {"path":"app_a/src/app_a.erl","line":20,"char":1,"code":"ELP","severity":"warning","name":"L1230 (L1230)","original":null,"replacement":null,"description":"function bat/2 is unused\n\nFor more information see: /erlang-error-index/l/L1230","docPath":null} +{"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_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_b/src/app_b.erl","line":5,"char":5,"code":"ELP","severity":"warning","name":"W0011 (application_get_env)","original":null,"replacement":null,"description":"module `app_b` belongs to app `app_b`, 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_b/src/app_b_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"} {"path":"app_a/src/custom_function_matches.erl","line":15,"char":5,"code":"ELP","severity":"warning","name":"W0017 (undefined_function)","original":null,"replacement":null,"description":"Function 'cross:call/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/expression_updates_literal.erl","line":7,"char":1,"code":"ELP","severity":"warning","name":"L1309 (L1309)","original":null,"replacement":null,"description":"missing specification for function a_fun/0\n\nFor more information see: /erlang-error-index/l/L1309","docPath":null} {"path":"app_a/src/expression_updates_literal.erl","line":8,"char":7,"code":"ELP","severity":"warning","name":"L1318 (L1318)","original":null,"replacement":null,"description":"expression updates a literal\n\nFor more information see: /erlang-error-index/l/L1318","docPath":null} {"path":"app_a/src/spelling.erl","line":2,"char":2,"code":"ELP","severity":"error","name":"W0013 (misspelled_attribute)","original":null,"replacement":null,"description":"misspelled attribute, saw 'dyalizer' but expected 'dialyzer'\n\nFor more information see: /erlang-error-index/w/W0013","docPath":"website/docs/erlang-error-index/w/W0013.md"} +{"path":"app_b/src/app_b.erl","line":5,"char":5,"code":"ELP","severity":"warning","name":"W0011 (application_get_env)","original":null,"replacement":null,"description":"module `app_b` belongs to app `app_b`, 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_b/src/app_b_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} \ No newline at end of file diff --git a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout index 6a4b28b72b..9f7a5405b4 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout @@ -1,26 +1,19 @@ -Reporting all diagnostics codes -Diagnostics reported in 7 modules: - app_a: 7 - 5:5-5:35::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` - 9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food' 7:1-7:5: Mismatched clause name - 8:7-8:8::[Warning] [W0018] Unexpected ';' - 13:5-13:14::[Warning] [W0026] Function 'app_a:baz/2' is not exported. - 12:1-12:4::[Warning] [L1230] function bar/0 is unused - 16:1-16:4::[Warning] [L1230] function baz/2 is unused - 20:1-20:4::[Warning] [L1230] function bat/2 is unused - app_a_unused_param: 1 - 5:5-5:6::[Warning] [L1268] variable 'X' is unused - app_b: 1 - 5:5-5:35::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` - app_b_unused_param: 1 - 5:5-5:6::[Warning] [L1268] variable 'X' is unused - custom_function_matches: 3 - 13:5-13:22::[Warning] [W0017] Function 'excluded:function/0' is undefined. - 14:5-14:26::[Warning] [W0017] Function 'not_excluded:function/0' is undefined. - 15:5-15:15::[Warning] [W0017] Function 'cross:call/0' is undefined. - expression_updates_literal: 2 - 7:1-7:6::[Warning] [L1309] missing specification for function a_fun/0 - 8:7-9:16::[Warning] [L1318] expression updates a literal - spelling: 1 - 2:2-2:10::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' +Diagnostics reported: +Reporting all diagnostics codes +app_a/src/app_a.erl:12:1-12:4::[Warning] [L1230] function bar/0 is unused +app_a/src/app_a.erl:13:5-13:14::[Warning] [W0026] Function 'app_a:baz/2' is not exported. +app_a/src/app_a.erl:16:1-16:4::[Warning] [L1230] function baz/2 is unused +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_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. +app_a/src/custom_function_matches.erl:15:5-15:15::[Warning] [W0017] Function 'cross:call/0' is undefined. +app_a/src/expression_updates_literal.erl:7:1-7:6::[Warning] [L1309] missing specification for function a_fun/0 +app_a/src/expression_updates_literal.erl:8:7-9:16::[Warning] [L1318] expression updates a literal +app_a/src/spelling.erl:2:2-2:10::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' +app_b/src/app_b.erl:5:5-5:35::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` +app_b/src/app_b_unused_param.erl:5:5-5:6::[Warning] [L1268] variable 'X' is unused \ No newline at end of file diff --git a/crates/elp/src/resources/test/linter/ssr_ad_hoc.stdout b/crates/elp/src/resources/test/linter/ssr_ad_hoc.stdout index b081749a91..7ded0e8d2f 100644 --- a/crates/elp/src/resources/test/linter/ssr_ad_hoc.stdout +++ b/crates/elp/src/resources/test/linter/ssr_ad_hoc.stdout @@ -1,4 +1,3 @@ -Diagnostics reported in 1 modules: - app_a: 2 - 16:13-16:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {_@A, _@B}. - 20:13-20:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {_@A, _@B}. +Diagnostics reported: +app_a/src/app_a.erl:16:13-16:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {_@A, _@B}. +app_a/src/app_a.erl:20:13-20:18::[WeakWarning] [ad-hoc: ssr-match] SSR pattern matched: ssr: {_@A, _@B}. \ No newline at end of file diff --git a/crates/elp/src/resources/test/linter/warnings_as_errors.stdout b/crates/elp/src/resources/test/linter/warnings_as_errors.stdout index 2d9eae2b6e..00d8506230 100644 --- a/crates/elp/src/resources/test/linter/warnings_as_errors.stdout +++ b/crates/elp/src/resources/test/linter/warnings_as_errors.stdout @@ -1,26 +1,19 @@ -Reporting all diagnostics codes -Diagnostics reported in 7 modules: - app_a: 7 - 5:5-5:35::[Warning] [W0011] module `app_a` belongs to app `app_a`, but reads env for `misc` - 9:1-9:5::[Error] [P1700] head mismatch 'fooX' vs 'food' 7:1-7:5: Mismatched clause name - 8:7-8:8::[Warning] [W0018] Unexpected ';' - 13:5-13:14::[Warning] [W0026] Function 'app_a:baz/2' is not exported. - 12:1-12:4::[Error] [L1230] function bar/0 is unused - 16:1-16:4::[Error] [L1230] function baz/2 is unused - 20:1-20:4::[Error] [L1230] function bat/2 is unused - app_a_unused_param: 1 - 5:5-5:6::[Error] [L1268] variable 'X' is unused - app_b: 1 - 5:5-5:35::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` - app_b_unused_param: 1 - 5:5-5:6::[Error] [L1268] variable 'X' is unused - custom_function_matches: 3 - 13:5-13:22::[Warning] [W0017] Function 'excluded:function/0' is undefined. - 14:5-14:26::[Warning] [W0017] Function 'not_excluded:function/0' is undefined. - 15:5-15:15::[Warning] [W0017] Function 'cross:call/0' is undefined. - expression_updates_literal: 2 - 7:1-7:6::[Error] [L1309] missing specification for function a_fun/0 - 8:7-9:16::[Error] [L1318] expression updates a literal - spelling: 1 - 2:2-2:10::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' +Diagnostics reported: +Reporting all diagnostics codes +app_a/src/app_a.erl:12:1-12:4::[Error] [L1230] function bar/0 is unused +app_a/src/app_a.erl:13:5-13:14::[Warning] [W0026] Function 'app_a:baz/2' is not exported. +app_a/src/app_a.erl:16:1-16:4::[Error] [L1230] function baz/2 is unused +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_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. +app_a/src/custom_function_matches.erl:15:5-15:15::[Warning] [W0017] Function 'cross:call/0' is undefined. +app_a/src/expression_updates_literal.erl:7:1-7:6::[Error] [L1309] missing specification for function a_fun/0 +app_a/src/expression_updates_literal.erl:8:7-9:16::[Error] [L1318] expression updates a literal +app_a/src/spelling.erl:2:2-2:10::[Error] [W0013] misspelled attribute, saw 'dyalizer' but expected 'dialyzer' +app_b/src/app_b.erl:5:5-5:35::[Warning] [W0011] module `app_b` belongs to app `app_b`, but reads env for `misc` +app_b/src/app_b_unused_param.erl:5:5-5:6::[Error] [L1268] variable 'X' is unused \ No newline at end of file diff --git a/crates/elp/src/resources/test/xref/unavailable_type.stdout b/crates/elp/src/resources/test/xref/unavailable_type.stdout index 1521d13194..7064c359ff 100644 --- a/crates/elp/src/resources/test/xref/unavailable_type.stdout +++ b/crates/elp/src/resources/test/xref/unavailable_type.stdout @@ -1,6 +1,5 @@ Reporting all diagnostics codes module specified: unavailable_type -Diagnostics reported in 1 modules: - unavailable_type: 2 - 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'). - 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'). +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'). From 77e0ff9bc824bbb6dd408cdc023fa64088784afd Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Tue, 9 Dec 2025 08:47:59 -0800 Subject: [PATCH 082/142] Remove `pre_parse_for_speed` from `elp eqwalize-all` Summary: Measurements show it makes no difference to the run-time of `elp eqwalize-all`, and it is an impediment to providing streaming output. Without this, parsing happens on-demand as files are processed by eqwalizer, so we can generate output as each is processed. Reviewed By: robertoaloi Differential Revision: D88379872 fbshipit-source-id: 5110788ac5a7ef471038db9de0abfa00bbe7312f --- crates/elp/src/bin/eqwalizer_cli.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/crates/elp/src/bin/eqwalizer_cli.rs b/crates/elp/src/bin/eqwalizer_cli.rs index c946babe42..a91bcdd869 100644 --- a/crates/elp/src/bin/eqwalizer_cli.rs +++ b/crates/elp/src/bin/eqwalizer_cli.rs @@ -482,8 +482,6 @@ fn eqwalize( bail!("No files to eqWAlize detected") } - pre_parse_for_speed(reporter, analysis.clone(), &file_ids); - let files_count = file_ids.len(); let pb = reporter.progress(files_count as u64, "EqWAlizing"); let output = loaded.with_eqwalizer_progress_bar(pb.clone(), move |analysis| { @@ -602,17 +600,6 @@ fn eqwalize( } } -fn pre_parse_for_speed(reporter: &dyn Reporter, analysis: Analysis, file_ids: &[FileId]) { - let pb = reporter.progress(file_ids.len() as u64, "Parsing modules"); - file_ids - .par_iter() - .progress_with(pb.clone()) - .for_each_with(analysis, |analysis, &file_id| { - let _ = analysis.module_ast(file_id); - }); - pb.finish(); -} - fn set_eqwalizer_config(loaded: &mut LoadResult) { let config = EqwalizerConfig::default(); let db = loaded.analysis_host.raw_database_mut(); From 1a086de994a226e81e870e836a042880ea6cb288 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Tue, 9 Dec 2025 08:47:59 -0800 Subject: [PATCH 083/142] BE: args.color Summary: Move the decision-making logic for choosing to output color in CLI usage into a single place. Reviewed By: TD5 Differential Revision: D88377007 fbshipit-source-id: 398552dba1547a9e9c0b5e6cd38808316280dd7a --- crates/elp/src/bin/args.rs | 15 +++++++++++++++ crates/elp/src/bin/main.rs | 15 +++------------ crates/elp/src/bin/ssr_cli.rs | 35 +++++++++-------------------------- 3 files changed, 27 insertions(+), 38 deletions(-) diff --git a/crates/elp/src/bin/args.rs b/crates/elp/src/bin/args.rs index 906ebf09f0..0f057ce1e6 100644 --- a/crates/elp/src/bin/args.rs +++ b/crates/elp/src/bin/args.rs @@ -11,6 +11,7 @@ use std::cmp::Ordering; use std::env; use std::fs; +use std::io::IsTerminal; use std::path::PathBuf; use anyhow::Result; @@ -540,6 +541,20 @@ impl Args { BuckQueryConfig::BuildGeneratedCode } } + + /// Determine if color should be used based on the --color argument + pub fn should_use_color(&self) -> bool { + match self.color.as_deref() { + Some("always") => true, + Some("never") => false, + Some("auto") | None => { + // Check NO_COLOR environment variable - if set (regardless of value), disable color + // Also check if stdout is connected to a TTY + env::var("NO_COLOR").is_err() && std::io::stdout().is_terminal() + } + _ => false, // Should be caught by the guard, but handle anyway + } + } } pub fn command() -> impl Parser { diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index 59089b1c6e..2ecb900759 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -10,7 +10,6 @@ use std::env; use std::fs; -use std::io::IsTerminal; use std::io::Write; use std::path::PathBuf; use std::process; @@ -67,16 +66,7 @@ const THREAD_STACK_SIZE: usize = 10_000_000; fn main() { let _timer = timeit!("main"); let args = args::args().run(); - let use_color = match args.color.as_deref() { - Some("always") => true, - Some("never") => false, - Some("auto") | None => { - // Check NO_COLOR environment variable - if set (regardless of value), disable color - // Also check if stdout is connected to a TTY - env::var("NO_COLOR").is_err() && std::io::stdout().is_terminal() - } - _ => false, // Should be caught by the guard, but handle anyway - }; + let use_color = args.should_use_color(); let mut cli: Box = if use_color { Box::new(cli::Real::default()) } else { @@ -134,6 +124,7 @@ fn try_main(cli: &mut dyn Cli, args: Args) -> Result<()> { setup_thread_pool(); }); let query_config = args.query_config(); + let use_color = args.should_use_color(); match args.command { args::Command::RunServer(_) => run_server(logger)?, args::Command::ParseAll(args) => erlang_service_cli::parse_all(&args, cli, &query_config)?, @@ -152,7 +143,7 @@ fn try_main(cli: &mut dyn Cli, args: Args) -> Result<()> { args::Command::ProjectInfo(args) => build_info_cli::save_project_info(args, &query_config)?, args::Command::Lint(args) => lint_cli::run_lint_command(&args, cli, &query_config)?, args::Command::Ssr(ssr_args) => { - ssr_cli::run_ssr_command(&ssr_args, cli, &query_config, &args.color)? + ssr_cli::run_ssr_command(&ssr_args, cli, &query_config, use_color)? } args::Command::GenerateCompletions(args) => { let instructions = args::gen_completions(&args.shell); diff --git a/crates/elp/src/bin/ssr_cli.rs b/crates/elp/src/bin/ssr_cli.rs index 1458e2eb27..f5729034b2 100644 --- a/crates/elp/src/bin/ssr_cli.rs +++ b/crates/elp/src/bin/ssr_cli.rs @@ -9,7 +9,6 @@ */ use std::fs; -use std::io::IsTerminal; use std::path::Path; use std::str; use std::thread; @@ -66,7 +65,7 @@ pub fn run_ssr_command( args: &Ssr, cli: &mut dyn Cli, query_config: &BuckQueryConfig, - global_color: &Option, + use_color: bool, ) -> Result<()> { let start_time = SystemTime::now(); let memory_start = MemoryUsage::now(); @@ -130,7 +129,7 @@ pub fn run_ssr_command( let mut loaded = load_project(args, cli, query_config)?; telemetry::report_elapsed_time("ssr operational", start_time); - let r = run_ssr(cli, &mut loaded, &diagnostics_config, args, global_color); + let r = run_ssr(cli, &mut loaded, &diagnostics_config, args, use_color); telemetry::report_elapsed_time("ssr done", start_time); @@ -150,7 +149,7 @@ pub fn run_ssr( loaded: &mut LoadResult, diagnostics_config: &DiagnosticsConfig, args: &Ssr, - global_color: &Option, + use_color: bool, ) -> Result<()> { let analysis = loaded.analysis(); let (file_id, name) = match &args.module { @@ -196,7 +195,7 @@ pub fn run_ssr( &project_id, diagnostics_config, args, - global_color, + use_color, loaded, &mut match_count, )?; @@ -210,7 +209,7 @@ pub fn run_ssr( } if let Some(diag) = do_parse_one(&analysis, diagnostics_config, file_id, &name, args)? { match_count = 1; - print_single_result(cli, loaded, &diag, args, global_color)?; + print_single_result(cli, loaded, &diag, args, use_color)?; } } (Some(file_id), _) => { @@ -236,7 +235,7 @@ fn do_parse_all_streaming( project_id: &ProjectId, config: &DiagnosticsConfig, args: &Ssr, - global_color: &Option, + use_color: bool, loaded: &mut LoadResult, match_count: &mut usize, ) -> Result<()> { @@ -283,7 +282,7 @@ fn do_parse_all_streaming( // Process and print results as they arrive from the channel for result in rx { *match_count += 1; - print_single_result(cli, loaded, &result, args, global_color)?; + print_single_result(cli, loaded, &result, args, use_color)?; } Ok(()) @@ -294,7 +293,7 @@ fn print_single_result( loaded: &mut LoadResult, result: &(String, FileId, Vec), args: &Ssr, - global_color: &Option, + use_color: bool, ) -> Result<()> { let (name, file_id, diags) = result; @@ -352,9 +351,7 @@ fn print_single_result( // Only show source context if --show-source or --show-source-markers is set if show_source { - let should_show_color = should_use_color(global_color); - - if should_show_color { + if use_color { print_source_with_context( diag, &loaded.analysis(), @@ -554,20 +551,6 @@ fn should_show_group_separator(args: &Ssr, has_context: bool) -> Option ) } -/// Determine if color should be used based on the global --color argument -fn should_use_color(color: &Option) -> bool { - match color.as_deref() { - Some("always") => true, - Some("never") => false, - Some("auto") | None => { - // Check NO_COLOR environment variable - if set (regardless of value), disable color - // Also check if stdout is connected to a TTY - std::env::var("NO_COLOR").is_err() && std::io::stdout().is_terminal() - } - _ => false, // Should be caught by the guard, but handle anyway - } -} - /// Print source code context with the specified before/after context lines fn print_source_with_context( diag: &diagnostics::Diagnostic, From f6b54832d7464fd23640951712f76e7d402fcfc9 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Wed, 10 Dec 2025 03:29:08 -0800 Subject: [PATCH 084/142] Convert malformed_include linter to use a trait Summary: Mechanical conversion, no functional change. Reviewed By: alanz Differential Revision: D88623867 fbshipit-source-id: 2d1f17430a44a705f22ec542e242165beb577285 --- crates/ide/src/diagnostics/unspecific_include.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/ide/src/diagnostics/unspecific_include.rs b/crates/ide/src/diagnostics/unspecific_include.rs index 223e5a0064..f7405c798a 100644 --- a/crates/ide/src/diagnostics/unspecific_include.rs +++ b/crates/ide/src/diagnostics/unspecific_include.rs @@ -152,6 +152,7 @@ fn replace_include_path( #[cfg(test)] mod tests { use elp_ide_db::DiagnosticCode; + // @fb-only use expect_test::Expect; use expect_test::expect; @@ -168,9 +169,12 @@ mod tests { tests::check_filtered_diagnostics(fixture, &filter) } + #[rustfmt::skip] #[track_caller] fn check_fix(fixture_before: &str, fixture_after: Expect) { - let config = DiagnosticsConfig::default().disable(DiagnosticCode::UnusedInclude); + let config = DiagnosticsConfig::default() + // @fb-only + .disable(DiagnosticCode::UnusedInclude); tests::check_fix_with_config(config, fixture_before, fixture_after) } From 4df5a7ae8169fb57eb968d6ec894f43fd1591a8c Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Wed, 10 Dec 2025 03:45:29 -0800 Subject: [PATCH 085/142] Bump OSS VS Code extension version to 0.45.0 Summary: In preparation for OSS release. Reviewed By: alanz Differential Revision: D88839310 fbshipit-source-id: f489b3e503c41e8302193cbdc78a46859967cbda --- editors/code/package-lock.json | 4 ++-- editors/code/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json index 5e28184214..66d227ce12 100644 --- a/editors/code/package-lock.json +++ b/editors/code/package-lock.json @@ -1,12 +1,12 @@ { "name": "erlang-language-platform", - "version": "0.44.0", + "version": "0.45.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "erlang-language-platform", - "version": "0.44.0", + "version": "0.45.0", "hasInstallScript": true, "license": "Apache2", "devDependencies": { diff --git a/editors/code/package.json b/editors/code/package.json index d54a4de805..ae34cde798 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -3,7 +3,7 @@ "description": "Erlang language server", "author": "Meta Platforms, Inc", "license": "Apache2", - "version": "0.44.0", + "version": "0.45.0", "icon": "images/elp-logo-color.png", "homepage": "https://whatsapp.github.io/erlang-language-platform/", "repository": { From 6dbefe9bd0b40ad5481a67311b27575ba8377cb5 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Wed, 10 Dec 2025 03:50:13 -0800 Subject: [PATCH 086/142] Add Homebrew installation instructions for Mac users Summary: Mac users can now install ELP using the dedicated Homebrew formula at https://formulae.brew.sh/formula/erlang-language-platform. This provides a simpler installation method compared to downloading binaries manually or building from source. Updated the installation documentation to highlight Homebrew as the easiest installation method for Mac users, placing it before the binary installation instructions. Reviewed By: TheGeorge Differential Revision: D88836106 fbshipit-source-id: 4df1340e7fe29e56e29e089d840f56092864b503 --- website/docs/get-started/install.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/website/docs/get-started/install.md b/website/docs/get-started/install.md index 4c8adb7bb1..74cc3b9159 100644 --- a/website/docs/get-started/install.md +++ b/website/docs/get-started/install.md @@ -4,9 +4,26 @@ sidebar_position: 2 # Install ELP -The easiest way to install to ELP is [from binary](#from-binary). It is also +The easiest way to install ELP on Mac is [using Homebrew](#using-homebrew-mac). +For other platforms, you can install [from binary](#from-binary). It is also possible to compile it [from source](#from-source). +## Using Homebrew (Mac) + +Mac users can install ELP using the dedicated Homebrew formula: + +``` +brew install erlang-language-platform +``` + +This will install the latest version of ELP and make it available in your PATH. + +For more information about the Homebrew formula, visit: +https://formulae.brew.sh/formula/erlang-language-platform + +Follow [these steps](cli.md#verify-elp-is-correctly-installed) to verify ELP is +correctly installed. + ## From Binary Visit our From a4ded31602cdd2d2a2c7ed29face13a31c3f630a Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Wed, 10 Dec 2025 07:00:19 -0800 Subject: [PATCH 087/142] 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 --- editors/code/package-lock.json | 4 ++-- editors/code/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json index 66d227ce12..1adcc191a7 100644 --- a/editors/code/package-lock.json +++ b/editors/code/package-lock.json @@ -1,12 +1,12 @@ { "name": "erlang-language-platform", - "version": "0.45.0", + "version": "0.46.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "erlang-language-platform", - "version": "0.45.0", + "version": "0.46.0", "hasInstallScript": true, "license": "Apache2", "devDependencies": { diff --git a/editors/code/package.json b/editors/code/package.json index ae34cde798..ef5b9a1700 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -3,7 +3,7 @@ "description": "Erlang language server", "author": "Meta Platforms, Inc", "license": "Apache2", - "version": "0.45.0", + "version": "0.46.0", "icon": "images/elp-logo-color.png", "homepage": "https://whatsapp.github.io/erlang-language-platform/", "repository": { From a239a322cf9f5584d620429d9efbed697e34ed88 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Wed, 10 Dec 2025 07:03:12 -0800 Subject: [PATCH 088/142] 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 --- eqwalizer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eqwalizer b/eqwalizer index 1e0f24c5b8..a835ce03b0 160000 --- a/eqwalizer +++ b/eqwalizer @@ -1 +1 @@ -Subproject commit 1e0f24c5b8f2b023fdd53d4571c3240430d31f69 +Subproject commit a835ce03b0308a9869af964e35a24466b49cda51 From ed8820b3b22c5ee603d49db6dd145734343e0765 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Wed, 10 Dec 2025 07:06:17 -0800 Subject: [PATCH 089/142] 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](https://github.com/WhatsApp/erlang-language-platform/actions/runs/20099328252). This applies the recommended remediation. Reviewed By: alanz Differential Revision: D88848551 fbshipit-source-id: 201fa45e4834b082e84a150343042ae5a6104189 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a2bb2508f..cf3834b5ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 From 29c017c565bf30af19bf98e312ec9632c4dc1266 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Wed, 10 Dec 2025 07:06:30 -0800 Subject: [PATCH 090/142] Polish extension README, improve package.json for discoverability 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 --- editors/code/README.md | 85 ++++++++++++++++++++++++++++++++++----- editors/code/package.json | 6 ++- 2 files changed, 78 insertions(+), 13 deletions(-) diff --git a/editors/code/README.md b/editors/code/README.md index b03ce757f2..ff5663d5ca 100644 --- a/editors/code/README.md +++ b/editors/code/README.md @@ -1,19 +1,82 @@ # Erlang Language Platform -Provide support for the [Erlang](https://www.erlang.org/) Programming Language. +The **Erlang Language Platform (ELP)** is a modern Language Server Protocol +(LSP) implementation for Erlang, developed by WhatsApp. ELP provides +comprehensive IDE support for Erlang development, bringing features you'd expect +from modern development tools to the Erlang ecosystem. + +Built with scalability and performance in mind, ELP uses incremental analysis to +handle large codebases efficiently, making it suitable for projects of any size. ## Features -* Syntax Highlighting -* Go To Definition -* Find References -* Auto-completion -* Call Hierarchy -* Signature Help -* Diagnostics -* Inlay Hints -* ... +### Code Intelligence + +- **📍 Go To Definition** - Jump to function, type, record definitions, etc +- **🔍 Find References** - Find all usages of functions, types, and variables +- **✨ Auto-completion** - Intelligent code completion for functions, variables, + modules and more +- **🏷️ Hover Information** - View documentation and type information on hover +- **📞 Call Hierarchy** - Explore caller/callee relationships +- **✍️ Signature Help** - Parameter hints while typing function calls +- **📋 Inlay Hints** - Display parameter names inline +- **🎨 Syntax Highlighting** - Full Erlang syntax support +- **📝 Code Lenses** - Inline actions for running tests and debugging +- **🔄 Workspace Symbol Search** - Quickly find symbols across your project + +### Code Quality + +- **🔧 Diagnostics** - Real-time error and warning detection +- **💡 Code Actions** - Quick fixes and refactoring suggestions +- **🧪 Eqwalizer Integration** - Type checking with + [Eqwalizer](https://github.com/WhatsApp/eqwalizer) + +### Debugging & Testing + +- **🐞 Integrated Debugger** - Debug Erlang applications with breakpoints and + variable inspection ## Documentation -See https://whatsapp.github.io/erlang-language-platform/ for more information. +For comprehensive documentation, visit: + +- **📚 + [Official Documentation](https://whatsapp.github.io/erlang-language-platform/)** +- **🚀 + [Getting Started Guide](https://whatsapp.github.io/erlang-language-platform/docs/get-started/)** +- **⚙️ + [Configuration Reference](https://whatsapp.github.io/erlang-language-platform/docs/configuration/)** +- **❓ [FAQ](https://whatsapp.github.io/erlang-language-platform/docs/faq/)** + +## Contributing + +We welcome contributions! Please see our +[Contributing Guide](https://github.com/WhatsApp/erlang-language-platform/blob/main/CONTRIBUTING.md) +for details. + +## Community & Support + +- **🐛 Issues**: + [GitHub Issues](https://github.com/WhatsApp/erlang-language-platform/issues) +- **💬 Discussions**: + [GitHub Discussions](https://github.com/WhatsApp/erlang-language-platform/discussions) + +## License + +ELP is dual-licensed under: + +- [Apache License 2.0](https://github.com/WhatsApp/erlang-language-platform/blob/main/LICENSE-APACHE) +- [MIT License](https://github.com/WhatsApp/erlang-language-platform/blob/main/LICENSE-MIT) + +## Acknowledgments + +ELP was designed at **WhatsApp** and inspired by the +[Rust Analyzer](https://rust-analyzer.github.io/) project. Special thanks to all +[contributors](https://github.com/WhatsApp/erlang-language-platform/graphs/contributors) +who have helped make ELP better. + +--- + +

+ Made with ❤️ by the WhatsApp team +

diff --git a/editors/code/package.json b/editors/code/package.json index ef5b9a1700..ebf55e7c86 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -1,6 +1,7 @@ { "name": "erlang-language-platform", - "description": "Erlang language server", + "displayName": "Erlang Language Platform", + "description": "Erlang Language Support for VS Code, by WhatsApp.", "author": "Meta Platforms, Inc", "license": "Apache2", "version": "0.46.0", @@ -20,7 +21,8 @@ "Testing" ], "keywords": [ - "elp" + "elp", + "erlang" ], "engines": { "vscode": "^1.75.0" From 0aef1f4005a6ac63aeb017f93c630841e083367f Mon Sep 17 00:00:00 2001 From: Victor Lanvin Date: Wed, 10 Dec 2025 08:08:55 -0800 Subject: [PATCH 091/142] Update fixtures for OTP 26/28 Summary: As title. Reviewed By: robertoaloi, TD5 Differential Revision: D88853775 fbshipit-source-id: 8954794c428f6f1139ebe4f393ed80d1836a2784 --- .../check/custom-OTP-26.pretty | 746 +----------------- .../check/custom-OTP-28.pretty | 746 +----------------- 2 files changed, 28 insertions(+), 1464 deletions(-) diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-26.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-26.pretty index 370f7689e9..efc8c47aa0 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-26.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-26.pretty @@ -37,12 +37,6 @@ Because in the expression's type: However the following candidate: [dynamic()] Differs from the expected type: tuple() ------------------------------- Detailed message ------------------------------ - - {atom(), string()} | [dynamic()] is not compatible with tuple() - because - [dynamic()] is not compatible with tuple() - error: index_out_of_bounds (See https://fb.me/eqwalizer_errors#index_out_of_bounds) ┌─ check/src/custom.erl:48:5 │ @@ -74,12 +68,6 @@ Because in the expression's type: However the following candidate: number() Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - atom() | number() is not compatible with atom() - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:68:5 │ @@ -96,12 +84,6 @@ Because in the expression's type: However the following candidate: number() Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - dynamic() | number() is not compatible with atom() - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:86:5 │ @@ -118,12 +100,6 @@ Because in the expression's type: However the following candidate: string() Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - 'foo' | 'ok' | 'error' | number() | string() is not compatible with atom() - because - string() is not compatible with atom() - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/custom.erl:91:1 │ @@ -202,12 +178,6 @@ Because in the expression's type: However the following candidate: number() Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - number() | atom() is not compatible with atom() - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:200:5 │ @@ -224,12 +194,6 @@ Because in the expression's type: However the following candidate: number() Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - number() | atom() is not compatible with atom() - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:206:5 │ @@ -249,15 +213,6 @@ Because in the expression's type: Differs from the expected type: number() } ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {atom(), 'undefined' | number()} is not compatible with {atom(), number()} - because - 'undefined' | number() is not compatible with number() - because - 'undefined' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:221:27 │ @@ -274,14 +229,6 @@ Because in the expression's type: However the following candidate: 'c_v' Differs from the expected type: 'a_v' | 'b_v' ------------------------------- Detailed message ------------------------------ - - 'a_v' | 'c_v' is not compatible with 'a_v' | 'b_v' - because - 'c_v' is not compatible with 'a_v' | 'b_v' - because - 'c_v' is not compatible with 'a_v' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:233:27 │ @@ -298,14 +245,6 @@ Because in the expression's type: However the following candidate: 'c_v' Differs from the expected type: 'a_v' | 'b_v' | 'undefined' ------------------------------- Detailed message ------------------------------ - - 'undefined' | 'a_v' | 'c_v' is not compatible with 'a_v' | 'b_v' | 'undefined' - because - 'c_v' is not compatible with 'a_v' | 'b_v' | 'undefined' - because - 'c_v' is not compatible with 'a_v' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:309:5 │ @@ -362,28 +301,13 @@ Because in the expression's type: However the following candidate: 'a' Differs from the expected type: #{term() => term()} | maps:iterator() ------------------------------- Detailed message ------------------------------ - - #{K => V} | 'a' is not compatible with #{term() => term()} | maps:iterator() - because - 'a' is not compatible with #{term() => term()} | maps:iterator() - because - 'a' is not compatible with #{term() => term()} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:425:20 │ 425 │ maps:filter(F, non_kv), - │ ^^^^^^ - │ │ - │ 'non_kv'. + │ ^^^^^^ 'non_kv'. Expression has type: 'non_kv' Context expected type: #{term() => term()} | maps:iterator() - │ - - 'non_kv' is not compatible with #{term() => term()} | maps:iterator() - because - 'non_kv' is not compatible with #{term() => term()} error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:454:5 @@ -403,13 +327,6 @@ Because in the expression's type: Context expects type: boolean() , ... } ------------------------------- Detailed message ------------------------------ - - #{number() => pid()} is not compatible with #{number() => boolean()} - the default associations are not compatible - because - pid() is not compatible with boolean() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:474:14 │ @@ -451,29 +368,13 @@ Because in the expression's type: Context expects type: 'a' , ... } ------------------------------- Detailed message ------------------------------ - - #{a := boolean(), b := boolean()} is not compatible with #{a => 'a', b => 'b'} - because - at key `a`: - #{a := boolean(), b := boolean()} is not compatible with #{a => 'a', b => 'b'} - because - boolean() is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:503:17 │ 503 │ maps:map(F, non_kv), - │ ^^^^^^ - │ │ - │ 'non_kv'. + │ ^^^^^^ 'non_kv'. Expression has type: 'non_kv' Context expected type: #{term() => term()} | maps:iterator() - │ - - 'non_kv' is not compatible with #{term() => term()} | maps:iterator() - because - 'non_kv' is not compatible with #{term() => term()} error: fun_arity_mismatch (See https://fb.me/eqwalizer_errors#fun_arity_mismatch) ┌─ check/src/custom.erl:538:9 @@ -502,14 +403,6 @@ Because in the expression's type: No candidate matches in the expected union. ] ------------------------------- Detailed message ------------------------------ - - [[[]]] is not compatible with [number() | 'a' | 'b'] - because - [[]] is not compatible with number() | 'a' | 'b' - because - [[]] is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:545:28 │ @@ -531,14 +424,6 @@ Because in the expression's type: ] ] ------------------------------- Detailed message ------------------------------ - - [[[[]]]] is not compatible with [[[]]] - because - [[[]]] is not compatible with [[]] - because - [[]] is not compatible with [] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:552:5 │ @@ -555,16 +440,9 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/custom.erl:555:9 │ 555 │ non_kv - │ ^^^^^^ - │ │ - │ 'non_kv'. + │ ^^^^^^ 'non_kv'. Expression has type: 'non_kv' Context expected type: #{term() => term()} | maps:iterator() - │ - - 'non_kv' is not compatible with #{term() => term()} | maps:iterator() - because - 'non_kv' is not compatible with #{term() => term()} error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/custom.erl:560:1 @@ -606,12 +484,6 @@ Because in the expression's type: However the following candidate: [] Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - [] | atom() is not compatible with atom() - because - [] is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:601:9 │ @@ -682,30 +554,13 @@ Because in the expression's type: Differs from the expected type: binary() ] ------------------------------- Detailed message ------------------------------ - - [number() | binary() | atom()] is not compatible with [binary()] | [number()] | [atom()] - because - [number() | binary() | atom()] is not compatible with [binary()] - because - number() | binary() | atom() is not compatible with binary() - because - number() is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:671:41 │ 671 │ maps_to_list_7_neg(Num) -> maps:to_list(Num). - │ ^^^ - │ │ - │ Num. + │ ^^^ Num. Expression has type: number() Context expected type: #{term() => term()} | maps:iterator() - │ - - number() is not compatible with #{term() => term()} | maps:iterator() - because - number() is not compatible with #{term() => term()} error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:675:25 @@ -723,13 +578,6 @@ Because in the expression's type: Context expects type: #{...} (no default association) The expected map has no default association while the type of the expression has one. ------------------------------- Detailed message ------------------------------ - - #{'b' | 'a' | 'c' => number() | string() | atom()} is not compatible with #{a => string(), b => number(), c => atom()} - key a is not present in the former map but is incompatible with its default association - because - number() | string() | atom() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:679:25 │ @@ -746,13 +594,6 @@ Because in the expression's type: Context expects type: #{...} (no default association) The expected map has no default association while the type of the expression has one. ------------------------------- Detailed message ------------------------------ - - #{'b' | 'a' | 'c' => number() | string() | atom()} is not compatible with #{a => string(), b => number() | boolean(), c => atom()} - key a is not present in the former map but is incompatible with its default association - because - number() | string() | atom() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:683:25 │ @@ -769,10 +610,6 @@ Because in the expression's type: Context expects type: #{a := ..., b := ..., ...} The type of the expression is missing the following required keys: a, b. ------------------------------- Detailed message ------------------------------ - -keys `a`, `b` are declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:697:44 │ @@ -797,13 +634,6 @@ Because in the expression's type: Context expects type: #{a := ..., b := ..., ...} The type of the expression is missing the following required keys: a, b. ------------------------------- Detailed message ------------------------------ - - #{'a' | 'b' => atom() | number()} is not compatible with #{a := atom(), b := number()} | #{a := atom()} - because - #{'a' | 'b' => atom() | number()} is not compatible with #{a := atom(), b := number()} - keys `a`, `b` are declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:721:9 │ @@ -822,14 +652,6 @@ Because in the expression's type: No candidate matches in the expected union. ) ------------------------------- Detailed message ------------------------------ - - fun((binary()) -> [number()]) is not compatible with fun((number()) -> boolean() | {'true', term()}) - because - [number()] is not compatible with boolean() | {'true', term()} - because - [number()] is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:739:20 │ @@ -846,12 +668,6 @@ Because in the expression's type: Context expects type: 'false' | 'true' | {'true', term()} No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - - 'wrong_ret' is not compatible with boolean() | {'true', term()} - because - 'wrong_ret' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:749:9 │ @@ -876,14 +692,6 @@ Because in the expression's type: However the following candidate: 'wrong_ret' Differs from the expected type: 'false' | 'true' | {'true', term()} ------------------------------- Detailed message ------------------------------ - - {'true', 'a'} | 'wrong_ret' is not compatible with boolean() | {'true', term()} - because - 'wrong_ret' is not compatible with boolean() | {'true', term()} - because - 'wrong_ret' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:827:9 │ @@ -902,16 +710,6 @@ Because in the expression's type: Differs from the expected type: 'false' | 'true' | ['a' | 'b'] ) ------------------------------- Detailed message ------------------------------ - - fun(('a' | 'b') -> ['a'] | 'true' | 'wrong_ret') is not compatible with fun(('a' | 'b') -> boolean() | ['a' | 'b']) - because - ['a'] | 'true' | 'wrong_ret' is not compatible with boolean() | ['a' | 'b'] - because - 'wrong_ret' is not compatible with boolean() | ['a' | 'b'] - because - 'wrong_ret' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:829:20 │ @@ -928,12 +726,6 @@ Because in the expression's type: Context expects type: 'false' | 'true' | ['a' | 'b'] No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - - 'wrong_ret' is not compatible with boolean() | ['a' | 'b'] - because - 'wrong_ret' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:837:9 │ @@ -951,15 +743,6 @@ Because in the expression's type: Differs from the expected type: 'false' | 'true' | [Item] ) ------------------------------- Detailed message ------------------------------ - - fun((dynamic()) -> {'true', 'a'} | 'true') is not compatible with fun((Item) -> boolean() | [Item]) - because - {'true', 'a'} | 'true' is not compatible with boolean() | [Item] - because - {'true', 'a'} is not compatible with boolean() | [Item] - expected union does not contain any tuple type of size 2 - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:837:20 │ @@ -976,10 +759,6 @@ Because in the expression's type: Context expects type: 'false' | 'true' | [dynamic()] No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - -expected union does not contain any tuple type of size 2 - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:839:9 │ @@ -995,12 +774,6 @@ Because in the expression's type: Here the type is: 'not_a_queue' Context expects type: {[Item], [Item]} ------------------------------- Detailed message ------------------------------ - - 'not_a_queue' is not compatible with queue:queue(Item) - because - 'not_a_queue' is not compatible with {[Item], [Item]} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:839:9 │ @@ -1016,12 +789,6 @@ Because in the expression's type: Here the type is: 'not_a_queue' Context expects type: {[dynamic()], [dynamic()]} ------------------------------- Detailed message ------------------------------ - - 'not_a_queue' is not compatible with queue:queue(dynamic()) - because - 'not_a_queue' is not compatible with {[dynamic()], [dynamic()]} - error: fun_arity_mismatch (See https://fb.me/eqwalizer_errors#fun_arity_mismatch) ┌─ check/src/custom.erl:846:9 │ @@ -1053,15 +820,6 @@ Because in the expression's type: Differs from the expected type: 'false' | 'true' | ['a' | 'b'] ) ------------------------------- Detailed message ------------------------------ - - fun(('a' | 'b') -> ['a'] | 'false' | {'true', 'a'}) is not compatible with fun(('a' | 'b') -> boolean() | ['a' | 'b']) - because - ['a'] | 'false' | {'true', 'a'} is not compatible with boolean() | ['a' | 'b'] - because - {'true', 'a'} is not compatible with boolean() | ['a' | 'b'] - expected union does not contain any tuple type of size 2 - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:857:20 │ @@ -1078,10 +836,6 @@ Because in the expression's type: Context expects type: 'false' | 'true' | ['a' | 'b'] No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - -expected union does not contain any tuple type of size 2 - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:873:9 │ @@ -1104,16 +858,6 @@ Because in the expression's type: Differs from the expected type: 'false' | 'true' | ['a' | 'b'] ) ------------------------------- Detailed message ------------------------------ - - fun(('a' | 'b') -> ['a'] | 'wrong_ret') is not compatible with fun(('a' | 'b') -> boolean() | ['a' | 'b']) - because - ['a'] | 'wrong_ret' is not compatible with boolean() | ['a' | 'b'] - because - 'wrong_ret' is not compatible with boolean() | ['a' | 'b'] - because - 'wrong_ret' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:881:17 │ @@ -1130,14 +874,6 @@ Because in the expression's type: However the following candidate: 'wrong_ret' Differs from the expected type: 'false' | 'true' | ['a' | 'b'] ------------------------------- Detailed message ------------------------------ - - ['a'] | 'wrong_ret' is not compatible with boolean() | ['a' | 'b'] - because - 'wrong_ret' is not compatible with boolean() | ['a' | 'b'] - because - 'wrong_ret' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:891:9 │ @@ -1156,14 +892,6 @@ Because in the expression's type: No candidate matches in the expected union. ) ------------------------------- Detailed message ------------------------------ - - fun((string()) -> atom()) is not compatible with fun((dynamic()) -> boolean() | [dynamic()]) - because - atom() is not compatible with boolean() | [dynamic()] - because - atom() is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:891:9 │ @@ -1182,14 +910,6 @@ Because in the expression's type: No candidate matches in the expected union. ) ------------------------------- Detailed message ------------------------------ - - fun((string()) -> atom()) is not compatible with fun(('a' | 'b') -> boolean() | ['a' | 'b']) - because - atom() is not compatible with boolean() | ['a' | 'b'] - because - atom() is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:900:9 │ @@ -1207,12 +927,6 @@ Because in the expression's type: Context expects type: boolean() | [atom()] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((atom()) -> boolean() | [atom()]) - because - string() is not compatible with boolean() | [atom()] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:900:9 │ @@ -1230,12 +944,6 @@ Because in the expression's type: Context expects type: boolean() | [dynamic(atom())] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((dynamic(atom())) -> boolean() | [dynamic(atom())]) - because - string() is not compatible with boolean() | [dynamic(atom())] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:909:9 │ @@ -1253,12 +961,6 @@ Because in the expression's type: Context expects type: boolean() | [atom()] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((atom()) -> boolean() | [atom()]) - because - string() is not compatible with boolean() | [atom()] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:909:9 │ @@ -1276,12 +978,6 @@ Because in the expression's type: Context expects type: boolean() | [dynamic(atom())] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((dynamic(atom())) -> boolean() | [dynamic(atom())]) - because - string() is not compatible with boolean() | [dynamic(atom())] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:919:9 │ @@ -1299,12 +995,6 @@ Because in the expression's type: Context expects type: boolean() | [atom()] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((atom()) -> boolean() | [atom()]) - because - string() is not compatible with boolean() | [atom()] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:919:9 │ @@ -1322,12 +1012,6 @@ Because in the expression's type: Context expects type: boolean() | [dynamic(atom())] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((dynamic(atom())) -> boolean() | [dynamic(atom())]) - because - string() is not compatible with boolean() | [dynamic(atom())] - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/custom.erl:925:1 │ @@ -1356,12 +1040,6 @@ Because in the expression's type: Context expects type: boolean() | [atom()] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((atom()) -> boolean() | [atom()]) - because - string() is not compatible with boolean() | [atom()] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:927:9 │ @@ -1379,12 +1057,6 @@ Because in the expression's type: Context expects type: boolean() | [dynamic(atom())] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((dynamic(atom())) -> boolean() | [dynamic(atom())]) - because - string() is not compatible with boolean() | [dynamic(atom())] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:955:5 │ @@ -1434,12 +1106,6 @@ Because in the expression's type: Context expects type: tuple() ] ------------------------------- Detailed message ------------------------------ - - ['non_tuple'] is not compatible with [tuple()] - because - 'non_tuple' is not compatible with tuple() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:975:5 │ @@ -1540,14 +1206,6 @@ Because in the expression's type: Differs from the expected type: number() ] ------------------------------- Detailed message ------------------------------ - - ['a' | number()] is not compatible with [number()] - because - 'a' | number() is not compatible with number() - because - 'a' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1042:15 │ @@ -1592,31 +1250,17 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/custom.erl:1100:5 │ 1100 │ proplists:get_value(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('k', L). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L). Expression has type: term() Context expected type: pid() | 'undefined' - │ - - term() is not compatible with pid() | 'undefined' - because - term() is not compatible with pid() error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1106:5 │ 1106 │ proplists:get_value(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('k', L). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L). Expression has type: term() Context expected type: pid() | 'undefined' | 'v' - │ - - term() is not compatible with pid() | 'undefined' | 'v' - because - term() is not compatible with pid() error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1111:5 @@ -1630,31 +1274,17 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/custom.erl:1117:5 │ 1117 │ proplists:get_value(k, L, 3). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('k', L, 3). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L, 3). Expression has type: term() Context expected type: pid() | number() - │ - - term() is not compatible with pid() | number() - because - term() is not compatible with pid() error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1123:5 │ 1123 │ proplists:get_value(k, L, my_default). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('k', L, 'my_default'). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L, 'my_default'). Expression has type: term() Context expected type: 'v1' | 'v2' | 'my_default' - │ - - term() is not compatible with 'v1' | 'v2' | 'my_default' - because - term() is not compatible with 'v1' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1129:5 @@ -1722,14 +1352,6 @@ Because in the expression's type: No candidate matches in the expected union. ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with [pid() | 'default'] - because - term() is not compatible with pid() | 'default' - because - term() is not compatible with pid() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1166:5 │ @@ -1748,14 +1370,6 @@ Because in the expression's type: No candidate matches in the expected union. ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with [pid() | 'default' | 'v'] - because - term() is not compatible with pid() | 'default' | 'v' - because - term() is not compatible with pid() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1171:5 │ @@ -1773,12 +1387,6 @@ Because in the expression's type: Context expects type: pid() ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with [pid()] - because - term() is not compatible with pid() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1176:5 │ @@ -1846,12 +1454,6 @@ Because in the expression's type: Context expects type: 'c' ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with ['c'] - because - term() is not compatible with 'c' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1197:5 │ @@ -1870,14 +1472,6 @@ Because in the expression's type: No candidate matches in the expected union. ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with ['a' | 'b' | 'c'] - because - term() is not compatible with 'a' | 'b' | 'c' - because - term() is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1202:24 │ @@ -1903,41 +1497,21 @@ Because in the expression's type: Context expects type: 'a' ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with ['a'] - because - term() is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1215:5 │ 1215 │ proplists:get_value(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('k', L). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L). Expression has type: term() Context expected type: 'a' | pid() - │ - - term() is not compatible with 'a' | pid() - because - term() is not compatible with 'a' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1220:5 │ 1220 │ proplists:get_value(k, b). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('k', 'b'). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', 'b'). Expression has type: term() Context expected type: 'a' | pid() - │ - - term() is not compatible with 'a' | pid() - because - term() is not compatible with 'a' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1220:28 @@ -1987,12 +1561,6 @@ Because in the expression's type: However the following candidate: tuple() Differs from the expected type: 'none' ------------------------------- Detailed message ------------------------------ - - 'none' | tuple() is not compatible with 'none' - because - tuple() is not compatible with 'none' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1252:29 │ @@ -2065,17 +1633,6 @@ Because in the expression's type: ] , [term()]} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {[[term()]], [term()]} is not compatible with {[plist('a', 'b')], plist('a', 'b')} - because - [[term()]] is not compatible with [plist('a', 'b')] - because - [term()] is not compatible with plist('a', 'b') - because - [term()] is not compatible with ['a' | {'a', 'b'}] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1315:21 │ @@ -2120,46 +1677,25 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/custom.erl:1363:5 │ 1363 │ proplists:get_value(a, [a]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('a', ['a']). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('a', ['a']). Expression has type: term() Context expected type: 'true' | 'undefined' - │ - - term() is not compatible with 'true' | 'undefined' - because - term() is not compatible with 'true' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1368:5 │ 1368 │ proplists:get_value(X, [a]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value(X, ['a']). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value(X, ['a']). Expression has type: term() Context expected type: 'true' | 'undefined' - │ - - term() is not compatible with 'true' | 'undefined' - because - term() is not compatible with 'true' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1373:5 │ 1373 │ proplists:get_value(a, [a], b). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('a', ['a'], 'b'). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('a', ['a'], 'b'). Expression has type: term() Context expected type: 'true' | 'b' - │ - - term() is not compatible with 'true' | 'b' - because - term() is not compatible with 'true' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1416:5 @@ -2194,12 +1730,6 @@ Because in the expression's type: Context expects type: tuple() ] ------------------------------- Detailed message ------------------------------ - - [number()] is not compatible with [tuple()] - because - number() is not compatible with tuple() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1448:5 │ @@ -2222,14 +1752,6 @@ Because in the expression's type: Differs from the expected type: number() ] ------------------------------- Detailed message ------------------------------ - - [string() | number()] is not compatible with [number()] - because - string() | number() is not compatible with number() - because - string() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1482:5 │ @@ -2373,17 +1895,6 @@ Because in the expression's type: , A} ] ------------------------------- Detailed message ------------------------------ - - [{A, B} | {B, A}] is not compatible with [{A, B}] - because - {A, B} | {B, A} is not compatible with {A, B} - because - at tuple index 1: - {B, A} is not compatible with {A, B} - because - B is not compatible with A - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1757:5 │ @@ -2404,10 +1915,6 @@ Because in the expression's type: Context expects type: #{...} The expected map has no corresponding key for: a. ------------------------------- Detailed message ------------------------------ - -key `a` is declared in the former but not in the latter and the latter map has no default association - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1769:18 │ @@ -2451,10 +1958,6 @@ Because in the expression's type: Context expects type: #{b := ..., ...} The type of the expression is missing the following required keys: b. ------------------------------- Detailed message ------------------------------ - -key `b` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1826:5 │ @@ -2472,10 +1975,6 @@ Because in the expression's type: Context expects type: #{b := ..., ...} The type of the expression is missing the following required keys: b. ------------------------------- Detailed message ------------------------------ - -key `b` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1827:12 │ @@ -2501,10 +2000,6 @@ Because in the expression's type: Context expects type: #{b := ..., ...} The type of the expression is missing the following required keys: b. ------------------------------- Detailed message ------------------------------ - -key `b` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1854:5 │ @@ -2525,16 +2020,9 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/custom.erl:1916:23 │ 1916 │ custom_overloaded(X). - │ ^ - │ │ - │ X. + │ ^ X. Expression has type: term() Context expected type: atom() | binary() - │ - - term() is not compatible with atom() | binary() - because - term() is not compatible with atom() error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1939:5 @@ -2554,15 +2042,6 @@ Because in the expression's type: Differs from the expected type: number() } ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {atom(), number() | pid()} is not compatible with {atom(), number()} - because - number() | pid() is not compatible with number() - because - pid() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2041:5 │ @@ -2578,14 +2057,6 @@ Because in the expression's type: Here the type is: string() | binary() Context expects type: string() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with file:filename() - because - string() | binary() is not compatible with file:filename() - because - string() | binary() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2046:5 │ @@ -2601,14 +2072,6 @@ Because in the expression's type: Here the type is: string() | binary() Context expects type: string() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with file:filename() - because - string() | binary() is not compatible with file:filename() - because - string() | binary() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2051:5 │ @@ -2624,14 +2087,6 @@ Because in the expression's type: Here the type is: string() | binary() Context expects type: string() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with file:filename() - because - string() | binary() is not compatible with file:filename() - because - string() | binary() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2066:5 │ @@ -2648,14 +2103,6 @@ Because in the expression's type: However the following candidate: string() Differs from the expected type: binary() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with binary() - because - string() | binary() is not compatible with binary() - because - string() is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2081:19 │ @@ -2674,16 +2121,6 @@ Because in the expression's type: Differs from the expected type: string() | atom() | file:deep_list() | binary() ] ------------------------------- Detailed message ------------------------------ - - [binary() | pid()] is not compatible with [file:name_all()] - because - binary() | pid() is not compatible with file:name_all() - because - binary() | pid() is not compatible with string() | atom() | file:deep_list() | binary() - because - pid() is not compatible with string() | atom() | file:deep_list() | binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2086:5 │ @@ -2699,14 +2136,6 @@ Because in the expression's type: Here the type is: string() | binary() Context expects type: string() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with file:filename() - because - string() | binary() is not compatible with file:filename() - because - string() | binary() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2091:5 │ @@ -2722,14 +2151,6 @@ Because in the expression's type: Here the type is: string() | binary() Context expects type: string() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with file:filename() - because - string() | binary() is not compatible with file:filename() - because - string() | binary() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2096:5 │ @@ -2745,14 +2166,6 @@ Because in the expression's type: Here the type is: string() | binary() Context expects type: string() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with file:filename() - because - string() | binary() is not compatible with file:filename() - because - string() | binary() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2111:5 │ @@ -2769,14 +2182,6 @@ Because in the expression's type: However the following candidate: string() Differs from the expected type: binary() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with binary() - because - string() | binary() is not compatible with binary() - because - string() is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2121:25 │ @@ -2793,14 +2198,6 @@ Because in the expression's type: Context expects type: string() | atom() | file:deep_list() | binary() No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - - pid() is not compatible with file:name_all() - because - pid() is not compatible with string() | atom() | file:deep_list() | binary() - because - pid() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2150:5 │ @@ -2822,17 +2219,6 @@ Because in the expression's type: ] , [atom() | number()]} ------------------------------- Detailed message ------------------------------ - - queue:queue(atom() | number()) is not compatible with queue:queue(number()) - because - {[atom() | number()], [atom() | number()]} is not compatible with queue:queue(number()) - because - at tuple index 1: - {[atom() | number()], [atom() | number()]} is not compatible with {[number()], [number()]} - because - [atom() | number()] is not compatible with [number()] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2191:5 │ @@ -2851,15 +2237,6 @@ Because in the expression's type: Context expects type: #{count := ..., ...} The type of the expression is missing the following required keys: count. ------------------------------- Detailed message ------------------------------ - - #{count := number(), module := 'foo'} | #{module := 'foo'} is not compatible with state1() - because - #{count := number(), module := 'foo'} | #{module := 'foo'} is not compatible with #{count := number(), module := atom()} - because - #{module := 'foo'} is not compatible with #{count := number(), module := atom()} - key `count` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2223:13 │ @@ -2884,10 +2261,6 @@ Because in the expression's type: Context expects type: #{a := ..., ...} The type of the expression is missing the following required keys: a. ------------------------------- Detailed message ------------------------------ - -key `a` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2320:5 │ @@ -2908,10 +2281,6 @@ Because in the expression's type: Context expects type: #{a := ..., b := ..., ...} The type of the expression is missing the following required keys: a, b. ------------------------------- Detailed message ------------------------------ - -keys `a`, `b` are declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2342:5 │ @@ -2932,13 +2301,6 @@ Because in the expression's type: Context expects type: atom() , ... } ------------------------------- Detailed message ------------------------------ - - #{atom() => binary()} is not compatible with #{atom() => atom()} - the default associations are not compatible - because - binary() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2354:23 │ @@ -2955,12 +2317,6 @@ Because in the expression's type: Context expects type: 'false' | 'true' | {'true', term()} No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - - 'err' is not compatible with boolean() | {'true', term()} - because - 'err' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2362:5 │ @@ -2979,13 +2335,6 @@ Because in the expression's type: Context expects type: atom() , ... } ------------------------------- Detailed message ------------------------------ - - #{atom() => binary()} is not compatible with #{atom() => atom()} - the default associations are not compatible - because - binary() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2363:45 │ @@ -3026,12 +2375,6 @@ Because in the expression's type: Context expects type: iolist() | binary() No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - - atom() is not compatible with iodata() | unicode:charlist() - because - atom() is not compatible with iolist() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2386:5 │ @@ -3060,17 +2403,6 @@ Because in the expression's type: } ] ------------------------------- Detailed message ------------------------------ - - [{'return', 'something'}] is not compatible with [{'newline', 'cr' | 'lf' | 'any' | 'crlf' | 'anycrlf'} | 'notempty_atstart' | 'noteol' | 'bsr_unicode' | 'notbol' | 'global' | {'match_limit_recursion', number()} | 'bsr_anycrlf' | re:compile_option() | {'match_limit', number()} | {'offset', number()} | 'notempty' | {'return', 'iodata' | 'list' | 'binary'} | 'anchored'] - because - {'return', 'something'} is not compatible with {'newline', 'cr' | 'lf' | 'any' | 'crlf' | 'anycrlf'} | 'notempty_atstart' | 'noteol' | 'bsr_unicode' | 'notbol' | 'global' | {'match_limit_recursion', number()} | 'bsr_anycrlf' | re:compile_option() | {'match_limit', number()} | {'offset', number()} | 'notempty' | {'return', 'iodata' | 'list' | 'binary'} | 'anchored' - because - at tuple index 2: - {'return', 'something'} is not compatible with {'return', 'iodata' | 'list' | 'binary'} - because - 'something' is not compatible with 'iodata' | 'list' | 'binary' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2506:5 │ @@ -3090,15 +2422,6 @@ Because in the expression's type: ] , [atom()]} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {[number()], [atom()]} is not compatible with {[atom()], [number()]} - because - [number()] is not compatible with [atom()] - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2518:5 │ @@ -3120,18 +2443,6 @@ Because in the expression's type: ] , [{term(), atom()}]} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {[{term(), number()}], [{term(), atom()}]} is not compatible with {[{term(), atom()}], [{term(), number()}]} - because - [{term(), number()}] is not compatible with [{term(), atom()}] - because - at tuple index 2: - {term(), number()} is not compatible with {term(), atom()} - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2536:5 │ @@ -3153,18 +2464,6 @@ Because in the expression's type: ] } ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {[{'ok', atom()}], [{'ok', atom()} | {'error', term()}]} is not compatible with {[{'ok', atom()}], [{'error', term()}]} - because - [{'ok', atom()} | {'error', term()}] is not compatible with [{'error', term()}] - because - {'ok', atom()} | {'error', term()} is not compatible with {'error', term()} - because - at tuple index 1: - {'ok', atom()} is not compatible with {'error', term()} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2576:33 │ @@ -3181,10 +2480,6 @@ Because in the expression's type: Context expects type: #{a := ..., ...} The type of the expression is missing the following required keys: a. ------------------------------- Detailed message ------------------------------ - -key `a` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2586:33 │ @@ -3202,15 +2497,6 @@ Because in the expression's type: Context expects type: 'true' , ... } ------------------------------- Detailed message ------------------------------ - - #{a => number()} is not compatible with #{a => 'true'} - because - at key `a`: - #{a => number()} is not compatible with #{a => 'true'} - because - number() is not compatible with 'true' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2596:33 │ @@ -3227,10 +2513,6 @@ Because in the expression's type: Context expects type: #{a := ..., ...} The type of the expression is missing the following required keys: a. ------------------------------- Detailed message ------------------------------ - -key `a` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2709:40 │ diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-28.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-28.pretty index ec107b3c14..5220990b8f 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-28.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-28.pretty @@ -37,12 +37,6 @@ Because in the expression's type: However the following candidate: [dynamic()] Differs from the expected type: tuple() ------------------------------- Detailed message ------------------------------ - - {atom(), string()} | [dynamic()] is not compatible with tuple() - because - [dynamic()] is not compatible with tuple() - error: index_out_of_bounds (See https://fb.me/eqwalizer_errors#index_out_of_bounds) ┌─ check/src/custom.erl:48:5 │ @@ -74,12 +68,6 @@ Because in the expression's type: However the following candidate: number() Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - atom() | number() is not compatible with atom() - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:68:5 │ @@ -96,12 +84,6 @@ Because in the expression's type: However the following candidate: number() Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - dynamic() | number() is not compatible with atom() - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:86:5 │ @@ -118,12 +100,6 @@ Because in the expression's type: However the following candidate: string() Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - 'foo' | 'ok' | 'error' | number() | string() is not compatible with atom() - because - string() is not compatible with atom() - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/custom.erl:91:1 │ @@ -202,12 +178,6 @@ Because in the expression's type: However the following candidate: number() Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - number() | atom() is not compatible with atom() - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:200:5 │ @@ -224,12 +194,6 @@ Because in the expression's type: However the following candidate: number() Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - number() | atom() is not compatible with atom() - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:206:5 │ @@ -249,15 +213,6 @@ Because in the expression's type: Differs from the expected type: number() } ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {atom(), 'undefined' | number()} is not compatible with {atom(), number()} - because - 'undefined' | number() is not compatible with number() - because - 'undefined' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:221:27 │ @@ -274,14 +229,6 @@ Because in the expression's type: However the following candidate: 'c_v' Differs from the expected type: 'a_v' | 'b_v' ------------------------------- Detailed message ------------------------------ - - 'a_v' | 'c_v' is not compatible with 'a_v' | 'b_v' - because - 'c_v' is not compatible with 'a_v' | 'b_v' - because - 'c_v' is not compatible with 'a_v' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:233:27 │ @@ -298,14 +245,6 @@ Because in the expression's type: However the following candidate: 'c_v' Differs from the expected type: 'a_v' | 'b_v' | 'undefined' ------------------------------- Detailed message ------------------------------ - - 'undefined' | 'a_v' | 'c_v' is not compatible with 'a_v' | 'b_v' | 'undefined' - because - 'c_v' is not compatible with 'a_v' | 'b_v' | 'undefined' - because - 'c_v' is not compatible with 'a_v' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:309:5 │ @@ -362,28 +301,13 @@ Because in the expression's type: However the following candidate: 'a' Differs from the expected type: #{term() => term()} | maps:iterator() ------------------------------- Detailed message ------------------------------ - - #{K => V} | 'a' is not compatible with #{term() => term()} | maps:iterator() - because - 'a' is not compatible with #{term() => term()} | maps:iterator() - because - 'a' is not compatible with #{term() => term()} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:425:20 │ 425 │ maps:filter(F, non_kv), - │ ^^^^^^ - │ │ - │ 'non_kv'. + │ ^^^^^^ 'non_kv'. Expression has type: 'non_kv' Context expected type: #{term() => term()} | maps:iterator() - │ - - 'non_kv' is not compatible with #{term() => term()} | maps:iterator() - because - 'non_kv' is not compatible with #{term() => term()} error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:454:5 @@ -403,13 +327,6 @@ Because in the expression's type: Context expects type: boolean() , ... } ------------------------------- Detailed message ------------------------------ - - #{number() => pid()} is not compatible with #{number() => boolean()} - the default associations are not compatible - because - pid() is not compatible with boolean() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:474:14 │ @@ -451,29 +368,13 @@ Because in the expression's type: Context expects type: 'a' , ... } ------------------------------- Detailed message ------------------------------ - - #{a := boolean(), b := boolean()} is not compatible with #{a => 'a', b => 'b'} - because - at key `a`: - #{a := boolean(), b := boolean()} is not compatible with #{a => 'a', b => 'b'} - because - boolean() is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:503:17 │ 503 │ maps:map(F, non_kv), - │ ^^^^^^ - │ │ - │ 'non_kv'. + │ ^^^^^^ 'non_kv'. Expression has type: 'non_kv' Context expected type: #{term() => term()} | maps:iterator() - │ - - 'non_kv' is not compatible with #{term() => term()} | maps:iterator() - because - 'non_kv' is not compatible with #{term() => term()} error: fun_arity_mismatch (See https://fb.me/eqwalizer_errors#fun_arity_mismatch) ┌─ check/src/custom.erl:538:9 @@ -502,14 +403,6 @@ Because in the expression's type: No candidate matches in the expected union. ] ------------------------------- Detailed message ------------------------------ - - [[[]]] is not compatible with [number() | 'a' | 'b'] - because - [[]] is not compatible with number() | 'a' | 'b' - because - [[]] is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:545:28 │ @@ -531,14 +424,6 @@ Because in the expression's type: ] ] ------------------------------- Detailed message ------------------------------ - - [[[[]]]] is not compatible with [[[]]] - because - [[[]]] is not compatible with [[]] - because - [[]] is not compatible with [] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:552:5 │ @@ -555,16 +440,9 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/custom.erl:555:9 │ 555 │ non_kv - │ ^^^^^^ - │ │ - │ 'non_kv'. + │ ^^^^^^ 'non_kv'. Expression has type: 'non_kv' Context expected type: #{term() => term()} | maps:iterator() - │ - - 'non_kv' is not compatible with #{term() => term()} | maps:iterator() - because - 'non_kv' is not compatible with #{term() => term()} error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/custom.erl:560:1 @@ -606,12 +484,6 @@ Because in the expression's type: However the following candidate: [] Differs from the expected type: atom() ------------------------------- Detailed message ------------------------------ - - [] | atom() is not compatible with atom() - because - [] is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:601:9 │ @@ -682,30 +554,13 @@ Because in the expression's type: Differs from the expected type: binary() ] ------------------------------- Detailed message ------------------------------ - - [number() | binary() | atom()] is not compatible with [binary()] | [number()] | [atom()] - because - [number() | binary() | atom()] is not compatible with [binary()] - because - number() | binary() | atom() is not compatible with binary() - because - number() is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:671:41 │ 671 │ maps_to_list_7_neg(Num) -> maps:to_list(Num). - │ ^^^ - │ │ - │ Num. + │ ^^^ Num. Expression has type: number() Context expected type: #{term() => term()} | maps:iterator() - │ - - number() is not compatible with #{term() => term()} | maps:iterator() - because - number() is not compatible with #{term() => term()} error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:675:25 @@ -723,13 +578,6 @@ Because in the expression's type: Context expects type: #{...} (no default association) The expected map has no default association while the type of the expression has one. ------------------------------- Detailed message ------------------------------ - - #{'b' | 'a' | 'c' => number() | string() | atom()} is not compatible with #{a => string(), b => number(), c => atom()} - key a is not present in the former map but is incompatible with its default association - because - number() | string() | atom() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:679:25 │ @@ -746,13 +594,6 @@ Because in the expression's type: Context expects type: #{...} (no default association) The expected map has no default association while the type of the expression has one. ------------------------------- Detailed message ------------------------------ - - #{'b' | 'a' | 'c' => number() | string() | atom()} is not compatible with #{a => string(), b => number() | boolean(), c => atom()} - key a is not present in the former map but is incompatible with its default association - because - number() | string() | atom() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:683:25 │ @@ -769,10 +610,6 @@ Because in the expression's type: Context expects type: #{a := ..., b := ..., ...} The type of the expression is missing the following required keys: a, b. ------------------------------- Detailed message ------------------------------ - -keys `a`, `b` are declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:697:44 │ @@ -797,13 +634,6 @@ Because in the expression's type: Context expects type: #{a := ..., b := ..., ...} The type of the expression is missing the following required keys: a, b. ------------------------------- Detailed message ------------------------------ - - #{'a' | 'b' => atom() | number()} is not compatible with #{a := atom(), b := number()} | #{a := atom()} - because - #{'a' | 'b' => atom() | number()} is not compatible with #{a := atom(), b := number()} - keys `a`, `b` are declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:721:9 │ @@ -822,14 +652,6 @@ Because in the expression's type: No candidate matches in the expected union. ) ------------------------------- Detailed message ------------------------------ - - fun((binary()) -> [number()]) is not compatible with fun((number()) -> boolean() | {'true', term()}) - because - [number()] is not compatible with boolean() | {'true', term()} - because - [number()] is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:739:20 │ @@ -846,12 +668,6 @@ Because in the expression's type: Context expects type: 'false' | 'true' | {'true', term()} No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - - 'wrong_ret' is not compatible with boolean() | {'true', term()} - because - 'wrong_ret' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:749:9 │ @@ -876,14 +692,6 @@ Because in the expression's type: However the following candidate: 'wrong_ret' Differs from the expected type: 'false' | 'true' | {'true', term()} ------------------------------- Detailed message ------------------------------ - - {'true', 'a'} | 'wrong_ret' is not compatible with boolean() | {'true', term()} - because - 'wrong_ret' is not compatible with boolean() | {'true', term()} - because - 'wrong_ret' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:827:9 │ @@ -902,16 +710,6 @@ Because in the expression's type: Differs from the expected type: 'false' | 'true' | ['a' | 'b'] ) ------------------------------- Detailed message ------------------------------ - - fun(('a' | 'b') -> ['a'] | 'true' | 'wrong_ret') is not compatible with fun(('a' | 'b') -> boolean() | ['a' | 'b']) - because - ['a'] | 'true' | 'wrong_ret' is not compatible with boolean() | ['a' | 'b'] - because - 'wrong_ret' is not compatible with boolean() | ['a' | 'b'] - because - 'wrong_ret' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:829:20 │ @@ -928,12 +726,6 @@ Because in the expression's type: Context expects type: 'false' | 'true' | ['a' | 'b'] No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - - 'wrong_ret' is not compatible with boolean() | ['a' | 'b'] - because - 'wrong_ret' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:837:9 │ @@ -951,15 +743,6 @@ Because in the expression's type: Differs from the expected type: 'false' | 'true' | [Item] ) ------------------------------- Detailed message ------------------------------ - - fun((dynamic()) -> {'true', 'a'} | 'true') is not compatible with fun((Item) -> boolean() | [Item]) - because - {'true', 'a'} | 'true' is not compatible with boolean() | [Item] - because - {'true', 'a'} is not compatible with boolean() | [Item] - expected union does not contain any tuple type of size 2 - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:837:20 │ @@ -976,10 +759,6 @@ Because in the expression's type: Context expects type: 'false' | 'true' | [dynamic()] No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - -expected union does not contain any tuple type of size 2 - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:839:9 │ @@ -995,12 +774,6 @@ Because in the expression's type: Here the type is: 'not_a_queue' Context expects type: {[Item], [Item]} ------------------------------- Detailed message ------------------------------ - - 'not_a_queue' is not compatible with queue:queue(Item) - because - 'not_a_queue' is not compatible with {[Item], [Item]} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:839:9 │ @@ -1016,12 +789,6 @@ Because in the expression's type: Here the type is: 'not_a_queue' Context expects type: {[dynamic()], [dynamic()]} ------------------------------- Detailed message ------------------------------ - - 'not_a_queue' is not compatible with queue:queue(dynamic()) - because - 'not_a_queue' is not compatible with {[dynamic()], [dynamic()]} - error: fun_arity_mismatch (See https://fb.me/eqwalizer_errors#fun_arity_mismatch) ┌─ check/src/custom.erl:846:9 │ @@ -1053,15 +820,6 @@ Because in the expression's type: Differs from the expected type: 'false' | 'true' | ['a' | 'b'] ) ------------------------------- Detailed message ------------------------------ - - fun(('a' | 'b') -> ['a'] | 'false' | {'true', 'a'}) is not compatible with fun(('a' | 'b') -> boolean() | ['a' | 'b']) - because - ['a'] | 'false' | {'true', 'a'} is not compatible with boolean() | ['a' | 'b'] - because - {'true', 'a'} is not compatible with boolean() | ['a' | 'b'] - expected union does not contain any tuple type of size 2 - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:857:20 │ @@ -1078,10 +836,6 @@ Because in the expression's type: Context expects type: 'false' | 'true' | ['a' | 'b'] No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - -expected union does not contain any tuple type of size 2 - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:873:9 │ @@ -1104,16 +858,6 @@ Because in the expression's type: Differs from the expected type: 'false' | 'true' | ['a' | 'b'] ) ------------------------------- Detailed message ------------------------------ - - fun(('a' | 'b') -> ['a'] | 'wrong_ret') is not compatible with fun(('a' | 'b') -> boolean() | ['a' | 'b']) - because - ['a'] | 'wrong_ret' is not compatible with boolean() | ['a' | 'b'] - because - 'wrong_ret' is not compatible with boolean() | ['a' | 'b'] - because - 'wrong_ret' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:881:17 │ @@ -1130,14 +874,6 @@ Because in the expression's type: However the following candidate: 'wrong_ret' Differs from the expected type: 'false' | 'true' | ['a' | 'b'] ------------------------------- Detailed message ------------------------------ - - ['a'] | 'wrong_ret' is not compatible with boolean() | ['a' | 'b'] - because - 'wrong_ret' is not compatible with boolean() | ['a' | 'b'] - because - 'wrong_ret' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:891:9 │ @@ -1156,14 +892,6 @@ Because in the expression's type: No candidate matches in the expected union. ) ------------------------------- Detailed message ------------------------------ - - fun((string()) -> atom()) is not compatible with fun((dynamic()) -> boolean() | [dynamic()]) - because - atom() is not compatible with boolean() | [dynamic()] - because - atom() is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:891:9 │ @@ -1182,14 +910,6 @@ Because in the expression's type: No candidate matches in the expected union. ) ------------------------------- Detailed message ------------------------------ - - fun((string()) -> atom()) is not compatible with fun(('a' | 'b') -> boolean() | ['a' | 'b']) - because - atom() is not compatible with boolean() | ['a' | 'b'] - because - atom() is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:900:9 │ @@ -1207,12 +927,6 @@ Because in the expression's type: Context expects type: boolean() | [atom()] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((atom()) -> boolean() | [atom()]) - because - string() is not compatible with boolean() | [atom()] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:900:9 │ @@ -1230,12 +944,6 @@ Because in the expression's type: Context expects type: boolean() | [dynamic(atom())] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((dynamic(atom())) -> boolean() | [dynamic(atom())]) - because - string() is not compatible with boolean() | [dynamic(atom())] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:909:9 │ @@ -1253,12 +961,6 @@ Because in the expression's type: Context expects type: boolean() | [atom()] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((atom()) -> boolean() | [atom()]) - because - string() is not compatible with boolean() | [atom()] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:909:9 │ @@ -1276,12 +978,6 @@ Because in the expression's type: Context expects type: boolean() | [dynamic(atom())] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((dynamic(atom())) -> boolean() | [dynamic(atom())]) - because - string() is not compatible with boolean() | [dynamic(atom())] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:919:9 │ @@ -1299,12 +995,6 @@ Because in the expression's type: Context expects type: boolean() | [atom()] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((atom()) -> boolean() | [atom()]) - because - string() is not compatible with boolean() | [atom()] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:919:9 │ @@ -1322,12 +1012,6 @@ Because in the expression's type: Context expects type: boolean() | [dynamic(atom())] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((dynamic(atom())) -> boolean() | [dynamic(atom())]) - because - string() is not compatible with boolean() | [dynamic(atom())] - error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) ┌─ check/src/custom.erl:925:1 │ @@ -1356,12 +1040,6 @@ Because in the expression's type: Context expects type: boolean() | [atom()] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((atom()) -> boolean() | [atom()]) - because - string() is not compatible with boolean() | [atom()] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:927:9 │ @@ -1379,12 +1057,6 @@ Because in the expression's type: Context expects type: boolean() | [dynamic(atom())] ) ------------------------------- Detailed message ------------------------------ - - fun((atom()) -> string()) is not compatible with fun((dynamic(atom())) -> boolean() | [dynamic(atom())]) - because - string() is not compatible with boolean() | [dynamic(atom())] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:955:5 │ @@ -1434,12 +1106,6 @@ Because in the expression's type: Context expects type: tuple() ] ------------------------------- Detailed message ------------------------------ - - ['non_tuple'] is not compatible with [tuple()] - because - 'non_tuple' is not compatible with tuple() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:975:5 │ @@ -1540,14 +1206,6 @@ Because in the expression's type: Differs from the expected type: number() ] ------------------------------- Detailed message ------------------------------ - - ['a' | number()] is not compatible with [number()] - because - 'a' | number() is not compatible with number() - because - 'a' is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1042:15 │ @@ -1592,31 +1250,17 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/custom.erl:1100:5 │ 1100 │ proplists:get_value(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('k', L). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L). Expression has type: term() Context expected type: pid() | 'undefined' - │ - - term() is not compatible with pid() | 'undefined' - because - term() is not compatible with pid() error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1106:5 │ 1106 │ proplists:get_value(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('k', L). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L). Expression has type: term() Context expected type: pid() | 'undefined' | 'v' - │ - - term() is not compatible with pid() | 'undefined' | 'v' - because - term() is not compatible with pid() error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1111:5 @@ -1630,31 +1274,17 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/custom.erl:1117:5 │ 1117 │ proplists:get_value(k, L, 3). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('k', L, 3). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L, 3). Expression has type: term() Context expected type: pid() | number() - │ - - term() is not compatible with pid() | number() - because - term() is not compatible with pid() error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1123:5 │ 1123 │ proplists:get_value(k, L, my_default). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('k', L, 'my_default'). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L, 'my_default'). Expression has type: term() Context expected type: 'v1' | 'v2' | 'my_default' - │ - - term() is not compatible with 'v1' | 'v2' | 'my_default' - because - term() is not compatible with 'v1' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1129:5 @@ -1722,14 +1352,6 @@ Because in the expression's type: No candidate matches in the expected union. ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with [pid() | 'default'] - because - term() is not compatible with pid() | 'default' - because - term() is not compatible with pid() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1166:5 │ @@ -1748,14 +1370,6 @@ Because in the expression's type: No candidate matches in the expected union. ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with [pid() | 'default' | 'v'] - because - term() is not compatible with pid() | 'default' | 'v' - because - term() is not compatible with pid() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1171:5 │ @@ -1773,12 +1387,6 @@ Because in the expression's type: Context expects type: pid() ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with [pid()] - because - term() is not compatible with pid() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1176:5 │ @@ -1846,12 +1454,6 @@ Because in the expression's type: Context expects type: 'c' ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with ['c'] - because - term() is not compatible with 'c' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1197:5 │ @@ -1870,14 +1472,6 @@ Because in the expression's type: No candidate matches in the expected union. ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with ['a' | 'b' | 'c'] - because - term() is not compatible with 'a' | 'b' | 'c' - because - term() is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1202:24 │ @@ -1903,41 +1497,21 @@ Because in the expression's type: Context expects type: 'a' ] ------------------------------- Detailed message ------------------------------ - - [term()] is not compatible with ['a'] - because - term() is not compatible with 'a' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1215:5 │ 1215 │ proplists:get_value(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('k', L). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L). Expression has type: term() Context expected type: 'a' | pid() - │ - - term() is not compatible with 'a' | pid() - because - term() is not compatible with 'a' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1220:5 │ 1220 │ proplists:get_value(k, b). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('k', 'b'). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', 'b'). Expression has type: term() Context expected type: 'a' | pid() - │ - - term() is not compatible with 'a' | pid() - because - term() is not compatible with 'a' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1220:28 @@ -1987,12 +1561,6 @@ Because in the expression's type: However the following candidate: tuple() Differs from the expected type: 'none' ------------------------------- Detailed message ------------------------------ - - 'none' | tuple() is not compatible with 'none' - because - tuple() is not compatible with 'none' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1252:29 │ @@ -2065,17 +1633,6 @@ Because in the expression's type: ] , [term()]} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {[[term()]], [term()]} is not compatible with {[plist('a', 'b')], plist('a', 'b')} - because - [[term()]] is not compatible with [plist('a', 'b')] - because - [term()] is not compatible with plist('a', 'b') - because - [term()] is not compatible with ['a' | {'a', 'b'}] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1315:21 │ @@ -2120,46 +1677,25 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/custom.erl:1363:5 │ 1363 │ proplists:get_value(a, [a]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('a', ['a']). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('a', ['a']). Expression has type: term() Context expected type: 'true' | 'undefined' - │ - - term() is not compatible with 'true' | 'undefined' - because - term() is not compatible with 'true' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1368:5 │ 1368 │ proplists:get_value(X, [a]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value(X, ['a']). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value(X, ['a']). Expression has type: term() Context expected type: 'true' | 'undefined' - │ - - term() is not compatible with 'true' | 'undefined' - because - term() is not compatible with 'true' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1373:5 │ 1373 │ proplists:get_value(a, [a], b). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_value('a', ['a'], 'b'). + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('a', ['a'], 'b'). Expression has type: term() Context expected type: 'true' | 'b' - │ - - term() is not compatible with 'true' | 'b' - because - term() is not compatible with 'true' error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1416:5 @@ -2194,12 +1730,6 @@ Because in the expression's type: Context expects type: tuple() ] ------------------------------- Detailed message ------------------------------ - - [number()] is not compatible with [tuple()] - because - number() is not compatible with tuple() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1448:5 │ @@ -2222,14 +1752,6 @@ Because in the expression's type: Differs from the expected type: number() ] ------------------------------- Detailed message ------------------------------ - - [string() | number()] is not compatible with [number()] - because - string() | number() is not compatible with number() - because - string() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1482:5 │ @@ -2373,17 +1895,6 @@ Because in the expression's type: , A} ] ------------------------------- Detailed message ------------------------------ - - [{A, B} | {B, A}] is not compatible with [{A, B}] - because - {A, B} | {B, A} is not compatible with {A, B} - because - at tuple index 1: - {B, A} is not compatible with {A, B} - because - B is not compatible with A - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1757:5 │ @@ -2404,10 +1915,6 @@ Because in the expression's type: Context expects type: #{...} The expected map has no corresponding key for: a. ------------------------------- Detailed message ------------------------------ - -key `a` is declared in the former but not in the latter and the latter map has no default association - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1769:18 │ @@ -2451,10 +1958,6 @@ Because in the expression's type: Context expects type: #{b := ..., ...} The type of the expression is missing the following required keys: b. ------------------------------- Detailed message ------------------------------ - -key `b` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1826:5 │ @@ -2472,10 +1975,6 @@ Because in the expression's type: Context expects type: #{b := ..., ...} The type of the expression is missing the following required keys: b. ------------------------------- Detailed message ------------------------------ - -key `b` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1827:12 │ @@ -2501,10 +2000,6 @@ Because in the expression's type: Context expects type: #{b := ..., ...} The type of the expression is missing the following required keys: b. ------------------------------- Detailed message ------------------------------ - -key `b` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1854:5 │ @@ -2525,16 +2020,9 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types ┌─ check/src/custom.erl:1916:23 │ 1916 │ custom_overloaded(X). - │ ^ - │ │ - │ X. + │ ^ X. Expression has type: term() Context expected type: atom() | binary() - │ - - term() is not compatible with atom() | binary() - because - term() is not compatible with atom() error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:1939:5 @@ -2554,15 +2042,6 @@ Because in the expression's type: Differs from the expected type: number() } ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {atom(), number() | pid()} is not compatible with {atom(), number()} - because - number() | pid() is not compatible with number() - because - pid() is not compatible with number() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2041:5 │ @@ -2578,14 +2057,6 @@ Because in the expression's type: Here the type is: string() | binary() Context expects type: string() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with file:filename() - because - string() | binary() is not compatible with file:filename() - because - string() | binary() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2046:5 │ @@ -2601,14 +2072,6 @@ Because in the expression's type: Here the type is: string() | binary() Context expects type: string() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with file:filename() - because - string() | binary() is not compatible with file:filename() - because - string() | binary() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2051:5 │ @@ -2624,14 +2087,6 @@ Because in the expression's type: Here the type is: string() | binary() Context expects type: string() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with file:filename() - because - string() | binary() is not compatible with file:filename() - because - string() | binary() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2066:5 │ @@ -2648,14 +2103,6 @@ Because in the expression's type: However the following candidate: string() Differs from the expected type: binary() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with binary() - because - string() | binary() is not compatible with binary() - because - string() is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2081:19 │ @@ -2674,16 +2121,6 @@ Because in the expression's type: Differs from the expected type: string() | atom() | file:deep_list() | binary() ] ------------------------------- Detailed message ------------------------------ - - [binary() | pid()] is not compatible with [file:name_all()] - because - binary() | pid() is not compatible with file:name_all() - because - binary() | pid() is not compatible with string() | atom() | file:deep_list() | binary() - because - pid() is not compatible with string() | atom() | file:deep_list() | binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2086:5 │ @@ -2699,14 +2136,6 @@ Because in the expression's type: Here the type is: string() | binary() Context expects type: string() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with file:filename() - because - string() | binary() is not compatible with file:filename() - because - string() | binary() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2091:5 │ @@ -2722,14 +2151,6 @@ Because in the expression's type: Here the type is: string() | binary() Context expects type: string() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with file:filename() - because - string() | binary() is not compatible with file:filename() - because - string() | binary() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2096:5 │ @@ -2745,14 +2166,6 @@ Because in the expression's type: Here the type is: string() | binary() Context expects type: string() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with file:filename() - because - string() | binary() is not compatible with file:filename() - because - string() | binary() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2111:5 │ @@ -2769,14 +2182,6 @@ Because in the expression's type: However the following candidate: string() Differs from the expected type: binary() ------------------------------- Detailed message ------------------------------ - - file:filename_all() is not compatible with binary() - because - string() | binary() is not compatible with binary() - because - string() is not compatible with binary() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2121:25 │ @@ -2793,14 +2198,6 @@ Because in the expression's type: Context expects type: string() | atom() | file:deep_list() | binary() No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - - pid() is not compatible with file:name_all() - because - pid() is not compatible with string() | atom() | file:deep_list() | binary() - because - pid() is not compatible with string() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2150:5 │ @@ -2822,17 +2219,6 @@ Because in the expression's type: ] , [atom() | number()]} ------------------------------- Detailed message ------------------------------ - - queue:queue(atom() | number()) is not compatible with queue:queue(number()) - because - {[atom() | number()], [atom() | number()]} is not compatible with queue:queue(number()) - because - at tuple index 1: - {[atom() | number()], [atom() | number()]} is not compatible with {[number()], [number()]} - because - [atom() | number()] is not compatible with [number()] - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2191:5 │ @@ -2851,15 +2237,6 @@ Because in the expression's type: Context expects type: #{count := ..., ...} The type of the expression is missing the following required keys: count. ------------------------------- Detailed message ------------------------------ - - #{count := number(), module := 'foo'} | #{module := 'foo'} is not compatible with state1() - because - #{count := number(), module := 'foo'} | #{module := 'foo'} is not compatible with #{count := number(), module := atom()} - because - #{module := 'foo'} is not compatible with #{count := number(), module := atom()} - key `count` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2223:13 │ @@ -2884,10 +2261,6 @@ Because in the expression's type: Context expects type: #{a := ..., ...} The type of the expression is missing the following required keys: a. ------------------------------- Detailed message ------------------------------ - -key `a` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2320:5 │ @@ -2908,10 +2281,6 @@ Because in the expression's type: Context expects type: #{a := ..., b := ..., ...} The type of the expression is missing the following required keys: a, b. ------------------------------- Detailed message ------------------------------ - -keys `a`, `b` are declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2342:5 │ @@ -2932,13 +2301,6 @@ Because in the expression's type: Context expects type: atom() , ... } ------------------------------- Detailed message ------------------------------ - - #{atom() => binary()} is not compatible with #{atom() => atom()} - the default associations are not compatible - because - binary() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2354:23 │ @@ -2955,12 +2317,6 @@ Because in the expression's type: Context expects type: 'false' | 'true' | {'true', term()} No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - - 'err' is not compatible with boolean() | {'true', term()} - because - 'err' is not compatible with 'false' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2362:5 │ @@ -2979,13 +2335,6 @@ Because in the expression's type: Context expects type: atom() , ... } ------------------------------- Detailed message ------------------------------ - - #{atom() => binary()} is not compatible with #{atom() => atom()} - the default associations are not compatible - because - binary() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2363:45 │ @@ -3026,12 +2375,6 @@ Because in the expression's type: Context expects type: iolist() | binary() No candidate matches in the expected union. ------------------------------- Detailed message ------------------------------ - - atom() is not compatible with iodata() | unicode:charlist() - because - atom() is not compatible with iolist() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2386:5 │ @@ -3060,17 +2403,6 @@ Because in the expression's type: } ] ------------------------------- Detailed message ------------------------------ - - [{'return', 'something'}] is not compatible with ['notempty_atstart' | 'noteol' | 'notbol' | 'global' | {'match_limit_recursion', number()} | re:compile_option() | {'match_limit', number()} | {'offset', number()} | 'notempty' | {'return', 'iodata' | 'list' | 'binary'} | 'anchored'] - because - {'return', 'something'} is not compatible with 'notempty_atstart' | 'noteol' | 'notbol' | 'global' | {'match_limit_recursion', number()} | re:compile_option() | {'match_limit', number()} | {'offset', number()} | 'notempty' | {'return', 'iodata' | 'list' | 'binary'} | 'anchored' - because - at tuple index 2: - {'return', 'something'} is not compatible with {'return', 'iodata' | 'list' | 'binary'} - because - 'something' is not compatible with 'iodata' | 'list' | 'binary' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2506:5 │ @@ -3090,15 +2422,6 @@ Because in the expression's type: ] , [atom()]} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {[number()], [atom()]} is not compatible with {[atom()], [number()]} - because - [number()] is not compatible with [atom()] - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2518:5 │ @@ -3120,18 +2443,6 @@ Because in the expression's type: ] , [{term(), atom()}]} ------------------------------- Detailed message ------------------------------ - - at tuple index 1: - {[{term(), number()}], [{term(), atom()}]} is not compatible with {[{term(), atom()}], [{term(), number()}]} - because - [{term(), number()}] is not compatible with [{term(), atom()}] - because - at tuple index 2: - {term(), number()} is not compatible with {term(), atom()} - because - number() is not compatible with atom() - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2536:5 │ @@ -3153,18 +2464,6 @@ Because in the expression's type: ] } ------------------------------- Detailed message ------------------------------ - - at tuple index 2: - {[{'ok', atom()}], [{'ok', atom()} | {'error', term()}]} is not compatible with {[{'ok', atom()}], [{'error', term()}]} - because - [{'ok', atom()} | {'error', term()}] is not compatible with [{'error', term()}] - because - {'ok', atom()} | {'error', term()} is not compatible with {'error', term()} - because - at tuple index 1: - {'ok', atom()} is not compatible with {'error', term()} - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2576:33 │ @@ -3181,10 +2480,6 @@ Because in the expression's type: Context expects type: #{a := ..., ...} The type of the expression is missing the following required keys: a. ------------------------------- Detailed message ------------------------------ - -key `a` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2586:33 │ @@ -3202,15 +2497,6 @@ Because in the expression's type: Context expects type: 'true' , ... } ------------------------------- Detailed message ------------------------------ - - #{a => number()} is not compatible with #{a => 'true'} - because - at key `a`: - #{a => number()} is not compatible with #{a => 'true'} - because - number() is not compatible with 'true' - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2596:33 │ @@ -3227,10 +2513,6 @@ Because in the expression's type: Context expects type: #{a := ..., ...} The type of the expression is missing the following required keys: a. ------------------------------- Detailed message ------------------------------ - -key `a` is declared as required in the latter but not in the former - error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) ┌─ check/src/custom.erl:2709:40 │ From 2d693f3578bb26a063be967f757aaa4f1bb2924f Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 10 Dec 2025 08:59:12 -0800 Subject: [PATCH 092/142] BE: restore expected test result to generate a panic Summary: As title. Note that this is still failing on GH. Reviewed By: robertoaloi Differential Revision: D88751685 fbshipit-source-id: 4050a9f2094ac7c46395f0f05a7f77dfad9a583b --- crates/elp/src/bin/main.rs | 1 + crates/elp/src/resources/test/hierarchical_config/basic.stdout | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index 2ecb900759..fc707eda47 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -2175,6 +2175,7 @@ mod tests { #[test_case(false ; "rebar")] #[test_case(true ; "buck")] + #[should_panic] // Support for hierarchical config is not implemented yet fn lint_hierarchical_config_basic(buck: bool) { simple_snapshot_sorted( args_vec!["lint", "--read-config"], diff --git a/crates/elp/src/resources/test/hierarchical_config/basic.stdout b/crates/elp/src/resources/test/hierarchical_config/basic.stdout index b0ccc8796b..de8cb63308 100644 --- a/crates/elp/src/resources/test/hierarchical_config/basic.stdout +++ b/crates/elp/src/resources/test/hierarchical_config/basic.stdout @@ -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 \ No newline at end of file +app_b/src/app_b.erl:6:1-6:5::[Warning] [L1230] function main/0 is unused From 0930edd69319ef39636401a84591f4632d25af51 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Thu, 11 Dec 2025 05:22:57 -0800 Subject: [PATCH 093/142] 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 --- crates/project_model/src/test_fixture.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crates/project_model/src/test_fixture.rs b/crates/project_model/src/test_fixture.rs index ac60d3b7c2..f121e20d88 100644 --- a/crates/project_model/src/test_fixture.rs +++ b/crates/project_model/src/test_fixture.rs @@ -277,6 +277,12 @@ impl FixtureWithProjectMeta { if let Some(entry) = res.last_mut() { entry.text.push_str(line); + } else if line.chars().any(|c| !c.is_whitespace()) { + panic!( + "Fixture has content before the first file marker (`//- /path/to/file.erl`). \ + Did you forget to add a file marker at the beginning?\n\ + The offending line: {line:?}" + ); } } } @@ -838,6 +844,19 @@ mod tests { use crate::test_fixture::extract_tags; use crate::test_fixture::remove_annotations; + #[test] + #[should_panic(expected = "Fixture has content before the first file marker")] + fn parse_fixture_panics_on_content_before_first_file_marker() { + FixtureWithProjectMeta::parse( + r#" + -module(main). + foo() -> ok. + //- /src/erl_eval.erl + -module(erl_eval). + "#, + ); + } + #[test] #[should_panic] fn parse_fixture_checks_further_indented_metadata() { From 290b931170a197007f294e981bcf77c18f6a3a5f Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Thu, 11 Dec 2025 06:21:28 -0800 Subject: [PATCH 094/142] 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 --- crates/base_db/src/fixture.rs | 53 +++++++++++++++++++++++++++++++++++ crates/ide_db/src/lib.rs | 13 +++++++++ 2 files changed, 66 insertions(+) diff --git a/crates/base_db/src/fixture.rs b/crates/base_db/src/fixture.rs index d6d2205ce9..0e770f539e 100644 --- a/crates/base_db/src/fixture.rs +++ b/crates/base_db/src/fixture.rs @@ -405,6 +405,59 @@ 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. + #[track_caller] + pub fn validate(&self, db: &DB) { + 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) { diff --git a/crates/ide_db/src/lib.rs b/crates/ide_db/src/lib.rs index a00ea1f0d9..9b64d09b31 100644 --- a/crates/ide_db/src/lib.rs +++ b/crates/ide_db/src/lib.rs @@ -448,4 +448,17 @@ mod tests { let position = fixture.position(); debug_assert_eq!(db.clamp_offset(position.file_id, 2000.into()), 15.into()) } + + #[test] + #[should_panic(expected = "Fixture validation failed: syntax errors found in test fixture")] + fn validate_fixture_with_parse_error() { + let fixture = r#" +//- /src/test.erl +-module(test). +foo( -> ok. +"#; + + let (db, fixture) = RootDatabase::with_fixture(fixture); + fixture.validate(&db); + } } From 4bfedaee9ad90fb146f0008b0abd10156cdd7f0b Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Thu, 11 Dec 2025 06:21:28 -0800 Subject: [PATCH 095/142] 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 --- crates/base_db/src/fixture.rs | 9 +++++ crates/elp/src/bin/glean.rs | 8 ++--- crates/hir/src/body/scope.rs | 2 +- crates/hir/src/body/tests.rs | 14 ++++++-- crates/hir/src/body/tree_print.rs | 6 ++-- crates/hir/src/fold.rs | 2 ++ crates/hir/src/form_list/tests.rs | 1 + crates/ide/src/diagnostics.rs | 8 ++++- crates/ide/src/diagnostics_collection.rs | 1 + crates/ide/src/expand_macro.rs | 6 ++-- crates/ide/src/inlay_hints/param_name.rs | 1 + crates/ide/src/runnables.rs | 2 +- crates/ide/src/signature_help.rs | 12 +++++++ crates/ide/src/tests.rs | 1 + crates/ide_completion/src/attributes.rs | 9 +++++ crates/ide_completion/src/ctx.rs | 35 +++++++++++++++++++ crates/ide_completion/src/export_functions.rs | 2 ++ crates/ide_completion/src/export_types.rs | 1 + crates/ide_completion/src/functions.rs | 23 ++++++++++++ crates/ide_completion/src/keywords.rs | 7 ++++ crates/ide_completion/src/macros.rs | 3 ++ crates/ide_completion/src/maps.rs | 13 +++++-- crates/ide_completion/src/modules.rs | 1 + crates/ide_completion/src/records.rs | 10 ++++++ crates/ide_completion/src/spec.rs | 2 ++ crates/ide_completion/src/types.rs | 5 +++ crates/ide_completion/src/vars.rs | 4 +++ crates/ide_ssr/src/tests.rs | 8 ++--- crates/project_model/src/test_fixture.rs | 9 +++++ 29 files changed, 185 insertions(+), 20 deletions(-) diff --git a/crates/base_db/src/fixture.rs b/crates/base_db/src/fixture.rs index 0e770f539e..cd1328d14d 100644 --- a/crates/base_db/src/fixture.rs +++ b/crates/base_db/src/fixture.rs @@ -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)>>, pub annotations: FxHashMap>, + 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, @@ -408,8 +412,13 @@ impl ChangeFixture { /// 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(&self, db: &DB) { + if self.expect_parse_errors { + return; + } + let mut errors_found = Vec::new(); for file_id in &self.files { diff --git a/crates/elp/src/bin/glean.rs b/crates/elp/src/bin/glean.rs index c31d45dc9e..2a45e450f2 100644 --- a/crates/elp/src/bin/glean.rs +++ b/crates/elp/src/bin/glean.rs @@ -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 diff --git a/crates/hir/src/body/scope.rs b/crates/hir/src/body/scope.rs index dd1da23e94..4ac4e366d8 100644 --- a/crates/hir/src/body/scope.rs +++ b/crates/hir/src/body/scope.rs @@ -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"], diff --git a/crates/hir/src/body/tests.rs b/crates/hir/src/body/tests.rs index c616df812d..eab603a76d 100644 --- a/crates/hir/src/body/tests.rs +++ b/crates/hir/src/body/tests.rs @@ -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]]) -> 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}). diff --git a/crates/hir/src/body/tree_print.rs b/crates/hir/src/body/tree_print.rs index 4aa6591d84..d6b956690f 100644 --- a/crates/hir/src/body/tree_print.rs +++ b/crates/hir/src/body/tree_print.rs @@ -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]). diff --git a/crates/hir/src/fold.rs b/crates/hir/src/fold.rs index 78cd442592..56ea3f56ae 100644 --- a/crates/hir/src/fold.rs +++ b/crates/hir/src/fold.rs @@ -2212,7 +2212,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}). "#; diff --git a/crates/hir/src/form_list/tests.rs b/crates/hir/src/form_list/tests.rs index 00a5ec4020..79f542fd0d 100644 --- a/crates/hir/src/form_list/tests.rs +++ b/crates/hir/src/form_list/tests.rs @@ -318,6 +318,7 @@ fn export() { fn import() { check( r#" +//- expect_parse_errors -import(, []). -import(foo, []). -import(foo, [foo/1]). diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index f9373e25b4..e3aa185b19 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -3189,6 +3189,7 @@ mod tests { fn syntax_error() { check_diagnostics( r#" +//- expect_parse_errors -module(main). foo() -> XX 3.0. %% ^^ error: P1711: Syntax Error @@ -3366,6 +3367,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 +3383,7 @@ main(X) -> expect![[r#" Some( Range( - 24..56, + 5..45, ), ) "#]] @@ -3540,6 +3542,7 @@ main(X) -> config, &extra_diags, r#" + //- expect_parse_errors -module(main). -export([foo/0,bar/0]). @@ -3577,6 +3580,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 +3599,7 @@ main(X) -> check_diagnostics( r#" //- erlang_service + //- expect_parse_errors //- native //- /src/a_mod.erl app:app_a -module(a_mod). @@ -3673,6 +3678,7 @@ main(X) -> fn test_nested_syntax_errors() { check_diagnostics( r#" + //- expect_parse_errors -module(main). run() -> ExitCode = diff --git a/crates/ide/src/diagnostics_collection.rs b/crates/ide/src/diagnostics_collection.rs index 4abe143dd9..fe1fc759aa 100644 --- a/crates/ide/src/diagnostics_collection.rs +++ b/crates/ide/src/diagnostics_collection.rs @@ -415,6 +415,7 @@ mod tests { config, &extra_diags, r#" + //- expect_parse_errors -module(main). -export([foo/0,bar/0]). diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs index 95735a904d..a5a6f759fc 100644 --- a/crates/ide/src/expand_macro.rs +++ b/crates/ide/src/expand_macro.rs @@ -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, []), diff --git a/crates/ide/src/inlay_hints/param_name.rs b/crates/ide/src/inlay_hints/param_name.rs index cf16198dfe..88a95e86d7 100644 --- a/crates/ide/src/inlay_hints/param_name.rs +++ b/crates/ide/src/inlay_hints/param_name.rs @@ -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. diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 4b2348f610..0aa731c8c9 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -243,7 +243,7 @@ mod tests { //- /my_app/src/runnables.erl ~ -module(runnables). - -export([all/]). + -export([all/0]). main() -> ok. "#, diff --git a/crates/ide/src/signature_help.rs b/crates/ide/src/signature_help.rs index fba6d12d73..d09ec201f8 100644 --- a/crates/ide/src/signature_help.rs +++ b/crates/ide/src/signature_help.rs @@ -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). diff --git a/crates/ide/src/tests.rs b/crates/ide/src/tests.rs index 86f0a57555..f1a9065e01 100644 --- a/crates/ide/src/tests.rs +++ b/crates/ide/src/tests.rs @@ -695,6 +695,7 @@ mod test { fn filtered_diagnostics_passes_syntax_errors() { check_filtered_diagnostics( r#" + //- expect_parse_errors %%<^^^^^^^^^^^^ 💡 error: L1201: no module definition foo() -> bug bug. diff --git a/crates/ide_completion/src/attributes.rs b/crates/ide_completion/src/attributes.rs index fc97832d30..f381df147d 100644 --- a/crates/ide_completion/src/attributes.rs +++ b/crates/ide_completion/src/attributes.rs @@ -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~ "#, diff --git a/crates/ide_completion/src/ctx.rs b/crates/ide_completion/src/ctx.rs index 22e385e3fa..0d9381e7ec 100644 --- a/crates/ide_completion/src/ctx.rs +++ b/crates/ide_completion/src/ctx.rs @@ -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, ~])}). "#), diff --git a/crates/ide_completion/src/export_functions.rs b/crates/ide_completion/src/export_functions.rs index f362ea830a..ef803eb9ab 100644 --- a/crates/ide_completion/src/export_functions.rs +++ b/crates/ide_completion/src/export_functions.rs @@ -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, diff --git a/crates/ide_completion/src/export_types.rs b/crates/ide_completion/src/export_types.rs index 9e80828b66..c21a37339e 100644 --- a/crates/ide_completion/src/export_types.rs +++ b/crates/ide_completion/src/export_types.rs @@ -79,6 +79,7 @@ mod test { check( r#" + //- expect_parse_errors -module(sample). -export_type([ foo~ diff --git a/crates/ide_completion/src/functions.rs b/crates/ide_completion/src/functions.rs index 4ce641d0e9..eab033765f 100644 --- a/crates/ide_completion/src/functions.rs +++ b/crates/ide_completion/src/functions.rs @@ -243,6 +243,7 @@ mod test { check( r#" + //- expect_parse_errors //- /src/sample1.erl -module(sample1). local() -> @@ -265,6 +266,7 @@ mod test { check( r#" + //- expect_parse_errors //- /src/sample1.erl -module(sample1). local() -> @@ -338,6 +340,7 @@ mod test { check( r#" + //- expect_parse_errors //- /src/sample1.erl -module(sample1). local() -> @@ -360,6 +363,7 @@ mod test { check( r#" + //- expect_parse_errors //- /src/sample1.erl -module(sample1). local() -> @@ -379,12 +383,14 @@ mod test { {label:foon/2, kind:Function, contents:Snippet("foon(${1:A}, ${2:B})"), position:Some(FilePosition { file_id: FileId(1), offset: 86 })}"#]], ); } + #[test] fn test_remote_calls_deprecated() { assert!(serde_json::to_string(&lsp_types::CompletionItemKind::MODULE).unwrap() == "9"); check( r#" + //- expect_parse_errors //- /src/sample1.erl -module(sample1). local() -> @@ -413,6 +419,7 @@ mod test { check( r#" + //- expect_parse_errors //- /src/sample1.erl -module(sample1). local() -> @@ -441,6 +448,7 @@ mod test { check( r#" + //- expect_parse_errors //- /src/sample1.erl -module(sample1). local() -> @@ -466,6 +474,7 @@ mod test { check( r#" + //- expect_parse_errors //- /src/sample1.erl -module(sample1). local() -> @@ -584,6 +593,7 @@ mod test { fn test_remote_fun_exprs_with_trigger() { check( r#" + //- expect_parse_errors //- /src/sample1.erl -module(sample1). main(_) -> @@ -607,6 +617,7 @@ mod test { fn test_local_fun_exprs_with_trigger() { check( r#" + //- expect_parse_errors //- /src/sample1.erl -module(sample1). foo() -> ok. @@ -622,6 +633,7 @@ mod test { fn test_local_fun_exprs_no_trigger() { check( r#" + //- expect_parse_errors //- /src/sample1.erl -module(sample1). foo() -> ok. @@ -637,6 +649,7 @@ mod test { fn function_error_recovery() { check( r#" + //- expect_parse_errors -module(sample1). foo() -> b~ @@ -650,6 +663,7 @@ mod test { ); check( r#" + //- expect_parse_errors //- /src/sample1.erl -module(sample1). local() -> @@ -668,6 +682,7 @@ mod test { fn test_local_and_remote() { check( r#" + //- expect_parse_errors //- /src/sample1.erl -module(sample1). samba() -> ok. @@ -688,6 +703,7 @@ mod test { fn test_remote_call_broken_case() { check( r#" + //- expect_parse_errors //- /src/sample1.erl -module(sample1). test() -> @@ -709,6 +725,7 @@ mod test { fn test_local_call_broken_case() { check( r#" + //- expect_parse_errors //- /src/sample1.erl -module(sample1). foo() -> ok. @@ -802,6 +819,7 @@ mod test { check( r#" + //- expect_parse_errors //- /src/sample1.erl -module(sample1). local() -> @@ -820,6 +838,7 @@ mod test { ); check( r#" +//- expect_parse_errors //- /src/sample1.erl -module(sample1). local() -> @@ -839,6 +858,7 @@ foo(X, Y) -> ok. ); check( r#" +//- expect_parse_errors //- /src/sample1.erl -module(sample1). local() -> @@ -902,6 +922,7 @@ foo(X, Y) -> ok. fn test_remote_call_same_module_macro_prefix() { check( r#" + //- expect_parse_errors -module(main). -export([main/0, do_not_use_me/0]). -deprecated({do_not_use_me, 0, "Because I said so"}). @@ -922,6 +943,7 @@ foo(X, Y) -> ok. fn test_in_dialyzer_attribute() { check( r#" + //- expect_parse_errors -module(main). -export([ foo/1, @@ -942,6 +964,7 @@ foo(X, Y) -> ok. fn test_quoted_local_call() { check( r#" + //- expect_parse_errors -module(sample). test() -> fo~(something). diff --git a/crates/ide_completion/src/keywords.rs b/crates/ide_completion/src/keywords.rs index 734afc80a5..dc60ae8158 100644 --- a/crates/ide_completion/src/keywords.rs +++ b/crates/ide_completion/src/keywords.rs @@ -141,6 +141,7 @@ mod test { fn test_no_keywords() { check( r#" + //- expect_parse_errors -module(sample). test(X) -> a~ @@ -150,6 +151,7 @@ mod test { ); check( r#" + //- expect_parse_errors -module(sample). test(~X) -> X. @@ -160,6 +162,7 @@ mod test { check( r#" + //- expect_parse_errors -module(m~). "#, None, @@ -168,6 +171,7 @@ mod test { check( r#" + //- expect_parse_errors -module(m). -~ "#, @@ -177,6 +181,7 @@ mod test { check( r#" + //- expect_parse_errors -module(m). -type foo() :: ~. "#, @@ -191,6 +196,7 @@ mod test { fn test_keywords_error_recovery() { check( r#" + //- expect_parse_errors -module(sample). test(X) -> X ~ @@ -234,6 +240,7 @@ mod test { check( r#" + //- expect_parse_errors -module(sample). test(X) -> ~ diff --git a/crates/ide_completion/src/macros.rs b/crates/ide_completion/src/macros.rs index 3c4b77cfad..e86cf3ba93 100644 --- a/crates/ide_completion/src/macros.rs +++ b/crates/ide_completion/src/macros.rs @@ -253,6 +253,7 @@ mod test { check( r#" + //- expect_parse_errors -module(sample1). -define(FOO, 1). -define(FOO(), 1). @@ -271,6 +272,7 @@ mod test { check( r#" + //- expect_parse_errors -module(sample1). -define(FOO, 1). -define(BAR, 1). @@ -282,6 +284,7 @@ mod test { check( r#" + //- expect_parse_errors -module(sample1). -define(FOO, ok). -define(BAR, 1). diff --git a/crates/ide_completion/src/maps.rs b/crates/ide_completion/src/maps.rs index 4371793356..e232a7d8bc 100644 --- a/crates/ide_completion/src/maps.rs +++ b/crates/ide_completion/src/maps.rs @@ -214,6 +214,7 @@ mod test { fn test_local_type() { check( r#" + //- expect_parse_errors -module(test_local_type). -type my_map() :: #{field1 := integer(), field2 => boolean()}. foo(X) -> #~ @@ -229,6 +230,7 @@ mod test { fn test_included_type() { check( r#" + //- expect_parse_errors //- /include/test_included_type.hrl include_path:/include -type my_included_map() :: #{field1 := integer(), field2 => integer()}. //- /src/test_included_type.erl @@ -250,6 +252,7 @@ mod test { fn test_empty_map_type() { check( r#" + //- expect_parse_errors -module(test_empty_map_type). -type my_map() :: #{}. foo(X) -> #~ @@ -265,6 +268,7 @@ mod test { fn test_arity_1_map_type() { check( r#" + //- expect_parse_errors -module(test_arity_1_map_type). -type my_map() :: #{field1 := true }. foo(X) -> #~ @@ -280,6 +284,7 @@ mod test { fn test_mixed_map_and_record_types() { check( r#" + //- expect_parse_errors -module(test_mixed_map_and_record_types). -type my_map() :: #{field1 := true }. -record(my_record, {field1}). @@ -297,6 +302,7 @@ mod test { fn test_nested_map_type() { check( r#" + //- expect_parse_errors -module(test_nested_map_type). -type my_map() :: #{field1 := true, field2 => false}. -type my_nested_map() :: #{field1 := #{field1 => my_map()}}. @@ -315,11 +321,12 @@ mod test { fn test_type_from_spec() { check( r#" + //- expect_parse_errors //- /src/one.erl -module(one). -type my_map(X) :: #{field1 := X}. -export_type([my_map/0]). - + //- /src/two.erl -module(two). -spec foo(one:my_map(integer())) -> one:my_map(integer()). @@ -338,11 +345,12 @@ mod test { fn test_type_from_unowned_spec() { check( r#" + //- expect_parse_errors //- /src/one.erl -module(one). -type my_map(X) :: #{field1 := X}. -export_type([my_map/0]). - + //- /src/two.erl -module(two). -spec foo(one:my_map(integer())) -> one:my_map(integer()). @@ -359,6 +367,7 @@ mod test { fn test_local_type_in_function_argument() { check( r#" + //- expect_parse_errors -module(test_local_type). -type my_map() :: #{field1 := integer(), field2 => boolean()}. foo(#~ diff --git a/crates/ide_completion/src/modules.rs b/crates/ide_completion/src/modules.rs index 820d04cfe8..3823339f78 100644 --- a/crates/ide_completion/src/modules.rs +++ b/crates/ide_completion/src/modules.rs @@ -132,6 +132,7 @@ foo() -> check( r#" + //- expect_parse_errors //- /src/sample1.erl -module(sample1). -export([ diff --git a/crates/ide_completion/src/records.rs b/crates/ide_completion/src/records.rs index cf8e0abc7c..d66e054423 100644 --- a/crates/ide_completion/src/records.rs +++ b/crates/ide_completion/src/records.rs @@ -320,6 +320,7 @@ mod test { fn test_record_name() { check( r#" + //- expect_parse_errors -module(sample). -record(this_record, {field1=1, field2=2}). -record(that_record, {}). @@ -334,6 +335,7 @@ mod test { check( r#" + //- expect_parse_errors -module(sample). -record(this_record, {field1=1, field2=2}). -record(that_record, {}). @@ -353,6 +355,7 @@ mod test { // Irregular names are quoted. check( r#" + //- expect_parse_errors -module(sample). -record('this.record', {field1=1, field2=2}). -record('that$record', {}). @@ -369,6 +372,7 @@ mod test { fn test_record_error_recovery() { check( r#" + //- expect_parse_errors -module(sample). -record(rec, {field1=1, field2=2}). foo(X) -> #rec{field1 = 1, field2~. @@ -381,6 +385,7 @@ mod test { check( r#" + //- expect_parse_errors -module(sample). -record(rec, {field1=1, field2=2}). foo(X) -> X#rec{field1 = 1, field2~. @@ -393,6 +398,7 @@ mod test { check( r#" + //- expect_parse_errors -module(sample). -record(rec, {field1=1, field2=2}). foo(X) -> case ok of @@ -404,6 +410,7 @@ mod test { check( r#" + //- expect_parse_errors -module(sample). -record(rec, {field1=1, field2=2}). foo(X) -> case ok of @@ -417,6 +424,7 @@ mod test { check( r#" + //- expect_parse_errors -module(sample). -record(rec, {field1=1, field2=2}). foo(X) -> case ok of @@ -433,6 +441,7 @@ mod test { fn test_record_name_in_function_signature() { check( r#" + //- expect_parse_errors -module(sample). -record(this_record, {field1=1, field2=2}). -record(that_record, {}). @@ -450,6 +459,7 @@ mod test { fn test_record_field_in_function_signature() { check( r#" + //- expect_parse_errors -module(sample). -record(rec, {field1, field2, other}). foo(#rec{fie~ diff --git a/crates/ide_completion/src/spec.rs b/crates/ide_completion/src/spec.rs index 6a0061e914..cc853af865 100644 --- a/crates/ide_completion/src/spec.rs +++ b/crates/ide_completion/src/spec.rs @@ -109,6 +109,7 @@ mod test { check( r#" + //- expect_parse_errors -module(sample). frog() -> ok. @@ -129,6 +130,7 @@ mod test { check( r#" + //- expect_parse_errors -module(sample). frog(A, B) -> {A, B}. diff --git a/crates/ide_completion/src/types.rs b/crates/ide_completion/src/types.rs index 3800a97b5c..e232ab2944 100644 --- a/crates/ide_completion/src/types.rs +++ b/crates/ide_completion/src/types.rs @@ -169,6 +169,7 @@ mod test { check( r#" + //- expect_parse_errors //- /src/sample.erl -module(sample). -type alias() :: ok. @@ -197,6 +198,7 @@ mod test { check( r#" + //- expect_parse_errors //- /src/sample.erl -module(sample). -type alias() :: ok. @@ -225,6 +227,7 @@ mod test { check( r#" + //- expect_parse_errors //- /src/sample.erl -module(sample). -spec foo() -> sample2:~. @@ -248,6 +251,7 @@ mod test { // something reasonable (show nothing) check( r#" + //- expect_parse_errors //- /src/sample.erl -module(sample). -spec foo() -> sample~:. @@ -263,6 +267,7 @@ mod test { check( r#" + //- expect_parse_errors //- /src/sample.erl -module(sample). -spec foo() -> sample~:. diff --git a/crates/ide_completion/src/vars.rs b/crates/ide_completion/src/vars.rs index 65740d56a4..5544475ed3 100644 --- a/crates/ide_completion/src/vars.rs +++ b/crates/ide_completion/src/vars.rs @@ -100,6 +100,7 @@ mod test { fn test_local_variables_1() { check( r#" + //- expect_parse_errors //- /src/sample1.erl -module(sample1). test(AnArg1,Blah) -> @@ -117,6 +118,7 @@ mod test { fn test_local_variables_limit_to_current_function() { check( r#" + //- expect_parse_errors //- /src/sample1.erl -module(sample1). another(AnArgNotMatched) -> @@ -142,6 +144,7 @@ mod test { fn test_local_variables_none_if_space() { check( r#" + //- expect_parse_errors //- /src/sample1.erl -module(sample1). test(AnArg1,Blah) -> @@ -157,6 +160,7 @@ mod test { fn test_local_variables_no_duplicates() { check( r#" + //- expect_parse_errors //- /src/sample1.erl -module(sample1). handle_update(Config, Contents) -> diff --git a/crates/ide_ssr/src/tests.rs b/crates/ide_ssr/src/tests.rs index 108680853e..881a5ac123 100644 --- a/crates/ide_ssr/src/tests.rs +++ b/crates/ide_ssr/src/tests.rs @@ -1288,7 +1288,7 @@ fn ssr_do_not_match_pattern_missing() { r#" g() -> <<$",$\\,194,181,$A,$">> = - ~b""" + """ "\\µA" """. "#, @@ -1327,7 +1327,7 @@ fn ssr_comments_in_match() { <<{ % preceding comment 3}, { 3 % following comment - >>. + }>>. "#; let strategy = Strategy { macros: MacroStrategy::Expand, @@ -1536,7 +1536,7 @@ fn ssr_predicates_on_match_expr_pat() { %-compile({"str", var, atom}). %-type foo(V) :: {"str", V, atom}. foo({"hello", Var, atom}) -> {"str", Var, aa}. - + "#; let strategy = Strategy { macros: MacroStrategy::Expand, @@ -1636,7 +1636,7 @@ fn ssr_predicates_on_match_type() { let pattern = "ssr: {_@V, _@A}."; let code = r#" -type foo(V) :: {V, atom}. - + "#; let strategy = Strategy { macros: MacroStrategy::Expand, diff --git a/crates/project_model/src/test_fixture.rs b/crates/project_model/src/test_fixture.rs index f121e20d88..140baec0ee 100644 --- a/crates/project_model/src/test_fixture.rs +++ b/crates/project_model/src/test_fixture.rs @@ -187,6 +187,7 @@ impl DiagnosticsEnabled { pub struct FixtureWithProjectMeta { pub fixture: Vec, pub diagnostics_enabled: DiagnosticsEnabled, + pub expect_parse_errors: bool, } impl FixtureWithProjectMeta { @@ -204,6 +205,7 @@ impl FixtureWithProjectMeta { let mut fixture = fixture.as_str(); let mut res: Vec = Vec::new(); let mut diagnostics_enabled = DiagnosticsEnabled::default(); + let mut expect_parse_errors = false; // --------------------------------------- // Each of the following is optional, but they must always @@ -232,6 +234,12 @@ impl FixtureWithProjectMeta { fixture = remain; } + if let Some(meta) = fixture.strip_prefix("//- expect_parse_errors") { + let (_meta, remain) = meta.split_once('\n').unwrap(); + expect_parse_errors = true; + fixture = remain; + } + if let Some(meta) = fixture.strip_prefix("//- native") { let (_meta, remain) = meta.split_once('\n').unwrap(); diagnostics_enabled.use_native = true; @@ -304,6 +312,7 @@ impl FixtureWithProjectMeta { FixtureWithProjectMeta { fixture: res, diagnostics_enabled, + expect_parse_errors, } } From 7b0b29ae21218ca444e8b57fdbec209312a43e40 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Thu, 11 Dec 2025 06:36:45 -0800 Subject: [PATCH 096/142] 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 --- crates/elp/src/bin/main.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index fc707eda47..48fc5cc621 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -2175,7 +2175,11 @@ mod tests { #[test_case(false ; "rebar")] #[test_case(true ; "buck")] - #[should_panic] // Support for hierarchical config is not implemented yet + // 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"], From babf61214c206f063911391aa6a4d1667106def4 Mon Sep 17 00:00:00 2001 From: Victor Lanvin Date: Thu, 11 Dec 2025 07:25:42 -0800 Subject: [PATCH 097/142] Bad occurrence typing with maps:foreach -- repro 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 --- .../eqwater/eqwater_maps.pretty | 18 +++++++++++++++++- .../eqwater/src/eqwater_maps.erl | 10 ++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater_maps.pretty b/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater_maps.pretty index 037e6b14a6..b5f637ead3 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater_maps.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/eqwater/eqwater_maps.pretty @@ -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 diff --git a/test_projects/eqwalizer_tests/eqwater/src/eqwater_maps.erl b/test_projects/eqwalizer_tests/eqwater/src/eqwater_maps.erl index 78ae852b8a..68d938f790 100644 --- a/test_projects/eqwalizer_tests/eqwater/src/eqwater_maps.erl +++ b/test_projects/eqwalizer_tests/eqwater/src/eqwater_maps.erl @@ -59,3 +59,13 @@ map_occ_08_neg(_) -> err. map_occ_09(#{a := undefined}) -> 1; map_occ_09(#{a := Map}) -> Map#{2 => 2}; map_occ_09(_) -> 3. + +-spec is_ok(ok) -> ok. +is_ok(ok) -> ok. + +-spec map_occ_foreach_neg(#{term() => #{a => ok | err}}) -> ok. +map_occ_foreach_neg(M) -> + maps:foreach(fun + (_, #{a := err}) -> ok; + (_, #{a := V}) -> is_ok(V) + end, M). From 0085fba772989aff5049d846d3988667cdd5fbc1 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Fri, 12 Dec 2025 01:38:41 -0800 Subject: [PATCH 098/142] Add `--severity` argument to `elp parse-elp` to limit output Summary: As title. Reviewed By: TD5 Differential Revision: D88963207 fbshipit-source-id: a8cc5ddfedcfa8063537e586ac086c8a0caac465 --- crates/elp/src/bin/args.rs | 30 ++++++++++++++++ crates/elp/src/bin/elp_parse_cli.rs | 36 +++++++++++++++++++ crates/elp/src/bin/main.rs | 18 ++++++++++ .../parse_all_diagnostics_error.stdout | 5 +++ .../src/resources/test/parse_elp_help.stdout | 3 +- 5 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 crates/elp/src/resources/test/diagnostics/parse_all_diagnostics_error.stdout diff --git a/crates/elp/src/bin/args.rs b/crates/elp/src/bin/args.rs index 0f057ce1e6..db3bc33df2 100644 --- a/crates/elp/src/bin/args.rs +++ b/crates/elp/src/bin/args.rs @@ -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, } #[derive(Clone, Debug, Bpaf)] @@ -783,6 +794,25 @@ fn format_guard(format: &Option) -> bool { } } +fn severity_completer(_: &Option) -> Vec<(String, Option)> { + vec![ + ("error".to_string(), None), + ("warning".to_string(), None), + ("weak_warning".to_string(), None), + ("information".to_string(), None), + ] +} + +fn severity_guard(severity: &Option) -> bool { + match severity { + None => true, + Some(s) if s == "error" || s == "warning" || s == "weak_warning" || s == "information" => { + true + } + _ => false, + } +} + fn macros_completer(_: &Option) -> Vec<(String, Option)> { vec![ ("expand".to_string(), None), diff --git a/crates/elp/src/bin/elp_parse_cli.rs b/crates/elp/src/bin/elp_parse_cli.rs index 16d4675d7d..3e57d69b17 100644 --- a/crates/elp/src/bin/elp_parse_cli.rs +++ b/crates/elp/src/bin/elp_parse_cli.rs @@ -57,6 +57,35 @@ use crate::args::ParseAllElp; use crate::reporting; use crate::reporting::print_memory_usage; +fn parse_severity(severity: &str) -> Option { + 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, +) -> bool { + match min_severity { + None => true, + Some(min) => severity_rank(diag_severity) <= severity_rank(min), + } +} + #[derive(Debug)] struct ParseResult { name: String, @@ -162,6 +191,10 @@ pub fn parse_all( } res.sort_by(|a, b| a.name.cmp(&b.name)); let mut err_in_diag = false; + let min_severity = args + .severity + .as_ref() + .and_then(|s| parse_severity(s.as_str())); for diags in res { let mut combined: Vec = diags.diagnostics.diagnostics_for(diags.file_id); @@ -172,6 +205,9 @@ pub fn parse_all( let line_index = db.file_line_index(diags.file_id); combined.sort_by(|a, b| a.range.start().cmp(&b.range.start())); for diag in combined { + if !meets_severity_threshold(diag.severity, min_severity) { + continue; + } if args.is_format_json() { err_in_diag = true; let vfs_path = loaded.vfs.file_path(diags.file_id); diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index 48fc5cc621..e1d26e3e7e 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -671,6 +671,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) { diff --git a/crates/elp/src/resources/test/diagnostics/parse_all_diagnostics_error.stdout b/crates/elp/src/resources/test/diagnostics/parse_all_diagnostics_error.stdout new file mode 100644 index 0000000000..ca3ca245ab --- /dev/null +++ b/crates/elp/src/resources/test/diagnostics/parse_all_diagnostics_error.stdout @@ -0,0 +1,5 @@ +module specified: diagnostics +Diagnostics reported in 1 modules: + diagnostics: 6 + 3:0-3:35::[Error] [L0000] Issue in included file + 5:30-5:44::[Error] [L1295] type undefined_type/0 undefined diff --git a/crates/elp/src/resources/test/parse_elp_help.stdout b/crates/elp/src/resources/test/parse_elp_help.stdout index 1457aed5c5..75cc38354e 100644 --- a/crates/elp/src/resources/test/parse_elp_help.stdout +++ b/crates/elp/src/resources/test/parse_elp_help.stdout @@ -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 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 Show diagnostics in JSON format --report-system-stats Report system memory usage and other statistics + --severity Minimum severity level to report. Valid values: error, warning, weak_warning, information -h, --help Prints help information From a16af7e9e29c234045961e3139129a8c03af9282 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Fri, 12 Dec 2025 02:19:15 -0800 Subject: [PATCH 099/142] 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 --- website/docs/get-started/editors/emacs.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/website/docs/get-started/editors/emacs.md b/website/docs/get-started/editors/emacs.md index 072e6902da..fab357f4a4 100644 --- a/website/docs/get-started/editors/emacs.md +++ b/website/docs/get-started/editors/emacs.md @@ -37,9 +37,8 @@ additional configuration options. ### Semantic tokens -Semantic token support has been added to eglot, but is not yet in the -released version. But it is possible to install the updated version -of eglot. +Semantic token support has been added to eglot, but is not yet in the released +version. But it is possible to install the updated version of eglot. To do so, add @@ -52,8 +51,14 @@ to your `init.el`, then run `M-x eglot-upgrade-eglot` Once upgraded, add the following to the `(use-package` entry for `eglot` ```elisp - (setq eglot-semantic-token-modifiers ' - ("bound" "exported_function" "exported_type" "deprecated_function" "type_dynamic")) +(setq-default eglot-workspace-configuration + ;; Run `elp config` to see that options can be used here + ;; Use `eglot-show-workspace-configuration` to see what is sent + '(:elp (:highlightDynamic (:enable t) + :typesOnHover (:enable t) )) + + eglot-semantic-token-modifiers + '("bound" "exported_function" "exported_type" "deprecated_function" "type_dynamic")) ;; Each face name arises as a template from the modifiers as ;; "eglot-semantic-%s-face" @@ -92,7 +97,6 @@ Once upgraded, add the following to the `(use-package` entry for `eglot` (eglot--widening (font-lock-flush))))) ``` - ## lsp-mode Install the `lsp-mode` package, which is a generic Emacs client for LSP servers. From 553c90d63143c82057d4888b40109ec9dca2f329 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Fri, 12 Dec 2025 03:02:33 -0800 Subject: [PATCH 100/142] Bump OSS VS Code extension to 0.47.0 Summary: Due to some [most likely transient infra issues](https://github.com/WhatsApp/erlang-language-platform/actions/runs/20137940955), 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 --- editors/code/package-lock.json | 4 ++-- editors/code/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json index 1adcc191a7..6309d4c81e 100644 --- a/editors/code/package-lock.json +++ b/editors/code/package-lock.json @@ -1,12 +1,12 @@ { "name": "erlang-language-platform", - "version": "0.46.0", + "version": "0.47.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "erlang-language-platform", - "version": "0.46.0", + "version": "0.47.0", "hasInstallScript": true, "license": "Apache2", "devDependencies": { diff --git a/editors/code/package.json b/editors/code/package.json index ebf55e7c86..eb7ced3148 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -4,7 +4,7 @@ "description": "Erlang Language Support for VS Code, by WhatsApp.", "author": "Meta Platforms, Inc", "license": "Apache2", - "version": "0.46.0", + "version": "0.47.0", "icon": "images/elp-logo-color.png", "homepage": "https://whatsapp.github.io/erlang-language-platform/", "repository": { From 887d287c9491c7058e48df140e959a0f02b10ec9 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Fri, 12 Dec 2025 03:56:28 -0800 Subject: [PATCH 101/142] 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 --- crates/elp/src/bin/elp_parse_cli.rs | 21 ++++++++++++------- .../parse_all_diagnostics_error.stdout | 2 +- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/crates/elp/src/bin/elp_parse_cli.rs b/crates/elp/src/bin/elp_parse_cli.rs index 3e57d69b17..770ab60456 100644 --- a/crates/elp/src/bin/elp_parse_cli.rs +++ b/crates/elp/src/bin/elp_parse_cli.rs @@ -176,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")?; @@ -191,13 +204,10 @@ pub fn parse_all( } res.sort_by(|a, b| a.name.cmp(&b.name)); let mut err_in_diag = false; - let min_severity = args - .severity - .as_ref() - .and_then(|s| parse_severity(s.as_str())); for diags in res { let mut combined: Vec = 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())?; } @@ -205,9 +215,6 @@ pub fn parse_all( let line_index = db.file_line_index(diags.file_id); combined.sort_by(|a, b| a.range.start().cmp(&b.range.start())); for diag in combined { - if !meets_severity_threshold(diag.severity, min_severity) { - continue; - } if args.is_format_json() { err_in_diag = true; let vfs_path = loaded.vfs.file_path(diags.file_id); diff --git a/crates/elp/src/resources/test/diagnostics/parse_all_diagnostics_error.stdout b/crates/elp/src/resources/test/diagnostics/parse_all_diagnostics_error.stdout index ca3ca245ab..3af8bfb086 100644 --- a/crates/elp/src/resources/test/diagnostics/parse_all_diagnostics_error.stdout +++ b/crates/elp/src/resources/test/diagnostics/parse_all_diagnostics_error.stdout @@ -1,5 +1,5 @@ module specified: diagnostics Diagnostics reported in 1 modules: - diagnostics: 6 + diagnostics: 2 3:0-3:35::[Error] [L0000] Issue in included file 5:30-5:44::[Error] [L1295] type undefined_type/0 undefined From eb5e0abbf8a19f764fc93f1293872da87b74ee7f Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Fri, 12 Dec 2025 04:22:36 -0800 Subject: [PATCH 102/142] 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 --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf3834b5ad..2fcdf48405 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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: From 2bd3d57d021262cc9e00552c2004be5de05e42e0 Mon Sep 17 00:00:00 2001 From: Victor Lanvin Date: Fri, 12 Dec 2025 06:08:41 -0800 Subject: [PATCH 103/142] 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 --- .../check/callbacks3_neg-OTP-26.pretty | 27 ++++++++++++++----- .../check/callbacks3_neg-OTP-27.pretty | 27 ++++++++++++++----- .../check/callbacks3_neg-OTP-28.pretty | 26 +++++++++++++----- .../check/src/callbacks3_neg.erl | 7 ++++- 4 files changed, 65 insertions(+), 22 deletions(-) diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-26.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-26.pretty index 63be36e638..6cb2012096 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-26.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-26.pretty @@ -1,14 +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:12:1 + ┌─ check/src/callbacks3_neg.erl:13:1 │ -12 │ -behavior(gen_server). +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_cast/2. Expected: {'noreply', term()} | {'noreply', term(), timeout() | 'hibernate' | {'continue', term()}} | {'stop', term(), term()}, Got: 'wrong_ret'. + │ 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'} │ - '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()} +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. + } -1 ERROR +2 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-27.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-27.pretty index 63be36e638..6cb2012096 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-27.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-27.pretty @@ -1,14 +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:12:1 + ┌─ check/src/callbacks3_neg.erl:13:1 │ -12 │ -behavior(gen_server). +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_cast/2. Expected: {'noreply', term()} | {'noreply', term(), timeout() | 'hibernate' | {'continue', term()}} | {'stop', term(), term()}, Got: 'wrong_ret'. + │ 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'} │ - '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()} +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. + } -1 ERROR +2 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-28.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-28.pretty index a24c7a6a48..849039f298 100644 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-28.pretty +++ b/crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-28.pretty @@ -1,14 +1,26 @@ 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 + ┌─ check/src/callbacks3_neg.erl:13:1 │ -12 │ -behavior(gen_server). +13 │ -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' + +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(), gen_server:action()} | {'stop', term(), term()}, Got: 'wrong_ret'. + │ Incorrect return type for implementation of gen_server:handle_info/2. +Expected: {'noreply', term()} | {'noreply', term(), gen_server:action()} | {'stop', term(), term()} +Got: {'noreply', 'ok', 'wrong_atom'} │ - '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()} +Because in the expression's type: + { 'noreply', 'ok', + Here the type is: 'wrong_atom' + Context expects type: gen_server:action() + } -1 ERROR +2 ERRORS diff --git a/test_projects/eqwalizer_tests/check/src/callbacks3_neg.erl b/test_projects/eqwalizer_tests/check/src/callbacks3_neg.erl index 8152487669..f904f0e718 100644 --- a/test_projects/eqwalizer_tests/check/src/callbacks3_neg.erl +++ b/test_projects/eqwalizer_tests/check/src/callbacks3_neg.erl @@ -7,7 +7,8 @@ -export([ init/1, handle_call/3, - handle_cast/2 + handle_cast/2, + handle_info/2 ]). -behavior(gen_server). @@ -19,3 +20,7 @@ handle_call(_, _From, State) -> -spec handle_cast(ok, ok) -> wrong_ret. handle_cast(_, _) -> wrong_ret. + +-spec handle_info(ok, ok) -> {noreply, ok, wrong_atom}. +handle_info(_, _) -> + {noreply, ok, wrong_atom}. From b525eac0eaca72daeee43ba3ed32a432a8a83847 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Fri, 12 Dec 2025 06:17:31 -0800 Subject: [PATCH 104/142] Pass range to fixes callback 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 --- crates/ide/src/diagnostics.rs | 3 ++- .../ide/src/diagnostics/boolean_precedence.rs | 15 +++++++-------- crates/ide/src/diagnostics/edoc.rs | 8 ++------ .../src/diagnostics/macro_precedence_suprise.rs | 14 ++++++-------- .../missing_compile_warn_missing_spec.rs | 6 ++---- .../ide/src/diagnostics/misspelled_attribute.rs | 7 +++---- .../ide/src/diagnostics/undocumented_module.rs | 17 ++++++++++------- crates/ide/src/diagnostics/unused_include.rs | 1 + crates/ide/src/diagnostics/unused_macro.rs | 8 +++++++- 9 files changed, 40 insertions(+), 39 deletions(-) diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index e3aa185b19..9852303132 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -865,6 +865,7 @@ pub(crate) trait GenericLinter: Linter { fn fixes( &self, _context: &Self::Context, + _range: TextRange, _sema: &Semantic, _file_id: FileId, ) -> Option> { @@ -898,7 +899,7 @@ impl 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) diff --git a/crates/ide/src/diagnostics/boolean_precedence.rs b/crates/ide/src/diagnostics/boolean_precedence.rs index 21e374a4f5..f8f7851f8a 100644 --- a/crates/ide/src/diagnostics/boolean_precedence.rs +++ b/crates/ide/src/diagnostics/boolean_precedence.rs @@ -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> { @@ -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, diff --git a/crates/ide/src/diagnostics/edoc.rs b/crates/ide/src/diagnostics/edoc.rs index ead44d7331..052c85d57f 100644 --- a/crates/ide/src/diagnostics/edoc.rs +++ b/crates/ide/src/diagnostics/edoc.rs @@ -56,7 +56,6 @@ impl Linter for EdocLinter { pub struct Context { header_ptr: Option>, doc_start: TextSize, - range: TextRange, } impl GenericLinter for EdocLinter { @@ -77,7 +76,6 @@ impl GenericLinter for EdocLinter { context: Context { header_ptr: Some(*header_ptr), doc_start, - range: doc.range, }, }); } @@ -88,7 +86,6 @@ impl GenericLinter for EdocLinter { context: Context { header_ptr: Some(*header_ptr), doc_start, - range: equiv.range, }, }); } @@ -99,7 +96,6 @@ impl GenericLinter for EdocLinter { context: Context { header_ptr: Some(*header_ptr), doc_start, - range: deprecated.range, }, }); } @@ -111,7 +107,6 @@ impl GenericLinter for EdocLinter { context: Context { header_ptr: Some(*header_ptr), doc_start, - range: hidden.range, }, }); } @@ -123,6 +118,7 @@ impl GenericLinter for EdocLinter { fn fixes( &self, context: &Self::Context, + range: TextRange, sema: &Semantic, file_id: FileId, ) -> Option> { @@ -134,7 +130,7 @@ impl GenericLinter for EdocLinter { file_id, header, context.doc_start, - context.range, + range, )]) } } diff --git a/crates/ide/src/diagnostics/macro_precedence_suprise.rs b/crates/ide/src/diagnostics/macro_precedence_suprise.rs index 838ddb3d9c..6cd5fe2429 100644 --- a/crates/ide/src/diagnostics/macro_precedence_suprise.rs +++ b/crates/ide/src/diagnostics/macro_precedence_suprise.rs @@ -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> { - 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]) } diff --git a/crates/ide/src/diagnostics/missing_compile_warn_missing_spec.rs b/crates/ide/src/diagnostics/missing_compile_warn_missing_spec.rs index b1f24eb60f..dd0b677dce 100644 --- a/crates/ide/src/diagnostics/missing_compile_warn_missing_spec.rs +++ b/crates/ide/src/diagnostics/missing_compile_warn_missing_spec.rs @@ -75,7 +75,6 @@ impl Linter for MissingCompileWarnMissingSpec { pub struct Context { found: Found, compile_option_id: Option, - 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> { @@ -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, )]) } } diff --git a/crates/ide/src/diagnostics/misspelled_attribute.rs b/crates/ide/src/diagnostics/misspelled_attribute.rs index f328f57b20..15ad1dd9d8 100644 --- a/crates/ide/src/diagnostics/misspelled_attribute.rs +++ b/crates/ide/src/diagnostics/misspelled_attribute.rs @@ -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> { - 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, )]) } } diff --git a/crates/ide/src/diagnostics/undocumented_module.rs b/crates/ide/src/diagnostics/undocumented_module.rs index eeb4caf7b1..1b2c80cfc9 100644 --- a/crates/ide/src/diagnostics/undocumented_module.rs +++ b/crates/ide/src/diagnostics/undocumented_module.rs @@ -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> { + fn fixes( + &self, + _context: &Context, + range: TextRange, + sema: &Semantic, + file_id: FileId, + ) -> Option> { 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]) } diff --git a/crates/ide/src/diagnostics/unused_include.rs b/crates/ide/src/diagnostics/unused_include.rs index eed17b3c5c..eb26bcd923 100644 --- a/crates/ide/src/diagnostics/unused_include.rs +++ b/crates/ide/src/diagnostics/unused_include.rs @@ -137,6 +137,7 @@ impl GenericLinter for UnusedIncludeLinter { fn fixes( &self, context: &Self::Context, + _range: TextRange, _sema: &Semantic, file_id: FileId, ) -> Option> { diff --git a/crates/ide/src/diagnostics/unused_macro.rs b/crates/ide/src/diagnostics/unused_macro.rs index 089371f85b..6ea8756b91 100644 --- a/crates/ide/src/diagnostics/unused_macro.rs +++ b/crates/ide/src/diagnostics/unused_macro.rs @@ -88,7 +88,13 @@ impl GenericLinter for UnusedMacroLinter { Some(DiagnosticTag::Unused) } - fn fixes(&self, context: &Context, _sema: &Semantic, file_id: FileId) -> Option> { + fn fixes( + &self, + context: &Context, + _range: TextRange, + _sema: &Semantic, + file_id: FileId, + ) -> Option> { Some(vec![delete_unused_macro( file_id, context.delete_range, From 166770cc92c3db50613b6d2a1ba9acf13e240307 Mon Sep 17 00:00:00 2001 From: Victor Lanvin Date: Mon, 15 Dec 2025 03:16:34 -0800 Subject: [PATCH 105/142] Remove logic for --include-tests, opt-in tests 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 --- crates/elp/src/bin/args.rs | 8 -------- crates/elp/src/bin/eqwalizer_cli.rs | 17 ++++------------- crates/elp/src/bin/glean.rs | 2 +- crates/elp/src/bin/reporting.rs | 11 ++--------- crates/elp/src/bin/shell.rs | 10 ++-------- crates/elp/src/convert.rs | 9 +-------- .../src/resources/test/eqwalize_all_help.stdout | 3 +-- .../elp/src/resources/test/eqwalize_app.stdout | 3 +-- .../resources/test/eqwalize_target_help.stdout | 3 +-- .../eqwalize_all_bail_on_error_failure.pretty | 10 +++++++++- .../standard/eqwalize_all_diagnostics.jsonl | 1 + .../standard/eqwalize_all_diagnostics.pretty | 10 +++++++++- .../standard/eqwalize_all_diagnostics_gen.jsonl | 1 + .../eqwalize_app_diagnostics_gen_rebar.pretty | 10 +++++++++- .../eqwalize_app_diagnostics_rebar.pretty | 10 +++++++++- crates/elp/src/server.rs | 2 +- crates/elp/src/snapshot.rs | 4 ++-- crates/elp/tests/slow-tests/buck_tests.rs | 2 +- crates/ide/src/lib.rs | 8 ++++---- crates/ide_db/src/eqwalizer.rs | 14 +++++--------- crates/ide_db/src/lib.rs | 2 +- 21 files changed, 65 insertions(+), 75 deletions(-) diff --git a/crates/elp/src/bin/args.rs b/crates/elp/src/bin/args.rs index db3bc33df2..2d97f50562 100644 --- a/crates/elp/src/bin/args.rs +++ b/crates/elp/src/bin/args.rs @@ -155,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 @@ -173,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/... @@ -193,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 @@ -217,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, } diff --git a/crates/elp/src/bin/eqwalizer_cli.rs b/crates/elp/src/bin/eqwalizer_cli.rs index a91bcdd869..141b2157d0 100644 --- a/crates/elp/src/bin/eqwalizer_cli.rs +++ b/crates/elp/src/bin/eqwalizer_cli.rs @@ -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 diff --git a/crates/elp/src/bin/glean.rs b/crates/elp/src/bin/glean.rs index 2a45e450f2..bfd4757106 100644 --- a/crates/elp/src/bin/glean.rs +++ b/crates/elp/src/bin/glean.rs @@ -1560,7 +1560,7 @@ impl GleanIndexer { vars: FxHashMap<&Location, &String>, ) -> Vec { 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]); diff --git a/crates/elp/src/bin/reporting.rs b/crates/elp/src/bin/reporting.rs index fe38287629..7ab10459fe 100644 --- a/crates/elp/src/bin/reporting.rs +++ b/crates/elp/src/bin/reporting.rs @@ -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}")?; } diff --git a/crates/elp/src/bin/shell.rs b/crates/elp/src/bin/shell.rs index 84ab218710..13ff79ed36 100644 --- a/crates/elp/src/bin/shell.rs +++ b/crates/elp/src/bin/shell.rs @@ -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 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 Eqwalize all modules in specified application - --include-tests Also eqwalize test modules from project --clause-coverage Use experimental clause coverage checker "; diff --git a/crates/elp/src/convert.rs b/crates/elp/src/convert.rs index 3715353c11..c8db07a5d0 100644 --- a/crates/elp/src/convert.rs +++ b/crates/elp/src/convert.rs @@ -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```"), diff --git a/crates/elp/src/resources/test/eqwalize_all_help.stdout b/crates/elp/src/resources/test/eqwalize_all_help.stdout index 43f66a0a3a..67497c4000 100644 --- a/crates/elp/src/resources/test/eqwalize_all_help.stdout +++ b/crates/elp/src/resources/test/eqwalize_all_help.stdout @@ -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 Path to directory with project, or to a JSON file (defaults to `.`) --as Rebar3 profile to pickup (default is test) --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 diff --git a/crates/elp/src/resources/test/eqwalize_app.stdout b/crates/elp/src/resources/test/eqwalize_app.stdout index eaf1d3126a..bb128f249a 100644 --- a/crates/elp/src/resources/test/eqwalize_app.stdout +++ b/crates/elp/src/resources/test/eqwalize_app.stdout @@ -1,4 +1,4 @@ -Usage: [--project PROJECT] [--as PROFILE] [--include-tests] [--rebar] [--bail-on-error] +Usage: [--project PROJECT] [--as PROFILE] [--rebar] [--bail-on-error] Available positional items: app name @@ -6,7 +6,6 @@ Available positional items: Available options: --project Path to directory with project, or to a JSON file (defaults to `.`) --as 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 diff --git a/crates/elp/src/resources/test/eqwalize_target_help.stdout b/crates/elp/src/resources/test/eqwalize_target_help.stdout index eec74e4938..e9a01166dd 100644 --- a/crates/elp/src/resources/test/eqwalize_target_help.stdout +++ b/crates/elp/src/resources/test/eqwalize_target_help.stdout @@ -1,10 +1,9 @@ -Usage: [--project PROJECT] [--include-tests] [--bail-on-error] +Usage: [--project PROJECT] [--bail-on-error] Available positional items: target, like //erl/chatd/... Available options: --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 diff --git a/crates/elp/src/resources/test/standard/eqwalize_all_bail_on_error_failure.pretty b/crates/elp/src/resources/test/standard/eqwalize_all_bail_on_error_failure.pretty index bfbe45f508..1d868bbbc6 100644 --- a/crates/elp/src/resources/test/standard/eqwalize_all_bail_on_error_failure.pretty +++ b/crates/elp/src/resources/test/standard/eqwalize_all_bail_on_error_failure.pretty @@ -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 diff --git a/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics.jsonl b/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics.jsonl index 8636389098..b311705d68 100644 --- a/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics.jsonl +++ b/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics.jsonl @@ -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} diff --git a/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics.pretty b/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics.pretty index bfbe45f508..1d868bbbc6 100644 --- a/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics.pretty +++ b/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics.pretty @@ -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 diff --git a/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics_gen.jsonl b/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics_gen.jsonl index 8636389098..b311705d68 100644 --- a/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics_gen.jsonl +++ b/crates/elp/src/resources/test/standard/eqwalize_all_diagnostics_gen.jsonl @@ -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} diff --git a/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics_gen_rebar.pretty b/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics_gen_rebar.pretty index 67d78913b6..043aa00af0 100644 --- a/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics_gen_rebar.pretty +++ b/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics_gen_rebar.pretty @@ -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 diff --git a/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics_rebar.pretty b/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics_rebar.pretty index 67d78913b6..043aa00af0 100644 --- a/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics_rebar.pretty +++ b/crates/elp/src/resources/test/standard/eqwalize_app_diagnostics_rebar.pretty @@ -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 diff --git a/crates/elp/src/server.rs b/crates/elp/src/server.rs index 65c75a1bbc..98890640bc 100644 --- a/crates/elp/src/server.rs +++ b/crates/elp/src/server.rs @@ -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); } diff --git a/crates/elp/src/snapshot.rs b/crates/elp/src/snapshot.rs index e79251f73a..9aa3e7eeb4 100644 --- a/crates/elp/src/snapshot.rs +++ b/crates/elp/src/snapshot.rs @@ -193,7 +193,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 +242,7 @@ impl Snapshot { let file_ids: Vec = 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 diff --git a/crates/elp/tests/slow-tests/buck_tests.rs b/crates/elp/tests/slow-tests/buck_tests.rs index e76fdb8bad..76540e2515 100644 --- a/crates/elp/tests/slow-tests/buck_tests.rs +++ b/crates/elp/tests/slow-tests/buck_tests.rs @@ -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(); diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index dcaff6de70..009edd4684 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -251,9 +251,9 @@ impl Analysis { }) } - pub fn should_eqwalize(&self, file_id: FileId, include_tests: bool) -> Cancellable { + pub fn should_eqwalize(&self, file_id: FileId) -> Cancellable { 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 { - self.with_db(|db| db.is_eqwalizer_enabled(file_id, include_tests)) + pub fn is_eqwalizer_enabled(&self, file_id: FileId) -> Cancellable { + self.with_db(|db| db.is_eqwalizer_enabled(file_id)) } /// ETF for the module's abstract forms diff --git a/crates/ide_db/src/eqwalizer.rs b/crates/ide_db/src/eqwalizer.rs index 4deed79778..fca577bb21 100644 --- a/crates/ide_db/src/eqwalizer.rs +++ b/crates/ide_db/src/eqwalizer.rs @@ -12,7 +12,6 @@ use std::sync::Arc; use elp_base_db::FileId; use elp_base_db::FileRange; -use elp_base_db::FileSource; use elp_base_db::ModuleName; use elp_base_db::ProjectId; use elp_base_db::SourceDatabase; @@ -89,7 +88,7 @@ pub trait EqwalizerDatabase: fn types_for_file(&self, file_id: FileId) -> Option>>; fn has_eqwalizer_module_marker(&self, file_id: FileId) -> bool; fn has_eqwalizer_ignore_marker(&self, file_id: FileId) -> bool; - fn is_eqwalizer_enabled(&self, file_id: FileId, include_tests: bool) -> bool; + fn is_eqwalizer_enabled(&self, file_id: FileId) -> bool; } pub fn eqwalizer_diagnostics_by_project( @@ -114,7 +113,7 @@ fn type_at_position( db: &dyn EqwalizerDatabase, range: FileRange, ) -> Option> { - if !db.is_eqwalizer_enabled(range.file_id, false) { + if !db.is_eqwalizer_enabled(range.file_id) { return None; } let project_id = db.file_app_data(range.file_id)?.project_id; @@ -149,7 +148,7 @@ fn type_at_position( } fn types_for_file(db: &dyn EqwalizerDatabase, file_id: FileId) -> Option>> { - if !db.is_eqwalizer_enabled(file_id, false) { + if !db.is_eqwalizer_enabled(file_id) { return None; } let project_id = db.file_app_data(file_id)?.project_id; @@ -162,7 +161,7 @@ fn types_for_file(db: &dyn EqwalizerDatabase, file_id: FileId) -> Option bool { +fn is_eqwalizer_enabled(db: &dyn EqwalizerDatabase, file_id: FileId) -> bool { if !otp_supported_by_eqwalizer() { return false; } @@ -178,11 +177,8 @@ fn is_eqwalizer_enabled(db: &dyn EqwalizerDatabase, file_id: FileId, include_tes let project = db.project_data(project_id); let eqwalizer_config = &project.eqwalizer_config; let module_index = db.module_index(project_id); - let is_src = module_index.file_source_for_file(file_id) == Some(FileSource::Src); - let is_test_opted_in = db.is_test_suite_or_test_helper(file_id) == Some(true) && include_tests; let global_opt_in = eqwalizer_config.enable_all; - let opt_in = - (global_opt_in && (is_src || is_test_opted_in)) || db.has_eqwalizer_module_marker(file_id); + let opt_in = global_opt_in || db.has_eqwalizer_module_marker(file_id); let ignored_in_config = if let Some(module_name) = module_index.module_for_file(file_id) { eqwalizer_config .ignore_modules_compiled_patterns diff --git a/crates/ide_db/src/lib.rs b/crates/ide_db/src/lib.rs index 9b64d09b31..30a67cfa24 100644 --- a/crates/ide_db/src/lib.rs +++ b/crates/ide_db/src/lib.rs @@ -385,7 +385,7 @@ impl TypedSemantic for RootDatabase { let project_id = app_data.project_id; - let eqwalizer_enabled = self.is_eqwalizer_enabled(file_id, false); + let eqwalizer_enabled = self.is_eqwalizer_enabled(file_id); if !eqwalizer_enabled { return Some(vec![]); } From d2da476882e325c0d431a1623dc4f5e7710e91b9 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Tue, 16 Dec 2025 02:03:43 -0800 Subject: [PATCH 106/142] 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 --- test_projects/codegen_test/.elp.toml | 8 + test_projects/codegen_test/README.md | 139 ++++++++++++++++++ .../app_a/src/codegen_test.app.src | 14 ++ .../codegen_test/app_a/src/example_usage.erl | 51 +++++++ .../app_a/test/codegen_test_SUITE.erl | 77 ++++++++++ .../generated/example_service_client.erl | 37 +++++ .../generated/example_service_types.erl | 55 +++++++ .../generated/example_service_types.hrl | 27 ++++ .../templates/example_service_client.erl | 38 +++++ .../templates/example_service_types.erl | 56 +++++++ .../templates/example_service_types.hrl | 28 ++++ 11 files changed, 530 insertions(+) create mode 100644 test_projects/codegen_test/.elp.toml create mode 100644 test_projects/codegen_test/README.md create mode 100644 test_projects/codegen_test/app_a/src/codegen_test.app.src create mode 100644 test_projects/codegen_test/app_a/src/example_usage.erl create mode 100644 test_projects/codegen_test/app_a/test/codegen_test_SUITE.erl create mode 100644 test_projects/codegen_test/generated/example_service_client.erl create mode 100644 test_projects/codegen_test/generated/example_service_types.erl create mode 100644 test_projects/codegen_test/generated/example_service_types.hrl create mode 100644 test_projects/codegen_test/templates/example_service_client.erl create mode 100644 test_projects/codegen_test/templates/example_service_types.erl create mode 100644 test_projects/codegen_test/templates/example_service_types.hrl diff --git a/test_projects/codegen_test/.elp.toml b/test_projects/codegen_test/.elp.toml new file mode 100644 index 0000000000..f0f166bcf8 --- /dev/null +++ b/test_projects/codegen_test/.elp.toml @@ -0,0 +1,8 @@ +[buck] +enabled = true +build_deps = true +included_targets = [ "fbcode//whatsapp/elp/test_projects/codegen_test:codegen_test_app" ] +source_root = "whatsapp/elp/test_projects/codegen_test" + +[eqwalizer] +enable_all = true diff --git a/test_projects/codegen_test/README.md b/test_projects/codegen_test/README.md new file mode 100644 index 0000000000..1af599454c --- /dev/null +++ b/test_projects/codegen_test/README.md @@ -0,0 +1,139 @@ +# Code Generation Test Project + +This test project demonstrates Buck2-based code generation for Erlang +applications using `buck_genrule`. + +## Project Structure + +``` +codegen_test/ +├── .elp.toml # ELP configuration +├── BUCK # Buck build configuration with genrule +├── README.md # This file +├── templates/ # Erlang template files (input) +│ ├── example_service_types.erl # Type definitions template +│ ├── example_service_client.erl # Client stubs template +│ └── example_service_types.hrl # Header file template +├── generated/ # Pre-generated code (for reference) +│ ├── example_service_types.erl # Generated type module +│ ├── example_service_client.erl # Generated client stubs +│ └── example_service_types.hrl # Generated header file +└── app_a/ + ├── src/ + │ ├── codegen_test.app.src # Application metadata + │ └── example_usage.erl # Example using generated code + └── test/ + └── codegen_test_SUITE.erl # Test suite +``` + +## How It Works + +### 1. Template Files + +The templates directory contains well-formed Erlang files that serve as input to +the code generation process. These are complete, valid Erlang files that are +copied to the output directory during the build. + +### 2. Code Generation + +The code is generated automatically during the build process using a +`buck_genrule`: + +```python +buck_genrule( + name = "example_service_types_erl", + srcs = [ + "templates/example_service_types.erl", + ], + outs = { + "example_service_types.erl": ["example_service_types.erl"], + }, + bash = "cp $SRCDIR/templates/example_service_types.erl $OUT/example_service_types.erl", + cmd_exe = "copy %SRCDIR%\\templates\\example_service_types.erl %OUT%\\example_service_types.erl", +) +``` + +The genrule: + +- Takes the template file as input (`srcs`) +- Defines named outputs using `outs` parameter (creates subtargets for each + file) +- Uses `cp` (bash) or `copy` (cmd_exe) to copy template files to `$OUT` +- Outputs files to `$OUT` directory in `buck-out/` + +### 3. Build Integration + +The `BUCK` file references the generated files using subtarget syntax: + +```python +erlang_app( + name = "example_service_generated", + srcs = [ + ":example_service_types_erl[example_service_types.erl]", + ":example_service_client_erl[example_service_client.erl]", + ], + includes = [ + ":example_service_types_hrl[example_service_types.hrl]", + ], +) +``` + +When Buck builds the application: + +1. The genrule runs first, copying the template Erlang files to the output +2. The generated files are placed in + `buck-out/v2/gen/.../example_service_types_erl/` +3. The `erlang_app` consumes these files via subtarget references (`[filename]` + syntax) +4. The files are copied to the application's build directory in `buck-out/` + +### 4. Using Generated Code + +Application code includes the generated header and uses the types: + +```erlang +-module(example_usage). +-include("example_service_types.hrl"). + +create_sample_user() -> + #user_info{ + user_id = <<"user_123">>, + username = <<"john_doe">>, + age = 25, + is_active = true + }. + +get_user_by_id(UserId) -> + example_service_client:get_user(UserId). +``` + +## Building and Testing + +```bash +# Build just the code generation step +buck2 build fbcode//whatsapp/elp/test_projects/codegen_test:example_service_types_erl + +# Build the application (automatically runs code generation) +buck2 build fbcode//whatsapp/elp/test_projects/codegen_test:codegen_test_app + +# Run tests +buck2 test fbcode//whatsapp/elp/test_projects/codegen_test:codegen_test_SUITE + +# View generated files in buck-out +find buck-out -path "*codegen_test*" -name "example_service_*.erl" +``` + +## Test Coverage + +The test suite (`app_a/test/codegen_test_SUITE.erl`) verifies: + +- Generated record types work correctly +- Generated client functions are callable +- Record fields have correct types and defaults +- Integration with application code + +All tests pass: + +``` +Tests finished: Pass 3. Fail 0. Fatal 0. Skip 0. Build failure 0 +``` diff --git a/test_projects/codegen_test/app_a/src/codegen_test.app.src b/test_projects/codegen_test/app_a/src/codegen_test.app.src new file mode 100644 index 0000000000..7a38d49a24 --- /dev/null +++ b/test_projects/codegen_test/app_a/src/codegen_test.app.src @@ -0,0 +1,14 @@ +{application, codegen_test, + [{description, "Test application demonstrating code generation"}, + {vsn, "1.0.0"}, + {registered, []}, + {applications, + [kernel, + stdlib, + example_service_generated + ]}, + {env,[]}, + {modules, []}, + {licenses, ["Apache 2.0"]}, + {links, []} + ]}. diff --git a/test_projects/codegen_test/app_a/src/example_usage.erl b/test_projects/codegen_test/app_a/src/example_usage.erl new file mode 100644 index 0000000000..a624c2089b --- /dev/null +++ b/test_projects/codegen_test/app_a/src/example_usage.erl @@ -0,0 +1,51 @@ +-module(example_usage). +-compile([warn_missing_spec_all]). +-moduledoc """ +Example module that demonstrates using generated code. +This module uses the types and client functions generated from +the example_service.schema file. +""". + +%% Include the generated header file +-include_lib("example_service_generated/include/example_service_types.hrl"). + +%% API exports +-export([ + create_sample_user/0, + create_query_request/1, + get_user_by_id/1 +]). + +%%%=================================================================== +%%% API +%%%=================================================================== + +-doc """ +Creates a sample user_info record using generated types +""". +-spec create_sample_user() -> #user_info{}. +create_sample_user() -> + #user_info{ + user_id = <<"user_123">>, + username = <<"john_doe">>, + age = 25, + is_active = true + }. + +-doc """ +Creates a query_request record +""". +-spec create_query_request(binary()) -> #query_request{}. +create_query_request(QueryId) -> + #query_request{ + query_id = QueryId, + filters = [{age, greater_than, 18}] + }. + +-doc """ +Uses the generated client to get user information +""". +-spec get_user_by_id(binary()) -> {ok, term()} | {error, term()}. +get_user_by_id(UserId) -> + %% This calls the generated client function + example_service_client:get_user(UserId). diff --git a/test_projects/codegen_test/app_a/test/codegen_test_SUITE.erl b/test_projects/codegen_test/app_a/test/codegen_test_SUITE.erl new file mode 100644 index 0000000000..797c3c58f3 --- /dev/null +++ b/test_projects/codegen_test/app_a/test/codegen_test_SUITE.erl @@ -0,0 +1,77 @@ +%%%------------------------------------------------------------------- +%%% @doc +%%% Test suite for code generation functionality +%%% @end +%%%------------------------------------------------------------------- +-module(codegen_test_SUITE). + +-include_lib("stdlib/include/assert.hrl"). +-include("example_service_types.hrl"). + +%% CT callbacks +-export([all/0, init_per_suite/1, end_per_suite/1]). + +%% Test cases +-export([ + test_generated_types/1, + test_generated_client/1, + test_user_record_creation/1 +]). + +%%%=================================================================== +%%% CT Callbacks +%%%=================================================================== + +all() -> + [ + test_generated_types, + test_generated_client, + test_user_record_creation + ]. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +%%%=================================================================== +%%% Test Cases +%%%=================================================================== + +test_generated_types(_Config) -> + %% Test that we can create a user_info record + User = #user_info{ + user_id = <<"test_123">>, + username = <<"testuser">>, + age = 30, + is_active = true + }, + + %% Verify the record fields + ?assertEqual(<<"test_123">>, User#user_info.user_id), + ?assertEqual(<<"testuser">>, User#user_info.username), + ?assertEqual(30, User#user_info.age), + ?assertEqual(true, User#user_info.is_active), + + ok. + +test_generated_client(_Config) -> + %% Test that the generated client module exists and can be called + Result = example_service_client:get_user(<<"user_123">>), + + %% The generated stub returns {ok, generated_response} + ?assertMatch({ok, _}, Result), + + ok. + +test_user_record_creation(_Config) -> + %% Test the example_usage module + User = example_usage:create_sample_user(), + + %% Verify it's a valid user_info record + ?assertMatch(#user_info{}, User), + ?assertEqual(<<"user_123">>, User#user_info.user_id), + ?assertEqual(25, User#user_info.age), + + ok. diff --git a/test_projects/codegen_test/generated/example_service_client.erl b/test_projects/codegen_test/generated/example_service_client.erl new file mode 100644 index 0000000000..6b38c39425 --- /dev/null +++ b/test_projects/codegen_test/generated/example_service_client.erl @@ -0,0 +1,37 @@ +%%%------------------------------------------------------------------- +%%% @doc +%%% Auto-generated client for example_service +%%% DO NOT EDIT - Generated by generate_erlang.sh +%%% @end +%%%------------------------------------------------------------------- +-module(example_service_client). + +-include("example_service_types.hrl"). + +%% API exports +-export([get_user/1]). +-export([query_users/1]). +-export([update_user/2]). + +%%%=================================================================== +%%% API +%%%=================================================================== + + +%% @doc Retrieves user information by user ID +-spec get_user(term()) -> {ok, term()} | {error, term()}. +get_user(_UserId) -> + %% Auto-generated client stub + {ok, generated_response}. + +%% @doc Queries users with given filters +-spec query_users(term()) -> {ok, term()} | {error, term()}. +query_users(_Request) -> + %% Auto-generated client stub + {ok, generated_response}. + +%% @doc Updates user information +-spec update_user(term(), term()) -> {ok, term()} | {error, term()}. +update_user(_UserId, _UserInfo) -> + %% Auto-generated client stub + {ok, generated_response}. diff --git a/test_projects/codegen_test/generated/example_service_types.erl b/test_projects/codegen_test/generated/example_service_types.erl new file mode 100644 index 0000000000..3f89adcba9 --- /dev/null +++ b/test_projects/codegen_test/generated/example_service_types.erl @@ -0,0 +1,55 @@ +%%%------------------------------------------------------------------- +%%% @doc +%%% Auto-generated type definitions for example_service +%%% DO NOT EDIT - Generated by generate_erlang.sh +%%% @end +%%%------------------------------------------------------------------- +-module(example_service_types). + +%% API exports +-export([new_user_info/0]). +-export([new_query_request/0]). +-export([new_query_response/0]). +-export([get_version/0]). + +%% Type exports +-export_type([user_info/0]). +-export_type([query_request/0]). +-export_type([query_response/0]). + +%% Record definitions +-record(user_info, {user_id = <<>>, username = <<>>, age = 0, is_active = false}). +-record(query_request, {query_id = <<>>, filters = []}). +-record(query_response, {results = [], count = 0}). + +%% Type definitions +-type user_info() :: #user_info{}. +-type query_request() :: #query_request{}. +-type query_response() :: #query_response{}. + +%%%=================================================================== +%%% API +%%%=================================================================== + +%% @doc Returns the schema version +-spec get_version() -> binary(). +get_version() -> + <<"1.0.0">>. + + +%% @doc Creates a new user_info record +-spec new_user_info() -> #user_info{}. +new_user_info() -> + #user_info{}. + + +%% @doc Creates a new query_request record +-spec new_query_request() -> #query_request{}. +new_query_request() -> + #query_request{}. + + +%% @doc Creates a new query_response record +-spec new_query_response() -> #query_response{}. +new_query_response() -> + #query_response{}. diff --git a/test_projects/codegen_test/generated/example_service_types.hrl b/test_projects/codegen_test/generated/example_service_types.hrl new file mode 100644 index 0000000000..4c957a0906 --- /dev/null +++ b/test_projects/codegen_test/generated/example_service_types.hrl @@ -0,0 +1,27 @@ +%%%------------------------------------------------------------------- +%%% @doc +%%% Auto-generated header file for example_service +%%% DO NOT EDIT - Generated by generate_erlang.sh +%%% @end +%%%------------------------------------------------------------------- + + +-record(user_info, { + + user_id = <<>> :: binary(), + username = <<>> :: binary(), + age = 0 :: integer(), + is_active = false :: boolean() +}). + +-record(query_request, { + + query_id = <<>> :: binary(), + filters = [] :: list() +}). + +-record(query_response, { + + results = [] :: list(), + count = 0 :: integer() +}). diff --git a/test_projects/codegen_test/templates/example_service_client.erl b/test_projects/codegen_test/templates/example_service_client.erl new file mode 100644 index 0000000000..ab30a89f74 --- /dev/null +++ b/test_projects/codegen_test/templates/example_service_client.erl @@ -0,0 +1,38 @@ +%%%------------------------------------------------------------------- +%%% @doc +%%% Auto-generated client for example_service +%%% DO NOT EDIT - Generated by generate_erlang.sh +%%% @end +%%%------------------------------------------------------------------- +-module(example_service_client). + +-include("example_service_types.hrl"). + +%% API exports +-export([get_user/1]). +-export([query_users/1]). +-export([update_user/2]). + +%%%=================================================================== +%%% API +%%%=================================================================== + + +%% @doc Retrieves user information by user ID +-spec get_user(term()) -> {ok, term()} | {error, term()}. +get_user(_UserId) -> + %% Auto-generated client stub + {ok, generated_response}. + +%% @doc Queries users with given filters +-spec query_users(term()) -> {ok, term()} | {error, term()}. +query_users(_Request) -> + %% Auto-generated client stub + {ok, generated_response}. + +%% @doc Updates user information +-spec update_user(term(), term()) -> {ok, term()} | {error, term()}. +update_user(_UserId, _UserInfo) -> + %% Auto-generated client stub + {ok, generated_response}. + diff --git a/test_projects/codegen_test/templates/example_service_types.erl b/test_projects/codegen_test/templates/example_service_types.erl new file mode 100644 index 0000000000..833df167c6 --- /dev/null +++ b/test_projects/codegen_test/templates/example_service_types.erl @@ -0,0 +1,56 @@ +%%%------------------------------------------------------------------- +%%% @doc +%%% Auto-generated type definitions for example_service +%%% DO NOT EDIT - Generated by generate_erlang.sh +%%% @end +%%%------------------------------------------------------------------- +-module(example_service_types). + +%% API exports +-export([new_user_info/0]). +-export([new_query_request/0]). +-export([new_query_response/0]). +-export([get_version/0]). + +%% Type exports +-export_type([user_info/0]). +-export_type([query_request/0]). +-export_type([query_response/0]). + +%% Record definitions +-record(user_info, {user_id = <<>>, username = <<>>, age = 0, is_active = false}). +-record(query_request, {query_id = <<>>, filters = []}). +-record(query_response, {results = [], count = 0}). + +%% Type definitions +-type user_info() :: #user_info{}. +-type query_request() :: #query_request{}. +-type query_response() :: #query_response{}. + +%%%=================================================================== +%%% API +%%%=================================================================== + +%% @doc Returns the schema version +-spec get_version() -> binary(). +get_version() -> + <<"1.0.0">>. + + +%% @doc Creates a new user_info record +-spec new_user_info() -> #user_info{}. +new_user_info() -> + #user_info{}. + + +%% @doc Creates a new query_request record +-spec new_query_request() -> #query_request{}. +new_query_request() -> + #query_request{}. + + +%% @doc Creates a new query_response record +-spec new_query_response() -> #query_response{}. +new_query_response() -> + #query_response{}. + diff --git a/test_projects/codegen_test/templates/example_service_types.hrl b/test_projects/codegen_test/templates/example_service_types.hrl new file mode 100644 index 0000000000..88a2e7aed2 --- /dev/null +++ b/test_projects/codegen_test/templates/example_service_types.hrl @@ -0,0 +1,28 @@ +%%%------------------------------------------------------------------- +%%% @doc +%%% Auto-generated header file for example_service +%%% DO NOT EDIT - Generated by generate_erlang.sh +%%% @end +%%%------------------------------------------------------------------- + + +-record(user_info, { + + user_id = <<>> :: binary(), + username = <<>> :: binary(), + age = 0 :: integer(), + is_active = false :: boolean() +}). + +-record(query_request, { + + query_id = <<>> :: binary(), + filters = [] :: list() +}). + +-record(query_response, { + + results = [] :: list(), + count = 0 :: integer() +}). + From ba4505e981affde892245bd946fce7fb1beba01e Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Tue, 16 Dec 2025 08:56:01 -0800 Subject: [PATCH 107/142] Do not run application_env linter on test suites Reviewed By: alanz, TheGeorge, michalmuskala Differential Revision: D89287769 fbshipit-source-id: 29618250ee6d75ede850603cb60c5e4d77e3ff06 --- crates/ide/src/diagnostics/application_env.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ide/src/diagnostics/application_env.rs b/crates/ide/src/diagnostics/application_env.rs index 1e4fe7c4cf..be22f581f5 100644 --- a/crates/ide/src/diagnostics/application_env.rs +++ b/crates/ide/src/diagnostics/application_env.rs @@ -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| { From 271a52c37cd77c27c51641d61efbae006888ba61 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Tue, 16 Dec 2025 09:08:25 -0800 Subject: [PATCH 108/142] honour `--include-generated` for `elp ssr` Summary: As title Reviewed By: michalmuskala, TD5 Differential Revision: D89292307 fbshipit-source-id: cbde669d57276b2aaa2c7365906e3ed86f15aec5 --- crates/elp/src/bin/main.rs | 30 +++++++++++++++++++ crates/elp/src/bin/ssr_cli.rs | 3 ++ .../diagnostics/ssr_exclude_generated.stdout | 2 ++ .../diagnostics/ssr_include_generated.stdout | 5 ++++ 4 files changed, 40 insertions(+) create mode 100644 crates/elp/src/resources/test/diagnostics/ssr_exclude_generated.stdout create mode 100644 crates/elp/src/resources/test/diagnostics/ssr_include_generated.stdout diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index e1d26e3e7e..c9b2db945a 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -2191,6 +2191,36 @@ 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. diff --git a/crates/elp/src/bin/ssr_cli.rs b/crates/elp/src/bin/ssr_cli.rs index f5729034b2..36f26d4554 100644 --- a/crates/elp/src/bin/ssr_cli.rs +++ b/crates/elp/src/bin/ssr_cli.rs @@ -401,6 +401,9 @@ fn do_parse_one( name: &str, args: &Ssr, ) -> Result)>> { + 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); } diff --git a/crates/elp/src/resources/test/diagnostics/ssr_exclude_generated.stdout b/crates/elp/src/resources/test/diagnostics/ssr_exclude_generated.stdout new file mode 100644 index 0000000000..1cef26124c --- /dev/null +++ b/crates/elp/src/resources/test/diagnostics/ssr_exclude_generated.stdout @@ -0,0 +1,2 @@ +module specified: erlang_diagnostics_errors_gen +No matches found diff --git a/crates/elp/src/resources/test/diagnostics/ssr_include_generated.stdout b/crates/elp/src/resources/test/diagnostics/ssr_include_generated.stdout new file mode 100644 index 0000000000..2b88b6e10c --- /dev/null +++ b/crates/elp/src/resources/test/diagnostics/ssr_include_generated.stdout @@ -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 From 858b8e64ab85f60b39029d8113cefc5e29b5c9d1 Mon Sep 17 00:00:00 2001 From: Facebook Community Bot Date: Tue, 16 Dec 2025 10:54:56 -0800 Subject: [PATCH 109/142] 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 --- .cargo/config.toml | 4 ++-- .vscode/tasks.json | 12 +++++----- crates/base_db/src/lib.rs | 4 ++-- crates/elp/src/arc_types.rs | 4 ++-- crates/elp/src/bin/glean.rs | 12 +++++----- crates/elp/src/bin/main.rs | 4 ++-- crates/elp/src/config.rs | 4 ++-- crates/elp/src/lib.rs | 4 ++-- crates/elp/src/server/setup.rs | 4 ++-- crates/hir/src/lib.rs | 2 +- crates/hir/src/name.rs | 2 +- crates/hir/src/sema.rs | 2 +- crates/hir/src/sema/to_def.rs | 6 ++--- crates/ide/src/annotations.rs | 8 +++---- crates/ide/src/diagnostics.rs | 8 +++---- crates/ide/src/diagnostics/application_env.rs | 4 ++-- .../ide/src/diagnostics/atoms_exhaustion.rs | 14 +++++------ .../ide/src/diagnostics/debugging_function.rs | 4 ++-- .../src/diagnostics/deprecated_function.rs | 4 ++-- .../src/diagnostics/unexported_function.rs | 6 ++--- .../ide/src/diagnostics/unspecific_include.rs | 4 ++-- crates/ide/src/doc_links.rs | 8 +++---- crates/ide/src/lib.rs | 2 +- crates/ide_completion/src/lib.rs | 4 ++-- crates/ide_db/src/diagnostic_code.rs | 24 +++++++++---------- crates/ide_db/src/lib.rs | 2 +- crates/project_model/src/buck.rs | 2 +- erlang_service/src/elp_lint.erl | 8 +++---- 28 files changed, 83 insertions(+), 83 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 7e4e7a0f90..eb89fa1e55 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -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 diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 51f0340659..7572a84e98 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -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", diff --git a/crates/base_db/src/lib.rs b/crates/base_db/src/lib.rs index 6b3757ff43..0cd8df74c9 100644 --- a/crates/base_db/src/lib.rs +++ b/crates/base_db/src/lib.rs @@ -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 = { let regexes: Vec> = 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::>() }; diff --git a/crates/elp/src/arc_types.rs b/crates/elp/src/arc_types.rs index c113311661..374dcdba2f 100644 --- a/crates/elp/src/arc_types.rs +++ b/crates/elp/src/arc_types.rs @@ -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; diff --git a/crates/elp/src/bin/glean.rs b/crates/elp/src/bin/glean.rs index bfd4757106..cb420261d2 100644 --- a/crates/elp/src/bin/glean.rs +++ b/crates/elp/src/bin/glean.rs @@ -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 = 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, .. }) @@ -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 { diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index c9b2db945a..ac2fc901fa 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -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(); } } } diff --git a/crates/elp/src/config.rs b/crates/elp/src/config.rs index 2637eae5a2..681a022261 100644 --- a/crates/elp/src/config.rs +++ b/crates/elp/src/config.rs @@ -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) { diff --git a/crates/elp/src/lib.rs b/crates/elp/src/lib.rs index 0e9a2e28e6..f462141e39 100644 --- a/crates/elp/src/lib.rs +++ b/crates/elp/src/lib.rs @@ -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(); } diff --git a/crates/elp/src/server/setup.rs b/crates/elp/src/server/setup.rs index 8dba975e73..4b8615e07c 100644 --- a/crates/elp/src/server/setup.rs +++ b/crates/elp/src/server/setup.rs @@ -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!( diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 1ec75fd25e..35585ef702 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -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; diff --git a/crates/hir/src/name.rs b/crates/hir/src/name.rs index 00ce2f89f1..f94f139d38 100644 --- a/crates/hir/src/name.rs +++ b/crates/hir/src/name.rs @@ -10,7 +10,7 @@ //! See [`Name`]. -// @fb-only +// @fb-only: pub mod meta_only; use std::borrow::Cow; use std::collections::HashSet; diff --git a/crates/hir/src/sema.rs b/crates/hir/src/sema.rs index 78a7db7c3c..7d1b416a63 100644 --- a/crates/hir/src/sema.rs +++ b/crates/hir/src/sema.rs @@ -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); diff --git a/crates/hir/src/sema/to_def.rs b/crates/hir/src/sema/to_def.rs index 5918054fc5..0c8eba227e 100644 --- a/crates/hir/src/sema/to_def.rs +++ b/crates/hir/src/sema/to_def.rs @@ -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 { @@ -890,7 +890,7 @@ lazy_static! { static ref DYNAMIC_CALL_PATTERNS: FxHashMap = { let mut patterns = FxHashMap::default(); add_dynamic_call_patterns(&mut patterns); - // @fb-only + // @fb-only: meta_only::add_dynamic_call_patterns(&mut patterns); patterns }; } diff --git a/crates/ide/src/annotations.rs b/crates/ide/src/annotations.rs index 6cbdd3469c..d0ae18c158 100644 --- a/crates/ide/src/annotations.rs +++ b/crates/ide/src/annotations.rs @@ -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 { pub(crate) fn annotations(_db: &RootDatabase, _file_id: FileId) -> Vec { // @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 } diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 9852303132..093ba35986 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -117,7 +117,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; @@ -1525,7 +1525,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, @@ -1715,7 +1715,7 @@ pub(crate) fn linters() -> Vec { ); // Add meta-only linters - // @fb-only + // @fb-only: all_linters.extend(meta_only::linters()); all_linters } @@ -2593,7 +2593,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 diff --git a/crates/ide/src/diagnostics/application_env.rs b/crates/ide/src/diagnostics/application_env.rs index be22f581f5..615b7801f9 100644 --- a/crates/ide/src/diagnostics/application_env.rs +++ b/crates/ide/src/diagnostics/application_env.rs @@ -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; @@ -108,7 +108,7 @@ fn check_function(diags: &mut Vec, sema: &Semantic, def: &FunctionDe vec![2, 3], BadEnvCallAction::AppArg(0), ), - // @fb-only + // @fb-only: diagnostics::meta_only::application_env_bad_matches(), ] .into_iter() .flatten() diff --git a/crates/ide/src/diagnostics/atoms_exhaustion.rs b/crates/ide/src/diagnostics/atoms_exhaustion.rs index 4708e35b1f..56a43a50ca 100644 --- a/crates/ide/src/diagnostics/atoms_exhaustion.rs +++ b/crates/ide/src/diagnostics/atoms_exhaustion.rs @@ -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::>() ] } fn check_match(&self, context: &CheckCallCtx<'_, ()>) -> Option { #[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() { diff --git a/crates/ide/src/diagnostics/debugging_function.rs b/crates/ide/src/diagnostics/debugging_function.rs index 94c3d11364..1f0099781d 100644 --- a/crates/ide/src/diagnostics/debugging_function.rs +++ b/crates/ide/src/diagnostics/debugging_function.rs @@ -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::>() ] } diff --git a/crates/ide/src/diagnostics/deprecated_function.rs b/crates/ide/src/diagnostics/deprecated_function.rs index 540edfcca3..469c327eb7 100644 --- a/crates/ide/src/diagnostics/deprecated_function.rs +++ b/crates/ide/src/diagnostics/deprecated_function.rs @@ -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, sema: &Semantic, file_ lazy_static! { static ref DEPRECATED_FUNCTIONS: Vec<(FunctionMatch, DeprecationDetails)> = { let matches: Vec> = vec![ - // @fb-only + // @fb-only: diagnostics::meta_only::deprecated_function_matches(), ]; matches.into_iter() .flatten() diff --git a/crates/ide/src/diagnostics/unexported_function.rs b/crates/ide/src/diagnostics/unexported_function.rs index 7aae73a9f1..2e1ebf7f54 100644 --- a/crates/ide/src/diagnostics/unexported_function.rs +++ b/crates/ide/src/diagnostics/unexported_function.rs @@ -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) } } diff --git a/crates/ide/src/diagnostics/unspecific_include.rs b/crates/ide/src/diagnostics/unspecific_include.rs index f7405c798a..431740c085 100644 --- a/crates/ide/src/diagnostics/unspecific_include.rs +++ b/crates/ide/src/diagnostics/unspecific_include.rs @@ -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) } diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index d13befe8bb..6a85ab3dc3 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs @@ -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) } diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 009edd4684..ad5c1bb680 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -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; diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs index 71b4ff02c0..f41aecdd67 100644 --- a/crates/ide_completion/src/lib.rs +++ b/crates/ide_completion/src/lib.rs @@ -40,7 +40,7 @@ mod helpers; mod keywords; mod macros; mod maps; -// @fb-only +// @fb-only: mod meta_only; mod modules; mod records; mod spec; @@ -176,7 +176,7 @@ pub fn completions( } CtxKind::Other => { let _ = attributes::add_completions(&mut acc, ctx) - // @fb-only + // @fb-only: || meta_only::add_completions(&mut acc, ctx) || vars::add_completions(&mut acc, ctx) || maps::add_completions(&mut acc, ctx) || records::add_completions(&mut acc, ctx); diff --git a/crates/ide_db/src/diagnostic_code.rs b/crates/ide_db/src/diagnostic_code.rs index 957caecb09..e9d21cadca 100644 --- a/crates/ide_db/src/diagnostic_code.rs +++ b/crates/ide_db/src/diagnostic_code.rs @@ -20,9 +20,9 @@ use serde::de; use strum::IntoEnumIterator; use strum_macros::EnumIter; -// @fb-only +// @fb-only: use crate::meta_only::MetaOnlyDiagnosticCode; -// @fb-only +// @fb-only: pub const BASE_URL: &str = crate::meta_only::BASE_URL; pub const BASE_URL: &str = "https://whatsapp.github.io/erlang-language-platform/docs"; // @oss-only #[derive(Clone, Debug, PartialEq, Eq, Hash, EnumIter)] @@ -100,7 +100,7 @@ pub enum DiagnosticCode { Eqwalizer(String), // Used for ad-hoc diagnostics via lints/codemods AdHoc(String), - // @fb-only + // @fb-only: MetaOnly(MetaOnlyDiagnosticCode), } // These namespaces map the error codes returned by the Erlang Service. @@ -116,7 +116,7 @@ pub enum Namespace { Parser, EDoc, WhatsApp, - // @fb-only + // @fb-only: MetaOnly, } impl fmt::Display for Namespace { @@ -131,7 +131,7 @@ impl fmt::Display for Namespace { Namespace::Parser => "p", Namespace::EDoc => "o", Namespace::WhatsApp => "w", - // @fb-only + // @fb-only: Namespace::MetaOnly => "meta_only", }; write!(f, "{namespace}") } @@ -164,7 +164,7 @@ impl Namespace { pub fn supports_doc_path(&self) -> bool { match self { Namespace::WhatsApp => true, - // @fb-only + // @fb-only: Namespace::MetaOnly => true, _ => false, } } @@ -259,7 +259,7 @@ impl DiagnosticCode { DiagnosticCode::ErlangService(c) => c.to_string(), DiagnosticCode::Eqwalizer(c) => format!("eqwalizer: {c}"), DiagnosticCode::AdHoc(c) => format!("ad-hoc: {c}"), - // @fb-only + // @fb-only: DiagnosticCode::MetaOnly(c) => c.as_code(), } } @@ -360,7 +360,7 @@ impl DiagnosticCode { DiagnosticCode::ErlangService(c) => c.to_string(), DiagnosticCode::Eqwalizer(c) => c.to_string(), DiagnosticCode::AdHoc(c) => format!("ad-hoc: {c}"), - // @fb-only + // @fb-only: DiagnosticCode::MetaOnly(c) => c.as_label(), } } @@ -371,7 +371,7 @@ impl DiagnosticCode { pub fn maybe_from_string(s: &str) -> Option { DIAGNOSTIC_CODE_LOOKUPS .get(s).cloned() - // @fb-only + // @fb-only: .or_else(|| MetaOnlyDiagnosticCode::from_str(s).ok().map(DiagnosticCode::MetaOnly)) .or_else( || // Look for ErlangService and AdHoc if let Some(code) = Self::is_adhoc(s) { @@ -388,7 +388,7 @@ impl DiagnosticCode { match self { DiagnosticCode::DefaultCodeForEnumIter => None, DiagnosticCode::AdHoc(_) => None, - // @fb-only + // @fb-only: DiagnosticCode::MetaOnly(_) => Some(Namespace::MetaOnly), DiagnosticCode::ErlangService(code) => Namespace::from_str(code).ok(), _ => Namespace::from_str(&self.as_code()).ok(), } @@ -397,7 +397,7 @@ impl DiagnosticCode { pub fn supports_doc_path(&self) -> bool { match self { DiagnosticCode::DefaultCodeForEnumIter => false, - // @fb-only + // @fb-only: DiagnosticCode::MetaOnly(MetaOnlyDiagnosticCode::DefaultCodeForEnumIter) => false, _ => true, } } @@ -541,7 +541,7 @@ impl DiagnosticCode { DiagnosticCode::ErlangService(_) => false, DiagnosticCode::Eqwalizer(_) => false, DiagnosticCode::AdHoc(_) => false, - // @fb-only + // @fb-only: DiagnosticCode::MetaOnly(code) => code.allows_fixme_comment(), } } diff --git a/crates/ide_db/src/lib.rs b/crates/ide_db/src/lib.rs index 30a67cfa24..aecfd86473 100644 --- a/crates/ide_db/src/lib.rs +++ b/crates/ide_db/src/lib.rs @@ -57,7 +57,7 @@ pub mod docs; pub mod eqwalizer; mod erl_ast; mod line_index; -// @fb-only +// @fb-only: pub mod meta_only; pub mod metadata; mod search; pub mod text_edit; diff --git a/crates/project_model/src/buck.rs b/crates/project_model/src/buck.rs index 013f3cbc87..3658a6d783 100644 --- a/crates/project_model/src/buck.rs +++ b/crates/project_model/src/buck.rs @@ -1623,7 +1623,7 @@ mod tests { } // TODO: enable when buck is properly set up on github project - // @fb-only + // @fb-only: const BUCK_TESTS_ENABLED: bool = true; const BUCK_TESTS_ENABLED: bool = false; // @oss-only #[track_caller] diff --git a/erlang_service/src/elp_lint.erl b/erlang_service/src/elp_lint.erl index 609e8c0712..31d2c70248 100644 --- a/erlang_service/src/elp_lint.erl +++ b/erlang_service/src/elp_lint.erl @@ -4372,14 +4372,14 @@ is_format_function(io, fwrite) -> true; is_format_function(io, format) -> true; is_format_function(io_lib, fwrite) -> true; is_format_function(io_lib, format) -> true; -% @fb-only -% @fb-only +% @fb-only: is_format_function(wa_log, send_if) -> true; +% @fb-only: is_format_function(wa_string, format) -> true; is_format_function(M, F) when is_atom(M), is_atom(F) -> false. %% check_format_1([Arg]) -> ok | {warn,Level,Format,[Arg]}. -% @fb-only -% @fb-only +% @fb-only[end= ]: format_args(wa_log, send_if, [_Level, _Meta, _Opts, Format, Args]) -> [Format, Args]; +% @fb-only[end= ]: format_args(wa_string, format, [Format, Args, _Options]) -> [Format, Args]; format_args(_M, _F, As) -> As. From b2ea905e7f748f4f1f5ca154d242ffccaadcf82a Mon Sep 17 00:00:00 2001 From: Open Source Bot Date: Tue, 16 Dec 2025 11:27:09 -0800 Subject: [PATCH 110/142] Updating hashes Summary: GitHub commits: https://github.com/facebook/buck2-prelude/commit/020284a6e874592d03667b4320932b3b772ef578 https://github.com/facebook/facebook-for-woocommerce/commit/261ff05851ee8ec2322780a957569d67af460c5c https://github.com/facebook/fb303/commit/acdd3baf0a4f2a5e4a192d238724a84e72ed4e02 https://github.com/facebook/fbthrift/commit/b435fe91fa611b9d111755bcbef00a989c4cfcf6 https://github.com/facebook/folly/commit/a75b46bbbc8c8660a9dc416661a5531a96a85772 https://github.com/facebook/mvfst/commit/427330df232ec9c2e791334a599acef3ef915f3c https://github.com/facebook/proxygen/commit/fd2a5c46a7704e515215fa4ffdd3ab9593f3cc4b https://github.com/facebook/wangle/commit/e95090adb4e12129b6a87f596070dfa10390695e https://github.com/facebookexperimental/edencommon/commit/c28a0922492b3f79997dff67de4f2df371f25bdb https://github.com/facebookexperimental/rust-shed/commit/fab1e58dfd3eb1e09411173b4e3f61e21d85733a https://github.com/facebookincubator/Facebook-Pixel-for-Wordpress/commit/848ddcedb294e05292c321d8393a5d39ef698ea8 https://github.com/facebookincubator/fizz/commit/270d39b2c34abd4916092e0ef1cec4d520f63a28 https://github.com/facebookincubator/llm_orchestrator/commit/65b11fc09069dbdaa9cd77eafc1ac4e4dd44ea1c https://github.com/whatsapp/eqwalizer/commit/0f514eb3893fa7070835c83ecb49fbea31b0426d Reviewed By: pranavcivi fbshipit-source-id: 0c3453ad24a874db08fbc66cc4945f70c64d25d0 --- eqwalizer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eqwalizer b/eqwalizer index a835ce03b0..0f514eb389 160000 --- a/eqwalizer +++ b/eqwalizer @@ -1 +1 @@ -Subproject commit a835ce03b0308a9869af964e35a24466b49cda51 +Subproject commit 0f514eb3893fa7070835c83ecb49fbea31b0426d From d4e14636b4570e9e06f91e29873ba6dcef18bd2a Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Tue, 16 Dec 2025 15:30:53 -0800 Subject: [PATCH 111/142] Do not run selected linters on tests by default Differential Revision: D89302965 fbshipit-source-id: 9b5c1d3c6402a6fea9f3fd5a6d93c2e49acba51e --- crates/ide/src/diagnostics/no_error_logger.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/ide/src/diagnostics/no_error_logger.rs b/crates/ide/src/diagnostics/no_error_logger.rs index d587d3f3c7..46f3d15e3e 100644 --- a/crates/ide/src/diagnostics/no_error_logger.rs +++ b/crates/ide/src/diagnostics/no_error_logger.rs @@ -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 { From 37b7c5e28e4be59eefa04ec51e81359e3dcbf9b2 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Wed, 17 Dec 2025 01:18:32 -0800 Subject: [PATCH 112/142] Introduce top-level test directory Reviewed By: michalmuskala Differential Revision: D89272460 fbshipit-source-id: 91eb93d7df55a1ca64fe2b05e21097daca3b6d33 --- crates/elp/src/bin/main.rs | 32 +++++++++---------- .../test/xref/unavailable_type.stdout | 4 +-- crates/elp/tests/slow-tests/buck_tests.rs | 4 +-- crates/elp/tests/slow-tests/main.rs | 16 +++++----- crates/ide/src/diagnostics.rs | 2 +- crates/project_model/src/buck.rs | 22 ++++++------- .../test_projects}/.gitignore | 0 .../test_projects}/README.md | 0 test/test_projects/buck_bad_config/.elp.toml | 8 +++++ .../buck_bad_config/src/bad_app.erl | 0 test/test_projects/buck_tests/.elp.toml | 9 ++++++ .../test_projects}/buck_tests/TARGETS.v2_ | 0 .../buck_tests/test_elp/TARGETS.v2_ | 10 +++--- .../buck_tests/test_elp/include/test_elp.hrl | 0 .../buck_tests/test_elp/src/test_elp.app.src | 0 .../buck_tests/test_elp/src/test_elp.erl | 0 .../test_elp/test/test_elp_SUITE.erl | 0 .../handle_update_test1.json | 0 .../handle_update_test2.json | 0 .../test_elp_SUITE_data/untracked_header.hrl | 0 .../test_elp_SUITE_data/untracked_module.erl | 0 .../test_elp/test/test_elp_test_utils.erl | 0 .../test_elp/test/test_elp_test_utils.hrl | 0 .../test_elp_direct_dep/TARGETS.v2_ | 4 +-- .../include/test_elp_direct_dep.hrl | 0 .../src/test_elp_direct_dep.erl | 0 .../src/test_elp_direct_dep_private.hrl | 0 .../test_elp_flat_inside_target/TARGETS.v2_ | 0 .../test_elp_flat_inside_target.erl | 0 .../test_elp_flat_inside_target.hrl | 0 .../test_elp_flat_outside_target.erl | 0 .../test_elp_flat_outside_target.hrl | 0 .../test_elp_ignored/test_elp_ignored.erl | 0 .../include/test_elp_no_private_headers.hrl | 0 .../src/test_elp_no_private_headers.erl | 0 .../src/test_elp_no_headers.erl | 0 .../test_elp_transitive_dep/TARGETS.v2_ | 0 .../include/test_elp_transitive_dep.hrl | 0 .../src/test_elp_transitive_dep.erl | 0 .../src/test_elp_transitive_dep_private.hrl | 0 test/test_projects/buck_tests_2/.elp.toml | 12 +++++++ .../auto_gen_a/include/auto_gen_a.hrl | 0 .../auto_gen_a/out/pretend_generated.erl | 0 .../auto_gen/auto_gen_a/src/auto_gen_a.erl | 0 .../check_include/src/top_includer.erl | 0 .../include/include_with_bug.hrl | 0 .../include/top_includer.hrl | 0 .../include/separate_include.hrl | 0 .../generated/out/generated_header.hrl | 0 .../buck_tests_2/util/app_a/include/junk.hrl | 0 .../buck_tests_2/util/app_a/src/app_a.erl | 0 test/test_projects/codegen_test/.elp.toml | 8 +++++ .../test_projects}/codegen_test/README.md | 6 ++-- .../app_a/src/codegen_test.app.src | 0 .../codegen_test/app_a/src/example_usage.erl | 0 .../app_a/test/codegen_test_SUITE.erl | 0 .../generated/example_service_client.erl | 0 .../generated/example_service_types.erl | 0 .../generated/example_service_types.hrl | 0 .../templates/example_service_client.erl | 0 .../templates/example_service_types.erl | 0 .../templates/example_service_types.hrl | 0 .../custom_build_tool/.elp.toml | 0 .../apps/app_a/include/app_a.hrl | 0 .../apps/app_a/src/app_a.erl | 0 .../apps/app_b/src/app_b.erl | 0 test/test_projects/diagnostics/.elp.toml | 8 +++++ .../test_projects}/diagnostics/README.md | 0 .../diagnostics/app_a/extra/app_a.erl | 0 .../diagnostics/app_a/include/app_a.hrl | 0 .../app_a/include/broken_diagnostics.hrl | 0 .../diagnostics/app_a/include/diagnostics.hrl | 0 .../diagnostics/app_a/src/app_a.app.src | 0 .../diagnostics/app_a/src/app_a.erl | 0 .../app_a/src/broken_parse_trans.erl | 0 .../diagnostics/app_a/src/cascading.erl | 0 .../diagnostics/app_a/src/crlf.erl | 0 .../diagnostics/app_a/src/diagnostics.erl | 0 .../diagnostics/app_a/src/diagnostics.escript | 0 .../app_a/src/diagnostics_errors.escript | 0 .../app_a/src/diagnostics_warnings.escript | 0 .../src/erlang_diagnostics_errors_gen.erl | 0 .../diagnostics/app_a/src/file_attribute.erl | 0 .../diagnostics/app_a/src/lint_recursive.erl | 0 .../diagnostics/app_a/src/lints.erl | 0 .../app_a/src/otp27_docstrings.erl | 0 .../diagnostics/app_a/src/otp27_sigils.erl | 0 .../diagnostics/app_a/src/otp_7655.erl | 0 .../app_a/src/parse_error_a_cascade.erl | 0 .../diagnostics/app_a/src/suppressed.erl | 0 .../diagnostics/app_a/src/syntax.erl | 0 .../diagnostics/app_a/test/app_a_SUITE.erl | 0 .../diagnostics/erlang_ls.config | 0 .../test_projects}/diagnostics/rebar.config | 0 .../diagnostics/test_build_info.json | 0 test/test_projects/end_to_end/.elp.toml | 7 ++++ .../assist_examples/src/add_doc.erl | 0 .../src/assist_examples.app.src | 0 .../assist_examples/src/code_completion.erl | 0 .../assist_examples/src/extract_function.erl | 0 .../assist_examples/src/head_mismatch.erl | 0 .../configerator/source/whatsapp/my_caf.erl | 0 .../end_to_end/definitions/README.md | 0 .../definitions/src/definitions.app.src | 0 .../end_to_end/definitions/src/local_def.erl | 0 .../end_to_end/docs/src/docs.app.src | 0 .../end_to_end/docs/src/docs.erl | 0 .../end_to_end/erlang_ls.config | 0 .../end_to_end/fixtures/erlang-stacktrace.txt | 0 .../test_projects}/end_to_end/hover/README.md | 0 .../end_to_end/hover/src/doc_examples.erl | 0 .../end_to_end/hover/src/hover.app.src | 0 .../test_projects}/end_to_end/rebar.config | 0 .../end_to_end/single_errors/README.md | 0 .../single_errors/src/as_you_type.erl | 0 .../src/compiler_diagnostics.erl | 0 .../single_errors/src/single_errors.app.src | 0 .../single_errors/src/spec_mismatch.erl | 0 .../single_errors/src/spec_mismatch.erl.2 | 0 .../single_errors/src/types_on_hover.erl | 0 .../single_errors/src/unused_macro.erl | 0 .../eqwalizer/src/eqwalizer.app.src | 0 .../eqwalizer/src/eqwalizer_specs.erl | 0 .../test_projects/eqwalizer_callers/.elp.toml | 5 +++ .../eqwalizer_callers/.gitignore | 0 .../eqwalizer_callers/app_a/include/app_a.hrl | 0 .../eqwalizer_callers/app_a/src/app_a.app.src | 0 .../eqwalizer_callers/app_a/src/app_a.erl | 0 .../eqwalizer_callers/app_a/src/app_b.erl | 0 .../app_a/test/app_a_SUITE.erl | 0 .../eqwalizer_callers/rebar.config | 0 .../eqwalizer_ignore_modules/.elp.toml | 0 .../apps/app_a/include/app_a.hrl | 0 .../apps/app_a/src/another_module_a.erl | 0 .../apps/app_a/src/app_a.erl | 0 .../apps/app_b/src/app_b.erl | 0 test/test_projects/eqwalizer_tests/.elp.toml | 8 +++++ .../test_projects}/eqwalizer_tests/.gitignore | 0 .../eqwalizer_tests/.rebar.root | 0 .../check/include/my_header.hrl | 0 .../check/src/any_fun_type.erl | 0 .../eqwalizer_tests/check/src/apply_none.erl | 0 .../eqwalizer_tests/check/src/approx.erl | 0 .../eqwalizer_tests/check/src/as_pat.erl | 0 .../check/src/auto_imports.erl | 0 .../eqwalizer_tests/check/src/behave.erl | 0 .../eqwalizer_tests/check/src/binaries.erl | 0 .../eqwalizer_tests/check/src/booleans.erl | 0 .../check/src/callbacks1_pos.erl | 0 .../check/src/callbacks3_neg.erl | 0 .../check/src/callbacks4_neg.erl | 0 .../check/src/callbacks5_neg.erl | 0 .../check/src/callbacks6_neg.erl | 0 .../check/src/callbacks7_overload_pos.erl | 0 .../check/src/case_predicates.erl | 0 .../eqwalizer_tests/check/src/check.app.src | 0 .../check/src/compiler_macro.erl | 0 .../check/src/complex_maps.erl | 0 .../check/src/comprehensions.erl | 0 .../check/src/contravariant.erl | 0 .../eqwalizer_tests/check/src/custom.erl | 0 .../check/src/detached_specs1.erl | 0 .../check/src/detached_specs2.erl | 0 .../eqwalizer_tests/check/src/dyn_calls.erl | 0 .../check/src/dyn_remote_funs.erl | 0 .../check/src/dynamic_callbacks_1.erl | 0 .../check/src/dynamic_callbacks_2.erl | 0 .../check/src/dynamic_calls.erl | 0 .../check/src/dynamic_catch.erl | 0 .../eqwalizer_tests/check/src/dynamic_fun.erl | 0 .../check/src/dynamic_generics.erl | 0 .../check/src/dynamic_local_funs.erl | 0 .../check/src/dynamic_receive.erl | 0 .../check/src/dynamic_refine.erl | 0 .../check/src/dynamic_try_catch.erl | 0 .../eqwalizer_tests/check/src/elab_clause.erl | 0 .../check/src/error_messages.erl | 0 .../check/src/fancy_generics.erl | 0 .../eqwalizer_tests/check/src/format.erl | 0 .../eqwalizer_tests/check/src/fun_stats.erl | 0 .../eqwalizer_tests/check/src/fun_stats2.erl | 0 .../eqwalizer_tests/check/src/funs.erl | 0 .../eqwalizer_tests/check/src/funs2.erl | 0 .../check/src/funs_uncommon.erl | 0 .../check/src/generic_fun_application.erl | 0 .../check/src/generics_with_unions.erl | 0 .../check/src/gradual_bounded.erl | 0 .../check/src/gradual_complex_types.erl | 0 .../check/src/gradual_custom.erl | 0 .../check/src/gradual_lambdas.erl | 0 .../check/src/gradual_maybe.erl | 0 .../check/src/gradual_misc.erl | 0 .../check/src/gradual_overloaded.erl | 0 .../check/src/gradual_regression_01.erl | 0 .../check/src/gradual_untyped.erl | 0 .../check/src/guard_b_connections.erl | 0 .../eqwalizer_tests/check/src/guards.erl | 0 .../check/src/guards_logic.erl | 0 .../check/src/guards_simple.erl | 0 .../eqwalizer_tests/check/src/hints.erl | 0 .../eqwalizer_tests/check/src/index1.erl | 0 .../eqwalizer_tests/check/src/index2.erl | 0 .../eqwalizer_tests/check/src/iolists.erl | 0 .../eqwalizer_tests/check/src/kp_01.erl | 0 .../eqwalizer_tests/check/src/kp_02.erl | 0 .../eqwalizer_tests/check/src/lists_tests.erl | 0 .../eqwalizer_tests/check/src/misc.erl | 0 .../eqwalizer_tests/check/src/misc_lib.erl | 0 .../check/src/my_behaviour.erl | 0 .../check/src/my_gradual_behaviour.erl | 0 .../eqwalizer_tests/check/src/my_header.erl | 0 .../eqwalizer_tests/check/src/neg.erl | 0 .../check/src/nested1/misc_nested1.erl | 0 .../src/nested1/nested2/misc_nested2.erl | 0 .../eqwalizer_tests/check/src/nowarn.erl | 0 .../check/src/number_comparisons.erl | 0 .../eqwalizer_tests/check/src/numbers.erl | 0 .../eqwalizer_tests/check/src/opaque.erl | 0 .../eqwalizer_tests/check/src/other.erl | 0 .../eqwalizer_tests/check/src/otp28.erl | 0 .../eqwalizer_tests/check/src/otp_opaques.erl | 0 .../eqwalizer_tests/check/src/overloaded.erl | 0 .../check/src/overloaded_specs_union.erl | 0 .../check/src/parametricity.erl | 0 .../eqwalizer_tests/check/src/pats.erl | 0 .../eqwalizer_tests/check/src/pinned.erl | 0 .../eqwalizer_tests/check/src/pos.erl | 0 .../eqwalizer_tests/check/src/records.erl | 0 .../check/src/recursive_aliases.erl | 0 .../eqwalizer_tests/check/src/refine.erl | 0 .../eqwalizer_tests/check/src/scoping.erl | 0 .../eqwalizer_tests/check/src/skip.erl | 0 .../check/src/static_maybe.erl | 0 .../check/src/strict_complex_types.erl | 0 .../eqwalizer_tests/check/src/strict_fun.erl | 0 .../check/src/strict_receive.erl | 0 .../eqwalizer_tests/check/src/subtype_neg.erl | 0 .../eqwalizer_tests/check/src/subtype_pos.erl | 0 .../eqwalizer_tests/check/src/t_maps.erl | 0 .../check/src/tagged_tuples.erl | 0 .../eqwalizer_tests/check/src/test.erl | 0 .../eqwalizer_tests/check/src/tries.erl | 0 .../eqwalizer_tests/check/src/tuple_union.erl | 0 .../check/src/type_aliases.erl | 0 .../check/src/type_asserts.erl | 0 .../check/src/type_predicates.erl | 0 .../eqwalizer_tests/check/src/united_fun.erl | 0 .../eqwalizer_tests/check/src/unspecced.erl | 0 .../check/src/use_dynamic_gradual.erl | 0 .../check/src/use_dynamic_strict.erl | 0 .../eqwalizer_tests/check/src/vars1.erl | 0 .../eqwalizer_tests/check/src/vars2.erl | 0 .../check/test/check_SUITE.erl | 0 .../debug/include/debug_header.hrl | 0 .../eqwalizer_tests/debug/src/attributes.erl | 0 .../eqwalizer_tests/debug/src/debug.app.src | 0 .../debug/src/debug_header.erl | 0 .../eqwalizer_tests/debug/src/expand.erl | 0 .../eqwalizer_tests/debug/src/expr1.erl | 0 .../eqwalizer_tests/debug/src/expr2.erl | 0 .../eqwalizer_tests/debug/src/records_wip.erl | 0 .../eqwalizer_tests/debug/src/types1.erl | 0 .../eqwalizer_tests/debug/src/types2.erl | 0 .../eqwalizer_tests/debug/src/wip_maps.erl | 0 .../eqwalizer_tests/elm_core/src/basics.erl | 0 .../elm_core/src/elm_core.app.src | 0 .../eqwalizer_tests/elm_core/src/list.erl | 0 .../eqwalizer_tests/elm_core/src/map.erl | 0 .../eqwalizer_tests/elm_core/src/map_ffi.erl | 0 .../eqwalizer_tests/elm_core/src/maybe.erl | 0 .../eqwalizer_tests/elm_core/src/result.erl | 0 .../eqwalizer_tests/elm_core/src/tuple.erl | 0 .../eqwalizer/include/eqwalizer.hrl | 0 .../eqwalizer/src/eqwalizer.app.src | 0 .../eqwalizer/src/eqwalizer.erl | 0 .../eqwalizer/src/eqwalizer_specs.erl | 0 .../src/eqwalizer_build_info_prv.erl | 0 .../src/eqwalizer_rebar3.app.src | 0 .../eqwalizer_rebar3/src/eqwalizer_rebar3.erl | 0 .../eqwalizer_tests/eqwater/readme.md | 0 .../eqwater/src/deep_tuples.erl | 0 .../eqwater/src/eqwater.app.src | 0 .../eqwalizer_tests/eqwater/src/eqwater.erl | 0 .../eqwater/src/eqwater_aliases.erl | 0 .../eqwater/src/eqwater_generics.erl | 0 .../eqwater/src/eqwater_lists.erl | 0 .../eqwater/src/eqwater_maps.erl | 0 .../eqwater/src/eqwater_records.erl | 0 .../eqwater/src/eqwater_sound.erl | 0 .../eqwater/src/unlimited_refinement.erl | 0 .../fault_tolerance/.eqwalizer | 0 .../src/fault_tolerance.app.src | 0 .../fault_tolerance/src/fault_tolerance.erl | 0 .../eqwalizer_tests/options/src/bad_maps.erl | 0 .../options/src/dynamic_lambdas.erl | 0 .../options/src/options.app.src | 0 .../src/overloaded_specs_dynamic_result.erl | 0 .../options/src/redundant_guards.erl | 0 .../options/src/uncovered_clauses.erl | 0 .../eqwalizer_tests/rebar.config | 0 .../hierarchical_config/.elp.toml | 5 +++ .../hierarchical_config/.elp_lint.toml | 0 .../hierarchical_config/app_a/.elp_lint.toml | 0 .../app_a/src/app_a.app.src | 0 .../hierarchical_config/app_a/src/app_a.erl | 0 .../app_b/src/app_b.app.src | 0 .../hierarchical_config/app_b/src/app_b.erl | 0 .../hierarchical_config/rebar.config | 0 test/test_projects/in_place_tests/.elp.toml | 5 +++ .../test_projects}/in_place_tests/README.md | 0 .../in_place_tests/app_a/extra/app_a.erl | 0 .../in_place_tests/app_a/include/app_a.hrl | 0 .../app_a/include/broken_diagnostics.hrl | 0 .../app_a/include/diagnostics.hrl | 0 .../in_place_tests/app_a/src/app_a.app.src | 0 .../in_place_tests/app_a/src/app_a.erl | 0 .../in_place_tests/app_a/src/lints.erl | 0 .../in_place_tests/app_a/test/app_a_SUITE.erl | 0 .../in_place_tests/erlang_ls.config | 0 .../in_place_tests/rebar.config | 0 .../include_lib_dependency_test/.elp.toml | 5 +++ .../external_app/include/external_header.hrl | 0 .../external_app/src/external_app.app.src | 0 .../external_app/src/external_app.erl | 0 .../extra_app/include/extra_header.hrl | 0 .../extra_app/src/extra_app.app.src | 0 .../extra_app/src/extra_app.erl | 0 .../main_app/src/main_app.app.src | 0 .../main_app/src/main_app.erl | 0 .../normal_dep/include/assert.hrl | 0 .../normal_dep/src/normal_dep.app.src | 0 .../normal_dep/src/normal_dep.erl | 0 .../include_lib_dependency_test/rebar.config | 0 test/test_projects/linter/.elp.toml | 5 +++ .../test_projects}/linter/.elp_lint.toml | 0 .../test_projects}/linter/.gitignore | 0 .../linter/app_a/include/app_a.hrl | 0 .../linter/app_a/src/app_a.app.src | 0 .../test_projects}/linter/app_a/src/app_a.erl | 0 .../linter/app_a/src/app_a_edoc.erl | 0 .../linter/app_a/src/app_a_ssr.erl | 0 .../linter/app_a/src/app_a_unused_param.erl | 0 .../app_a/src/custom_function_matches.erl | 0 .../app_a/src/expression_updates_literal.erl | 0 .../linter/app_a/src/spelling.erl | 0 .../linter/app_a/test/app_a_SUITE.erl | 0 .../linter/app_a/test/app_a_test_helpers.erl | 0 .../test/app_a_test_helpers_not_opted_in.erl | 0 .../test/app_a_unreachable_test_SUITE.erl | 0 .../app_a/test/app_test_helpers_no_errors.erl | 0 .../linter/app_b/src/app_b.app.src | 0 .../test_projects}/linter/app_b/src/app_b.erl | 0 .../linter/app_b/src/app_b_unused_param.erl | 0 .../test_projects}/linter/elp_lint_adhoc.toml | 0 .../elp_lint_custom_function_matches.toml | 0 .../linter/elp_lint_ssr_adhoc.toml | 0 .../linter/elp_lint_ssr_adhoc_parse_fail.toml | 0 .../test_projects}/linter/elp_lint_test1.toml | 0 .../test_projects}/linter/elp_lint_test2.toml | 0 .../linter/elp_lint_test_ignore.toml | 0 .../linter/elp_lint_warnings_as_errors.toml | 0 .../test_projects}/linter/rebar.config | 0 .../test_projects/linter_bad_config/.elp.toml | 5 +++ .../linter_bad_config/.elp_lint.toml | 0 .../linter_bad_config/.gitignore | 0 .../linter_bad_config/app_a/include/app_a.hrl | 0 .../linter_bad_config/app_a/src/app_a.app.src | 0 .../linter_bad_config/app_a/src/app_a.erl | 0 .../linter_bad_config/linter/.elp.toml | 5 +++ .../linter_bad_config/linter/.elp_lint.toml | 0 .../linter_bad_config/linter/.gitignore | 0 .../linter/app_a/include/app_a.hrl | 0 .../linter/app_a/src/app_a.app.src | 0 .../linter/app_a/src/app_a.erl | 0 .../linter/app_a/src/app_a_unused_param.erl | 0 .../linter/app_a/test/app_a_SUITE.erl | 0 .../linter/app_a/test/app_a_test_helpers.erl | 0 .../test/app_a_test_helpers_not_opted_in.erl | 0 .../app_a/test/app_test_helpers_no_errors.erl | 0 .../linter/app_b/src/app_b.app.src | 0 .../linter/app_b/src/app_b.erl | 0 .../linter/app_b/src/app_b_unused_param.erl | 0 .../linter_bad_config/linter/rebar.config | 0 .../linter_bad_config/rebar.config | 0 test/test_projects/parse_error/.elp.toml | 8 +++++ .../test_projects}/parse_error/.gitignore | 0 .../test_projects}/parse_error/.rebar.root | 0 .../eqwalizer/src/eqwalizer.app.src | 0 .../parse_error/eqwalizer/src/eqwalizer.erl | 0 .../eqwalizer/src/eqwalizer_specs.erl | 0 .../parse_error_a/src/parse_error_a.app.src | 0 .../parse_error_a/src/parse_error_a.erl | 0 .../parse_error_a/src/parse_error_a_bad.erl | 0 .../src/parse_error_a_reference_bad.erl | 0 .../src/parse_error_a_syntax_error.erl | 0 .../parse_error_a/src/parse_error_a_worst.erl | 0 .../test_projects}/parse_error/rebar.config | 0 test/test_projects/standard/.elp.toml | 5 +++ .../test_projects}/standard/.gitignore | 0 .../test_projects}/standard/.rebar.root | 0 .../test_projects}/standard/app_a/.eqwalizer | 0 .../standard/app_a/extra/app_a.erl | 0 .../standard/app_a/include/app_a.hrl | 0 .../standard/app_a/src/app_a.app.src | 0 .../standard/app_a/src/app_a.erl | 0 .../app_a/src/app_a_errors_generated.erl | 0 .../standard/app_a/src/app_a_fixme.erl | 0 .../standard/app_a/src/app_a_ignored.erl | 0 .../standard/app_a/src/app_a_lists.erl | 0 .../standard/app_a/src/app_a_mod2.erl | 0 .../standard/app_a/src/app_a_no_errors.erl | 0 .../app_a/src/app_a_no_errors_generated.erl | 0 .../app_a/src/app_a_no_errors_opted_in.erl | 0 .../standard/app_a/test/app_a_SUITE.erl | 0 .../app_a/test/app_a_test_helpers.erl | 0 .../test/app_a_test_helpers_not_opted_in.erl | 0 .../app_a/test/app_test_helpers_no_errors.erl | 0 .../standard/app_b/src/app_b.app.src | 0 .../standard/app_b/src/app_b.erl | 0 .../standard/eqwalizer/src/eqwalizer.app.src | 0 .../standard/eqwalizer/src/eqwalizer.erl | 0 .../eqwalizer/src/eqwalizer_specs.erl | 0 .../test_projects}/standard/erlang_ls.config | 0 .../test_projects}/standard/rebar.config | 0 test/test_projects/xref/.elp.toml | 5 +++ .../xref/app_a/src/unavailable_type.erl | 0 .../test_projects}/xref/app_b/src/app_b.erl | 0 .../test_projects}/xref/app_c/src/app_c.erl | 0 .../xref/elp_lint_unavailable_type.toml | 0 test_projects/buck_bad_config/.elp.toml | 8 ----- test_projects/buck_tests/.elp.toml | 9 ------ test_projects/buck_tests_2/.elp.toml | 12 ------- test_projects/codegen_test/.elp.toml | 8 ----- test_projects/diagnostics/.elp.toml | 8 ----- test_projects/end_to_end/.elp.toml | 7 ---- test_projects/eqwalizer_callers/.elp.toml | 5 --- test_projects/eqwalizer_tests/.elp.toml | 8 ----- test_projects/hierarchical_config/.elp.toml | 5 --- test_projects/in_place_tests/.elp.toml | 5 --- .../include_lib_dependency_test/.elp.toml | 5 --- test_projects/linter/.elp.toml | 5 --- test_projects/linter_bad_config/.elp.toml | 5 --- .../linter_bad_config/linter/.elp.toml | 5 --- test_projects/parse_error/.elp.toml | 8 ----- test_projects/standard/.elp.toml | 5 --- test_projects/xref/.elp.toml | 5 --- 446 files changed, 163 insertions(+), 163 deletions(-) rename {test_projects => test/test_projects}/.gitignore (100%) rename {test_projects => test/test_projects}/README.md (100%) create mode 100644 test/test_projects/buck_bad_config/.elp.toml rename {test_projects => test/test_projects}/buck_bad_config/src/bad_app.erl (100%) create mode 100644 test/test_projects/buck_tests/.elp.toml rename {test_projects => test/test_projects}/buck_tests/TARGETS.v2_ (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp/TARGETS.v2_ (76%) rename {test_projects => test/test_projects}/buck_tests/test_elp/include/test_elp.hrl (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp/src/test_elp.app.src (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp/src/test_elp.erl (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp/test/test_elp_SUITE.erl (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp/test/test_elp_SUITE_data/handle_update_test1.json (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp/test/test_elp_SUITE_data/handle_update_test2.json (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp/test/test_elp_SUITE_data/untracked_header.hrl (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp/test/test_elp_SUITE_data/untracked_module.erl (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp/test/test_elp_test_utils.erl (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp/test/test_elp_test_utils.hrl (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp_direct_dep/TARGETS.v2_ (61%) rename {test_projects => test/test_projects}/buck_tests/test_elp_direct_dep/include/test_elp_direct_dep.hrl (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp_direct_dep/src/test_elp_direct_dep.erl (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp_direct_dep/src/test_elp_direct_dep_private.hrl (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp_flat_inside_target/TARGETS.v2_ (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp_flat_inside_target/test_elp_flat_inside_target.erl (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp_flat_inside_target/test_elp_flat_inside_target.hrl (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp_flat_outside_target/test_elp_flat_outside_target.erl (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp_flat_outside_target/test_elp_flat_outside_target.hrl (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp_ignored/test_elp_ignored.erl (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp_no_private_headers/include/test_elp_no_private_headers.hrl (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp_no_private_headers/src/test_elp_no_private_headers.erl (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp_no_public_headers/src/test_elp_no_headers.erl (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp_transitive_dep/TARGETS.v2_ (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp_transitive_dep/include/test_elp_transitive_dep.hrl (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp_transitive_dep/src/test_elp_transitive_dep.erl (100%) rename {test_projects => test/test_projects}/buck_tests/test_elp_transitive_dep/src/test_elp_transitive_dep_private.hrl (100%) create mode 100644 test/test_projects/buck_tests_2/.elp.toml rename {test_projects => test/test_projects}/buck_tests_2/auto_gen/auto_gen_a/include/auto_gen_a.hrl (100%) rename {test_projects => test/test_projects}/buck_tests_2/auto_gen/auto_gen_a/out/pretend_generated.erl (100%) rename {test_projects => test/test_projects}/buck_tests_2/auto_gen/auto_gen_a/src/auto_gen_a.erl (100%) rename {test_projects => test/test_projects}/buck_tests_2/check_include/src/top_includer.erl (100%) rename {test_projects => test/test_projects}/buck_tests_2/check_include_separate_1/include/include_with_bug.hrl (100%) rename {test_projects => test/test_projects}/buck_tests_2/check_include_separate_1/include/top_includer.hrl (100%) rename {test_projects => test/test_projects}/buck_tests_2/check_include_separate_2/include/separate_include.hrl (100%) rename {test_projects => test/test_projects}/buck_tests_2/generated/out/generated_header.hrl (100%) rename {test_projects => test/test_projects}/buck_tests_2/util/app_a/include/junk.hrl (100%) rename {test_projects => test/test_projects}/buck_tests_2/util/app_a/src/app_a.erl (100%) create mode 100644 test/test_projects/codegen_test/.elp.toml rename {test_projects => test/test_projects}/codegen_test/README.md (93%) rename {test_projects => test/test_projects}/codegen_test/app_a/src/codegen_test.app.src (100%) rename {test_projects => test/test_projects}/codegen_test/app_a/src/example_usage.erl (100%) rename {test_projects => test/test_projects}/codegen_test/app_a/test/codegen_test_SUITE.erl (100%) rename {test_projects => test/test_projects}/codegen_test/generated/example_service_client.erl (100%) rename {test_projects => test/test_projects}/codegen_test/generated/example_service_types.erl (100%) rename {test_projects => test/test_projects}/codegen_test/generated/example_service_types.hrl (100%) rename {test_projects => test/test_projects}/codegen_test/templates/example_service_client.erl (100%) rename {test_projects => test/test_projects}/codegen_test/templates/example_service_types.erl (100%) rename {test_projects => test/test_projects}/codegen_test/templates/example_service_types.hrl (100%) rename {test_projects => test/test_projects}/custom_build_tool/.elp.toml (100%) rename {test_projects => test/test_projects}/custom_build_tool/apps/app_a/include/app_a.hrl (100%) rename {test_projects => test/test_projects}/custom_build_tool/apps/app_a/src/app_a.erl (100%) rename {test_projects => test/test_projects}/custom_build_tool/apps/app_b/src/app_b.erl (100%) create mode 100644 test/test_projects/diagnostics/.elp.toml rename {test_projects => test/test_projects}/diagnostics/README.md (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/extra/app_a.erl (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/include/app_a.hrl (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/include/broken_diagnostics.hrl (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/include/diagnostics.hrl (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/src/app_a.app.src (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/src/app_a.erl (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/src/broken_parse_trans.erl (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/src/cascading.erl (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/src/crlf.erl (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/src/diagnostics.erl (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/src/diagnostics.escript (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/src/diagnostics_errors.escript (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/src/diagnostics_warnings.escript (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/src/erlang_diagnostics_errors_gen.erl (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/src/file_attribute.erl (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/src/lint_recursive.erl (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/src/lints.erl (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/src/otp27_docstrings.erl (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/src/otp27_sigils.erl (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/src/otp_7655.erl (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/src/parse_error_a_cascade.erl (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/src/suppressed.erl (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/src/syntax.erl (100%) rename {test_projects => test/test_projects}/diagnostics/app_a/test/app_a_SUITE.erl (100%) rename {test_projects => test/test_projects}/diagnostics/erlang_ls.config (100%) rename {test_projects => test/test_projects}/diagnostics/rebar.config (100%) rename {test_projects => test/test_projects}/diagnostics/test_build_info.json (100%) create mode 100644 test/test_projects/end_to_end/.elp.toml rename {test_projects => test/test_projects}/end_to_end/assist_examples/src/add_doc.erl (100%) rename {test_projects => test/test_projects}/end_to_end/assist_examples/src/assist_examples.app.src (100%) rename {test_projects => test/test_projects}/end_to_end/assist_examples/src/code_completion.erl (100%) rename {test_projects => test/test_projects}/end_to_end/assist_examples/src/extract_function.erl (100%) rename {test_projects => test/test_projects}/end_to_end/assist_examples/src/head_mismatch.erl (100%) rename {test_projects => test/test_projects}/end_to_end/caf/configerator/source/whatsapp/my_caf.erl (100%) rename {test_projects => test/test_projects}/end_to_end/definitions/README.md (100%) rename {test_projects => test/test_projects}/end_to_end/definitions/src/definitions.app.src (100%) rename {test_projects => test/test_projects}/end_to_end/definitions/src/local_def.erl (100%) rename {test_projects => test/test_projects}/end_to_end/docs/src/docs.app.src (100%) rename {test_projects => test/test_projects}/end_to_end/docs/src/docs.erl (100%) rename {test_projects => test/test_projects}/end_to_end/erlang_ls.config (100%) rename {test_projects => test/test_projects}/end_to_end/fixtures/erlang-stacktrace.txt (100%) rename {test_projects => test/test_projects}/end_to_end/hover/README.md (100%) rename {test_projects => test/test_projects}/end_to_end/hover/src/doc_examples.erl (100%) rename {test_projects => test/test_projects}/end_to_end/hover/src/hover.app.src (100%) rename {test_projects => test/test_projects}/end_to_end/rebar.config (100%) rename {test_projects => test/test_projects}/end_to_end/single_errors/README.md (100%) rename {test_projects => test/test_projects}/end_to_end/single_errors/src/as_you_type.erl (100%) rename {test_projects => test/test_projects}/end_to_end/single_errors/src/compiler_diagnostics.erl (100%) rename {test_projects => test/test_projects}/end_to_end/single_errors/src/single_errors.app.src (100%) rename {test_projects => test/test_projects}/end_to_end/single_errors/src/spec_mismatch.erl (100%) rename {test_projects => test/test_projects}/end_to_end/single_errors/src/spec_mismatch.erl.2 (100%) rename {test_projects => test/test_projects}/end_to_end/single_errors/src/types_on_hover.erl (100%) rename {test_projects => test/test_projects}/end_to_end/single_errors/src/unused_macro.erl (100%) rename {test_projects => test/test_projects}/eqwalizer/src/eqwalizer.app.src (100%) rename {test_projects => test/test_projects}/eqwalizer/src/eqwalizer_specs.erl (100%) create mode 100644 test/test_projects/eqwalizer_callers/.elp.toml rename {test_projects => test/test_projects}/eqwalizer_callers/.gitignore (100%) rename {test_projects => test/test_projects}/eqwalizer_callers/app_a/include/app_a.hrl (100%) rename {test_projects => test/test_projects}/eqwalizer_callers/app_a/src/app_a.app.src (100%) rename {test_projects => test/test_projects}/eqwalizer_callers/app_a/src/app_a.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_callers/app_a/src/app_b.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_callers/app_a/test/app_a_SUITE.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_callers/rebar.config (100%) rename {test_projects => test/test_projects}/eqwalizer_ignore_modules/.elp.toml (100%) rename {test_projects => test/test_projects}/eqwalizer_ignore_modules/apps/app_a/include/app_a.hrl (100%) rename {test_projects => test/test_projects}/eqwalizer_ignore_modules/apps/app_a/src/another_module_a.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_ignore_modules/apps/app_a/src/app_a.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_ignore_modules/apps/app_b/src/app_b.erl (100%) create mode 100644 test/test_projects/eqwalizer_tests/.elp.toml rename {test_projects => test/test_projects}/eqwalizer_tests/.gitignore (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/.rebar.root (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/include/my_header.hrl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/any_fun_type.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/apply_none.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/approx.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/as_pat.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/auto_imports.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/behave.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/binaries.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/booleans.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/callbacks1_pos.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/callbacks3_neg.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/callbacks4_neg.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/callbacks5_neg.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/callbacks6_neg.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/callbacks7_overload_pos.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/case_predicates.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/check.app.src (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/compiler_macro.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/complex_maps.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/comprehensions.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/contravariant.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/custom.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/detached_specs1.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/detached_specs2.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/dyn_calls.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/dyn_remote_funs.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/dynamic_callbacks_1.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/dynamic_callbacks_2.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/dynamic_calls.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/dynamic_catch.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/dynamic_fun.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/dynamic_generics.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/dynamic_local_funs.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/dynamic_receive.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/dynamic_refine.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/dynamic_try_catch.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/elab_clause.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/error_messages.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/fancy_generics.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/format.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/fun_stats.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/fun_stats2.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/funs.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/funs2.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/funs_uncommon.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/generic_fun_application.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/generics_with_unions.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/gradual_bounded.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/gradual_complex_types.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/gradual_custom.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/gradual_lambdas.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/gradual_maybe.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/gradual_misc.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/gradual_overloaded.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/gradual_regression_01.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/gradual_untyped.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/guard_b_connections.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/guards.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/guards_logic.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/guards_simple.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/hints.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/index1.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/index2.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/iolists.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/kp_01.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/kp_02.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/lists_tests.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/misc.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/misc_lib.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/my_behaviour.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/my_gradual_behaviour.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/my_header.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/neg.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/nested1/misc_nested1.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/nested1/nested2/misc_nested2.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/nowarn.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/number_comparisons.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/numbers.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/opaque.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/other.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/otp28.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/otp_opaques.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/overloaded.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/overloaded_specs_union.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/parametricity.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/pats.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/pinned.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/pos.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/records.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/recursive_aliases.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/refine.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/scoping.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/skip.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/static_maybe.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/strict_complex_types.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/strict_fun.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/strict_receive.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/subtype_neg.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/subtype_pos.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/t_maps.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/tagged_tuples.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/test.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/tries.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/tuple_union.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/type_aliases.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/type_asserts.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/type_predicates.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/united_fun.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/unspecced.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/use_dynamic_gradual.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/use_dynamic_strict.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/vars1.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/src/vars2.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/check/test/check_SUITE.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/debug/include/debug_header.hrl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/debug/src/attributes.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/debug/src/debug.app.src (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/debug/src/debug_header.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/debug/src/expand.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/debug/src/expr1.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/debug/src/expr2.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/debug/src/records_wip.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/debug/src/types1.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/debug/src/types2.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/debug/src/wip_maps.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/elm_core/src/basics.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/elm_core/src/elm_core.app.src (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/elm_core/src/list.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/elm_core/src/map.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/elm_core/src/map_ffi.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/elm_core/src/maybe.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/elm_core/src/result.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/elm_core/src/tuple.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/eqwalizer/include/eqwalizer.hrl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/eqwalizer/src/eqwalizer.app.src (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/eqwalizer/src/eqwalizer.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/eqwalizer/src/eqwalizer_specs.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/eqwalizer_rebar3/src/eqwalizer_build_info_prv.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/eqwalizer_rebar3/src/eqwalizer_rebar3.app.src (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/eqwalizer_rebar3/src/eqwalizer_rebar3.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/eqwater/readme.md (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/eqwater/src/deep_tuples.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/eqwater/src/eqwater.app.src (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/eqwater/src/eqwater.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/eqwater/src/eqwater_aliases.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/eqwater/src/eqwater_generics.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/eqwater/src/eqwater_lists.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/eqwater/src/eqwater_maps.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/eqwater/src/eqwater_records.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/eqwater/src/eqwater_sound.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/eqwater/src/unlimited_refinement.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/fault_tolerance/.eqwalizer (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/fault_tolerance/src/fault_tolerance.app.src (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/fault_tolerance/src/fault_tolerance.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/options/src/bad_maps.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/options/src/dynamic_lambdas.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/options/src/options.app.src (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/options/src/overloaded_specs_dynamic_result.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/options/src/redundant_guards.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/options/src/uncovered_clauses.erl (100%) rename {test_projects => test/test_projects}/eqwalizer_tests/rebar.config (100%) create mode 100644 test/test_projects/hierarchical_config/.elp.toml rename {test_projects => test/test_projects}/hierarchical_config/.elp_lint.toml (100%) rename {test_projects => test/test_projects}/hierarchical_config/app_a/.elp_lint.toml (100%) rename {test_projects => test/test_projects}/hierarchical_config/app_a/src/app_a.app.src (100%) rename {test_projects => test/test_projects}/hierarchical_config/app_a/src/app_a.erl (100%) rename {test_projects => test/test_projects}/hierarchical_config/app_b/src/app_b.app.src (100%) rename {test_projects => test/test_projects}/hierarchical_config/app_b/src/app_b.erl (100%) rename {test_projects => test/test_projects}/hierarchical_config/rebar.config (100%) create mode 100644 test/test_projects/in_place_tests/.elp.toml rename {test_projects => test/test_projects}/in_place_tests/README.md (100%) rename {test_projects => test/test_projects}/in_place_tests/app_a/extra/app_a.erl (100%) rename {test_projects => test/test_projects}/in_place_tests/app_a/include/app_a.hrl (100%) rename {test_projects => test/test_projects}/in_place_tests/app_a/include/broken_diagnostics.hrl (100%) rename {test_projects => test/test_projects}/in_place_tests/app_a/include/diagnostics.hrl (100%) rename {test_projects => test/test_projects}/in_place_tests/app_a/src/app_a.app.src (100%) rename {test_projects => test/test_projects}/in_place_tests/app_a/src/app_a.erl (100%) rename {test_projects => test/test_projects}/in_place_tests/app_a/src/lints.erl (100%) rename {test_projects => test/test_projects}/in_place_tests/app_a/test/app_a_SUITE.erl (100%) rename {test_projects => test/test_projects}/in_place_tests/erlang_ls.config (100%) rename {test_projects => test/test_projects}/in_place_tests/rebar.config (100%) create mode 100644 test/test_projects/include_lib_dependency_test/.elp.toml rename {test_projects => test/test_projects}/include_lib_dependency_test/external_app/include/external_header.hrl (100%) rename {test_projects => test/test_projects}/include_lib_dependency_test/external_app/src/external_app.app.src (100%) rename {test_projects => test/test_projects}/include_lib_dependency_test/external_app/src/external_app.erl (100%) rename {test_projects => test/test_projects}/include_lib_dependency_test/extra_app/include/extra_header.hrl (100%) rename {test_projects => test/test_projects}/include_lib_dependency_test/extra_app/src/extra_app.app.src (100%) rename {test_projects => test/test_projects}/include_lib_dependency_test/extra_app/src/extra_app.erl (100%) rename {test_projects => test/test_projects}/include_lib_dependency_test/main_app/src/main_app.app.src (100%) rename {test_projects => test/test_projects}/include_lib_dependency_test/main_app/src/main_app.erl (100%) rename {test_projects => test/test_projects}/include_lib_dependency_test/normal_dep/include/assert.hrl (100%) rename {test_projects => test/test_projects}/include_lib_dependency_test/normal_dep/src/normal_dep.app.src (100%) rename {test_projects => test/test_projects}/include_lib_dependency_test/normal_dep/src/normal_dep.erl (100%) rename {test_projects => test/test_projects}/include_lib_dependency_test/rebar.config (100%) create mode 100644 test/test_projects/linter/.elp.toml rename {test_projects => test/test_projects}/linter/.elp_lint.toml (100%) rename {test_projects => test/test_projects}/linter/.gitignore (100%) rename {test_projects => test/test_projects}/linter/app_a/include/app_a.hrl (100%) rename {test_projects => test/test_projects}/linter/app_a/src/app_a.app.src (100%) rename {test_projects => test/test_projects}/linter/app_a/src/app_a.erl (100%) rename {test_projects => test/test_projects}/linter/app_a/src/app_a_edoc.erl (100%) rename {test_projects => test/test_projects}/linter/app_a/src/app_a_ssr.erl (100%) rename {test_projects => test/test_projects}/linter/app_a/src/app_a_unused_param.erl (100%) rename {test_projects => test/test_projects}/linter/app_a/src/custom_function_matches.erl (100%) rename {test_projects => test/test_projects}/linter/app_a/src/expression_updates_literal.erl (100%) rename {test_projects => test/test_projects}/linter/app_a/src/spelling.erl (100%) rename {test_projects => test/test_projects}/linter/app_a/test/app_a_SUITE.erl (100%) rename {test_projects => test/test_projects}/linter/app_a/test/app_a_test_helpers.erl (100%) rename {test_projects => test/test_projects}/linter/app_a/test/app_a_test_helpers_not_opted_in.erl (100%) rename {test_projects => test/test_projects}/linter/app_a/test/app_a_unreachable_test_SUITE.erl (100%) rename {test_projects => test/test_projects}/linter/app_a/test/app_test_helpers_no_errors.erl (100%) rename {test_projects => test/test_projects}/linter/app_b/src/app_b.app.src (100%) rename {test_projects => test/test_projects}/linter/app_b/src/app_b.erl (100%) rename {test_projects => test/test_projects}/linter/app_b/src/app_b_unused_param.erl (100%) rename {test_projects => test/test_projects}/linter/elp_lint_adhoc.toml (100%) rename {test_projects => test/test_projects}/linter/elp_lint_custom_function_matches.toml (100%) rename {test_projects => test/test_projects}/linter/elp_lint_ssr_adhoc.toml (100%) rename {test_projects => test/test_projects}/linter/elp_lint_ssr_adhoc_parse_fail.toml (100%) rename {test_projects => test/test_projects}/linter/elp_lint_test1.toml (100%) rename {test_projects => test/test_projects}/linter/elp_lint_test2.toml (100%) rename {test_projects => test/test_projects}/linter/elp_lint_test_ignore.toml (100%) rename {test_projects => test/test_projects}/linter/elp_lint_warnings_as_errors.toml (100%) rename {test_projects => test/test_projects}/linter/rebar.config (100%) create mode 100644 test/test_projects/linter_bad_config/.elp.toml rename {test_projects => test/test_projects}/linter_bad_config/.elp_lint.toml (100%) rename {test_projects => test/test_projects}/linter_bad_config/.gitignore (100%) rename {test_projects => test/test_projects}/linter_bad_config/app_a/include/app_a.hrl (100%) rename {test_projects => test/test_projects}/linter_bad_config/app_a/src/app_a.app.src (100%) rename {test_projects => test/test_projects}/linter_bad_config/app_a/src/app_a.erl (100%) create mode 100644 test/test_projects/linter_bad_config/linter/.elp.toml rename {test_projects => test/test_projects}/linter_bad_config/linter/.elp_lint.toml (100%) rename {test_projects => test/test_projects}/linter_bad_config/linter/.gitignore (100%) rename {test_projects => test/test_projects}/linter_bad_config/linter/app_a/include/app_a.hrl (100%) rename {test_projects => test/test_projects}/linter_bad_config/linter/app_a/src/app_a.app.src (100%) rename {test_projects => test/test_projects}/linter_bad_config/linter/app_a/src/app_a.erl (100%) rename {test_projects => test/test_projects}/linter_bad_config/linter/app_a/src/app_a_unused_param.erl (100%) rename {test_projects => test/test_projects}/linter_bad_config/linter/app_a/test/app_a_SUITE.erl (100%) rename {test_projects => test/test_projects}/linter_bad_config/linter/app_a/test/app_a_test_helpers.erl (100%) rename {test_projects => test/test_projects}/linter_bad_config/linter/app_a/test/app_a_test_helpers_not_opted_in.erl (100%) rename {test_projects => test/test_projects}/linter_bad_config/linter/app_a/test/app_test_helpers_no_errors.erl (100%) rename {test_projects => test/test_projects}/linter_bad_config/linter/app_b/src/app_b.app.src (100%) rename {test_projects => test/test_projects}/linter_bad_config/linter/app_b/src/app_b.erl (100%) rename {test_projects => test/test_projects}/linter_bad_config/linter/app_b/src/app_b_unused_param.erl (100%) rename {test_projects => test/test_projects}/linter_bad_config/linter/rebar.config (100%) rename {test_projects => test/test_projects}/linter_bad_config/rebar.config (100%) create mode 100644 test/test_projects/parse_error/.elp.toml rename {test_projects => test/test_projects}/parse_error/.gitignore (100%) rename {test_projects => test/test_projects}/parse_error/.rebar.root (100%) rename {test_projects => test/test_projects}/parse_error/eqwalizer/src/eqwalizer.app.src (100%) rename {test_projects => test/test_projects}/parse_error/eqwalizer/src/eqwalizer.erl (100%) rename {test_projects => test/test_projects}/parse_error/eqwalizer/src/eqwalizer_specs.erl (100%) rename {test_projects => test/test_projects}/parse_error/parse_error_a/src/parse_error_a.app.src (100%) rename {test_projects => test/test_projects}/parse_error/parse_error_a/src/parse_error_a.erl (100%) rename {test_projects => test/test_projects}/parse_error/parse_error_a/src/parse_error_a_bad.erl (100%) rename {test_projects => test/test_projects}/parse_error/parse_error_a/src/parse_error_a_reference_bad.erl (100%) rename {test_projects => test/test_projects}/parse_error/parse_error_a/src/parse_error_a_syntax_error.erl (100%) rename {test_projects => test/test_projects}/parse_error/parse_error_a/src/parse_error_a_worst.erl (100%) rename {test_projects => test/test_projects}/parse_error/rebar.config (100%) create mode 100644 test/test_projects/standard/.elp.toml rename {test_projects => test/test_projects}/standard/.gitignore (100%) rename {test_projects => test/test_projects}/standard/.rebar.root (100%) rename {test_projects => test/test_projects}/standard/app_a/.eqwalizer (100%) rename {test_projects => test/test_projects}/standard/app_a/extra/app_a.erl (100%) rename {test_projects => test/test_projects}/standard/app_a/include/app_a.hrl (100%) rename {test_projects => test/test_projects}/standard/app_a/src/app_a.app.src (100%) rename {test_projects => test/test_projects}/standard/app_a/src/app_a.erl (100%) rename {test_projects => test/test_projects}/standard/app_a/src/app_a_errors_generated.erl (100%) rename {test_projects => test/test_projects}/standard/app_a/src/app_a_fixme.erl (100%) rename {test_projects => test/test_projects}/standard/app_a/src/app_a_ignored.erl (100%) rename {test_projects => test/test_projects}/standard/app_a/src/app_a_lists.erl (100%) rename {test_projects => test/test_projects}/standard/app_a/src/app_a_mod2.erl (100%) rename {test_projects => test/test_projects}/standard/app_a/src/app_a_no_errors.erl (100%) rename {test_projects => test/test_projects}/standard/app_a/src/app_a_no_errors_generated.erl (100%) rename {test_projects => test/test_projects}/standard/app_a/src/app_a_no_errors_opted_in.erl (100%) rename {test_projects => test/test_projects}/standard/app_a/test/app_a_SUITE.erl (100%) rename {test_projects => test/test_projects}/standard/app_a/test/app_a_test_helpers.erl (100%) rename {test_projects => test/test_projects}/standard/app_a/test/app_a_test_helpers_not_opted_in.erl (100%) rename {test_projects => test/test_projects}/standard/app_a/test/app_test_helpers_no_errors.erl (100%) rename {test_projects => test/test_projects}/standard/app_b/src/app_b.app.src (100%) rename {test_projects => test/test_projects}/standard/app_b/src/app_b.erl (100%) rename {test_projects => test/test_projects}/standard/eqwalizer/src/eqwalizer.app.src (100%) rename {test_projects => test/test_projects}/standard/eqwalizer/src/eqwalizer.erl (100%) rename {test_projects => test/test_projects}/standard/eqwalizer/src/eqwalizer_specs.erl (100%) rename {test_projects => test/test_projects}/standard/erlang_ls.config (100%) rename {test_projects => test/test_projects}/standard/rebar.config (100%) create mode 100644 test/test_projects/xref/.elp.toml rename {test_projects => test/test_projects}/xref/app_a/src/unavailable_type.erl (100%) rename {test_projects => test/test_projects}/xref/app_b/src/app_b.erl (100%) rename {test_projects => test/test_projects}/xref/app_c/src/app_c.erl (100%) rename {test_projects => test/test_projects}/xref/elp_lint_unavailable_type.toml (100%) delete mode 100644 test_projects/buck_bad_config/.elp.toml delete mode 100644 test_projects/buck_tests/.elp.toml delete mode 100644 test_projects/buck_tests_2/.elp.toml delete mode 100644 test_projects/codegen_test/.elp.toml delete mode 100644 test_projects/diagnostics/.elp.toml delete mode 100644 test_projects/end_to_end/.elp.toml delete mode 100644 test_projects/eqwalizer_callers/.elp.toml delete mode 100644 test_projects/eqwalizer_tests/.elp.toml delete mode 100644 test_projects/hierarchical_config/.elp.toml delete mode 100644 test_projects/in_place_tests/.elp.toml delete mode 100644 test_projects/include_lib_dependency_test/.elp.toml delete mode 100644 test_projects/linter/.elp.toml delete mode 100644 test_projects/linter_bad_config/.elp.toml delete mode 100644 test_projects/linter_bad_config/linter/.elp.toml delete mode 100644 test_projects/parse_error/.elp.toml delete mode 100644 test_projects/standard/.elp.toml delete mode 100644 test_projects/xref/.elp.toml diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index ac2fc901fa..38ef3c3880 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -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 { // 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", @@ -606,7 +606,7 @@ mod tests { simple_snapshot( args_vec![ "eqwalize-target", - "//whatsapp/elp/test_projects/standard:app_a", + "//whatsapp/elp/test/test_projects/standard:app_a", ], "standard", expect_file!("../resources/test/standard/eqwalize_target_diagnostics.pretty"), @@ -1444,7 +1444,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"), @@ -1456,7 +1456,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"); @@ -1472,7 +1472,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"), @@ -1498,7 +1498,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", @@ -1529,7 +1529,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"), @@ -1573,7 +1573,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"); @@ -1625,7 +1625,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"), @@ -1943,7 +1943,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"), @@ -1958,7 +1958,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" ], @@ -1975,7 +1975,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" ], @@ -1992,7 +1992,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"), @@ -2007,7 +2007,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"), @@ -3099,7 +3099,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 { diff --git a/crates/elp/src/resources/test/xref/unavailable_type.stdout b/crates/elp/src/resources/test/xref/unavailable_type.stdout index 7064c359ff..b6338343c0 100644 --- a/crates/elp/src/resources/test/xref/unavailable_type.stdout +++ b/crates/elp/src/resources/test/xref/unavailable_type.stdout @@ -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 'fbcode//whatsapp/elp/test/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/test_projects/xref:app_a'). diff --git a/crates/elp/tests/slow-tests/buck_tests.rs b/crates/elp/tests/slow-tests/buck_tests.rs index 76540e2515..91f2b234d9 100644 --- a/crates/elp/tests/slow-tests/buck_tests.rs +++ b/crates/elp/tests/slow-tests/buck_tests.rs @@ -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(); @@ -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) = diff --git a/crates/elp/tests/slow-tests/main.rs b/crates/elp/tests/slow-tests/main.rs index 60167593fd..e256ce2c62 100644 --- a/crates/elp/tests/slow-tests/main.rs +++ b/crates/elp/tests/slow-tests/main.rs @@ -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()); diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 093ba35986..733347bfcb 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -3564,7 +3564,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). diff --git a/crates/project_model/src/buck.rs b/crates/project_model/src/buck.rs index 3658a6d783..bb3ec38b5f 100644 --- a/crates/project_model/src/buck.rs +++ b/crates/project_model/src/buck.rs @@ -1655,7 +1655,7 @@ mod tests { .arg("--") .args(generated_args) .arg("--included_targets") - .arg("fbcode//whatsapp/elp/test_projects/buck_tests_2/auto_gen/...") + .arg("fbcode//whatsapp/elp/test/test_projects/buck_tests_2/auto_gen/...") .output() .unwrap(); if !output.status.success() { @@ -1679,15 +1679,15 @@ mod tests { false, expect![[r#" { - "fbcode//whatsapp/elp/test_projects/buck_tests_2/auto_gen/auto_gen_a:auto_gen_a": { + "fbcode//whatsapp/elp/test/test_projects/buck_tests_2/auto_gen/auto_gen_a:auto_gen_a": { "name": "auto_gen_a", "app_name": null, "suite": null, "srcs": [ - "/[..]/test_projects/buck_tests_2/auto_gen/auto_gen_a/src/auto_gen_a.erl" + "/[..]/test/test_projects/buck_tests_2/auto_gen/auto_gen_a/src/auto_gen_a.erl" ], "includes": [ - "/[..]/test_projects/buck_tests_2/auto_gen/auto_gen_a/include" + "/[..]/test/test_projects/buck_tests_2/auto_gen/auto_gen_a/include" ], "labels": [ "user_application" @@ -1697,7 +1697,7 @@ mod tests { "included_apps": [], "origin": "app" }, - "fbcode//whatsapp/elp/test_projects/buck_tests_2/auto_gen/auto_gen_a:generated_srcs": { + "fbcode//whatsapp/elp/test/test_projects/buck_tests_2/auto_gen/auto_gen_a:generated_srcs": { "name": "generated_srcs", "app_name": null, "suite": null, @@ -1842,21 +1842,21 @@ mod tests { fn build_info_buck_bxl_generated_query() { if BUCK_TESTS_ENABLED { // Note that there is now a value for `srcs` in the - // "fbcode//whatsapp/elp/test_projects/buck_tests_2/auto_gen/auto_gen_a:generated_srcs" + // "fbcode//whatsapp/elp/test/test_projects/buck_tests_2/auto_gen/auto_gen_a:generated_srcs" // target check_buck_bxl_query( true, expect![[r#" { - "fbcode//whatsapp/elp/test_projects/buck_tests_2/auto_gen/auto_gen_a:auto_gen_a": { + "fbcode//whatsapp/elp/test/test_projects/buck_tests_2/auto_gen/auto_gen_a:auto_gen_a": { "name": "auto_gen_a", "app_name": null, "suite": null, "srcs": [ - "/[..]/test_projects/buck_tests_2/auto_gen/auto_gen_a/src/auto_gen_a.erl" + "/[..]/test/test_projects/buck_tests_2/auto_gen/auto_gen_a/src/auto_gen_a.erl" ], "includes": [ - "/[..]/test_projects/buck_tests_2/auto_gen/auto_gen_a/include" + "/[..]/test/test_projects/buck_tests_2/auto_gen/auto_gen_a/include" ], "labels": [ "user_application" @@ -1866,12 +1866,12 @@ mod tests { "included_apps": [], "origin": "app" }, - "fbcode//whatsapp/elp/test_projects/buck_tests_2/auto_gen/auto_gen_a:generated_srcs": { + "fbcode//whatsapp/elp/test/test_projects/buck_tests_2/auto_gen/auto_gen_a:generated_srcs": { "name": "generated_srcs", "app_name": null, "suite": null, "srcs": [ - "/[..]/test_projects/buck_tests_2/auto_gen/auto_gen_a/out/pretend_generated.erl" + "/[..]/test/test_projects/buck_tests_2/auto_gen/auto_gen_a/out/pretend_generated.erl" ], "includes": [], "labels": [ diff --git a/test_projects/.gitignore b/test/test_projects/.gitignore similarity index 100% rename from test_projects/.gitignore rename to test/test_projects/.gitignore diff --git a/test_projects/README.md b/test/test_projects/README.md similarity index 100% rename from test_projects/README.md rename to test/test_projects/README.md diff --git a/test/test_projects/buck_bad_config/.elp.toml b/test/test_projects/buck_bad_config/.elp.toml new file mode 100644 index 0000000000..a4d80e289f --- /dev/null +++ b/test/test_projects/buck_bad_config/.elp.toml @@ -0,0 +1,8 @@ +[buck] +enabled = true +build_deps = false +included_targets = [ "fbcode//whatsapp/elp/test/test_projects/buck_bad_config/..." ] +source_root = "whatsapp/elp/test/test_projects/buck_bad_config" + +[eqwalizer] +enable_all = false diff --git a/test_projects/buck_bad_config/src/bad_app.erl b/test/test_projects/buck_bad_config/src/bad_app.erl similarity index 100% rename from test_projects/buck_bad_config/src/bad_app.erl rename to test/test_projects/buck_bad_config/src/bad_app.erl diff --git a/test/test_projects/buck_tests/.elp.toml b/test/test_projects/buck_tests/.elp.toml new file mode 100644 index 0000000000..0fbba8f0d6 --- /dev/null +++ b/test/test_projects/buck_tests/.elp.toml @@ -0,0 +1,9 @@ +[buck] +enabled = true +build_deps = false +included_targets = [ "fbcode//whatsapp/elp/test/test_projects/buck_tests/..." ] +excluded_targets = [ "fbcode//whatsapp/elp/test/test_projects/buck_tests:test_elp_ignored" ] +source_root = "whatsapp/elp/test/test_projects/buck_tests" + +[eqwalizer] +enable_all = false diff --git a/test_projects/buck_tests/TARGETS.v2_ b/test/test_projects/buck_tests/TARGETS.v2_ similarity index 100% rename from test_projects/buck_tests/TARGETS.v2_ rename to test/test_projects/buck_tests/TARGETS.v2_ diff --git a/test_projects/buck_tests/test_elp/TARGETS.v2_ b/test/test_projects/buck_tests/test_elp/TARGETS.v2_ similarity index 76% rename from test_projects/buck_tests/test_elp/TARGETS.v2_ rename to test/test_projects/buck_tests/test_elp/TARGETS.v2_ index 83089813da..528f2dbc2a 100644 --- a/test_projects/buck_tests/test_elp/TARGETS.v2_ +++ b/test/test_projects/buck_tests/test_elp/TARGETS.v2_ @@ -7,11 +7,11 @@ erlang_application( ]), app_src = "src/test_elp.app.src", applications = [ - "//whatsapp/elp/test_projects/buck_tests/test_elp_direct_dep:test_elp_direct_dep", - "//whatsapp/elp/test_projects/buck_tests:test_elp_no_private_headers", - "//whatsapp/elp/test_projects/buck_tests:test_elp_no_public_headers", - "//whatsapp/elp/test_projects/buck_tests:test_elp_flat_outside_target", - "//whatsapp/elp/test_projects/buck_tests/test_elp_flat_inside_target:test_elp_flat_inside_target", + "//whatsapp/elp/test/test_projects/buck_tests/test_elp_direct_dep:test_elp_direct_dep", + "//whatsapp/elp/test/test_projects/buck_tests:test_elp_no_private_headers", + "//whatsapp/elp/test/test_projects/buck_tests:test_elp_no_public_headers", + "//whatsapp/elp/test/test_projects/buck_tests:test_elp_flat_outside_target", + "//whatsapp/elp/test/test_projects/buck_tests/test_elp_flat_inside_target:test_elp_flat_inside_target", ], includes = glob(["include/*.hrl"]), version = "1.0.0", diff --git a/test_projects/buck_tests/test_elp/include/test_elp.hrl b/test/test_projects/buck_tests/test_elp/include/test_elp.hrl similarity index 100% rename from test_projects/buck_tests/test_elp/include/test_elp.hrl rename to test/test_projects/buck_tests/test_elp/include/test_elp.hrl diff --git a/test_projects/buck_tests/test_elp/src/test_elp.app.src b/test/test_projects/buck_tests/test_elp/src/test_elp.app.src similarity index 100% rename from test_projects/buck_tests/test_elp/src/test_elp.app.src rename to test/test_projects/buck_tests/test_elp/src/test_elp.app.src diff --git a/test_projects/buck_tests/test_elp/src/test_elp.erl b/test/test_projects/buck_tests/test_elp/src/test_elp.erl similarity index 100% rename from test_projects/buck_tests/test_elp/src/test_elp.erl rename to test/test_projects/buck_tests/test_elp/src/test_elp.erl diff --git a/test_projects/buck_tests/test_elp/test/test_elp_SUITE.erl b/test/test_projects/buck_tests/test_elp/test/test_elp_SUITE.erl similarity index 100% rename from test_projects/buck_tests/test_elp/test/test_elp_SUITE.erl rename to test/test_projects/buck_tests/test_elp/test/test_elp_SUITE.erl diff --git a/test_projects/buck_tests/test_elp/test/test_elp_SUITE_data/handle_update_test1.json b/test/test_projects/buck_tests/test_elp/test/test_elp_SUITE_data/handle_update_test1.json similarity index 100% rename from test_projects/buck_tests/test_elp/test/test_elp_SUITE_data/handle_update_test1.json rename to test/test_projects/buck_tests/test_elp/test/test_elp_SUITE_data/handle_update_test1.json diff --git a/test_projects/buck_tests/test_elp/test/test_elp_SUITE_data/handle_update_test2.json b/test/test_projects/buck_tests/test_elp/test/test_elp_SUITE_data/handle_update_test2.json similarity index 100% rename from test_projects/buck_tests/test_elp/test/test_elp_SUITE_data/handle_update_test2.json rename to test/test_projects/buck_tests/test_elp/test/test_elp_SUITE_data/handle_update_test2.json diff --git a/test_projects/buck_tests/test_elp/test/test_elp_SUITE_data/untracked_header.hrl b/test/test_projects/buck_tests/test_elp/test/test_elp_SUITE_data/untracked_header.hrl similarity index 100% rename from test_projects/buck_tests/test_elp/test/test_elp_SUITE_data/untracked_header.hrl rename to test/test_projects/buck_tests/test_elp/test/test_elp_SUITE_data/untracked_header.hrl diff --git a/test_projects/buck_tests/test_elp/test/test_elp_SUITE_data/untracked_module.erl b/test/test_projects/buck_tests/test_elp/test/test_elp_SUITE_data/untracked_module.erl similarity index 100% rename from test_projects/buck_tests/test_elp/test/test_elp_SUITE_data/untracked_module.erl rename to test/test_projects/buck_tests/test_elp/test/test_elp_SUITE_data/untracked_module.erl diff --git a/test_projects/buck_tests/test_elp/test/test_elp_test_utils.erl b/test/test_projects/buck_tests/test_elp/test/test_elp_test_utils.erl similarity index 100% rename from test_projects/buck_tests/test_elp/test/test_elp_test_utils.erl rename to test/test_projects/buck_tests/test_elp/test/test_elp_test_utils.erl diff --git a/test_projects/buck_tests/test_elp/test/test_elp_test_utils.hrl b/test/test_projects/buck_tests/test_elp/test/test_elp_test_utils.hrl similarity index 100% rename from test_projects/buck_tests/test_elp/test/test_elp_test_utils.hrl rename to test/test_projects/buck_tests/test_elp/test/test_elp_test_utils.hrl diff --git a/test_projects/buck_tests/test_elp_direct_dep/TARGETS.v2_ b/test/test_projects/buck_tests/test_elp_direct_dep/TARGETS.v2_ similarity index 61% rename from test_projects/buck_tests/test_elp_direct_dep/TARGETS.v2_ rename to test/test_projects/buck_tests/test_elp_direct_dep/TARGETS.v2_ index d0fea8149b..dd46a58842 100644 --- a/test_projects/buck_tests/test_elp_direct_dep/TARGETS.v2_ +++ b/test/test_projects/buck_tests/test_elp_direct_dep/TARGETS.v2_ @@ -5,10 +5,10 @@ erlang_application( "src/*.hrl", ]), applications = [ - "//whatsapp/elp/test_projects/buck_tests/test_elp_transitive_dep:test_elp_transitive_dep", + "//whatsapp/elp/test/test_projects/buck_tests/test_elp_transitive_dep:test_elp_transitive_dep", ], extra_includes = [ - "//whatsapp/elp/test_projects/buck_tests/test_elp:test_elp", + "//whatsapp/elp/test/test_projects/buck_tests/test_elp:test_elp", ], includes = glob(["include/*.hrl"]), version = "1.0.0", diff --git a/test_projects/buck_tests/test_elp_direct_dep/include/test_elp_direct_dep.hrl b/test/test_projects/buck_tests/test_elp_direct_dep/include/test_elp_direct_dep.hrl similarity index 100% rename from test_projects/buck_tests/test_elp_direct_dep/include/test_elp_direct_dep.hrl rename to test/test_projects/buck_tests/test_elp_direct_dep/include/test_elp_direct_dep.hrl diff --git a/test_projects/buck_tests/test_elp_direct_dep/src/test_elp_direct_dep.erl b/test/test_projects/buck_tests/test_elp_direct_dep/src/test_elp_direct_dep.erl similarity index 100% rename from test_projects/buck_tests/test_elp_direct_dep/src/test_elp_direct_dep.erl rename to test/test_projects/buck_tests/test_elp_direct_dep/src/test_elp_direct_dep.erl diff --git a/test_projects/buck_tests/test_elp_direct_dep/src/test_elp_direct_dep_private.hrl b/test/test_projects/buck_tests/test_elp_direct_dep/src/test_elp_direct_dep_private.hrl similarity index 100% rename from test_projects/buck_tests/test_elp_direct_dep/src/test_elp_direct_dep_private.hrl rename to test/test_projects/buck_tests/test_elp_direct_dep/src/test_elp_direct_dep_private.hrl diff --git a/test_projects/buck_tests/test_elp_flat_inside_target/TARGETS.v2_ b/test/test_projects/buck_tests/test_elp_flat_inside_target/TARGETS.v2_ similarity index 100% rename from test_projects/buck_tests/test_elp_flat_inside_target/TARGETS.v2_ rename to test/test_projects/buck_tests/test_elp_flat_inside_target/TARGETS.v2_ diff --git a/test_projects/buck_tests/test_elp_flat_inside_target/test_elp_flat_inside_target.erl b/test/test_projects/buck_tests/test_elp_flat_inside_target/test_elp_flat_inside_target.erl similarity index 100% rename from test_projects/buck_tests/test_elp_flat_inside_target/test_elp_flat_inside_target.erl rename to test/test_projects/buck_tests/test_elp_flat_inside_target/test_elp_flat_inside_target.erl diff --git a/test_projects/buck_tests/test_elp_flat_inside_target/test_elp_flat_inside_target.hrl b/test/test_projects/buck_tests/test_elp_flat_inside_target/test_elp_flat_inside_target.hrl similarity index 100% rename from test_projects/buck_tests/test_elp_flat_inside_target/test_elp_flat_inside_target.hrl rename to test/test_projects/buck_tests/test_elp_flat_inside_target/test_elp_flat_inside_target.hrl diff --git a/test_projects/buck_tests/test_elp_flat_outside_target/test_elp_flat_outside_target.erl b/test/test_projects/buck_tests/test_elp_flat_outside_target/test_elp_flat_outside_target.erl similarity index 100% rename from test_projects/buck_tests/test_elp_flat_outside_target/test_elp_flat_outside_target.erl rename to test/test_projects/buck_tests/test_elp_flat_outside_target/test_elp_flat_outside_target.erl diff --git a/test_projects/buck_tests/test_elp_flat_outside_target/test_elp_flat_outside_target.hrl b/test/test_projects/buck_tests/test_elp_flat_outside_target/test_elp_flat_outside_target.hrl similarity index 100% rename from test_projects/buck_tests/test_elp_flat_outside_target/test_elp_flat_outside_target.hrl rename to test/test_projects/buck_tests/test_elp_flat_outside_target/test_elp_flat_outside_target.hrl diff --git a/test_projects/buck_tests/test_elp_ignored/test_elp_ignored.erl b/test/test_projects/buck_tests/test_elp_ignored/test_elp_ignored.erl similarity index 100% rename from test_projects/buck_tests/test_elp_ignored/test_elp_ignored.erl rename to test/test_projects/buck_tests/test_elp_ignored/test_elp_ignored.erl diff --git a/test_projects/buck_tests/test_elp_no_private_headers/include/test_elp_no_private_headers.hrl b/test/test_projects/buck_tests/test_elp_no_private_headers/include/test_elp_no_private_headers.hrl similarity index 100% rename from test_projects/buck_tests/test_elp_no_private_headers/include/test_elp_no_private_headers.hrl rename to test/test_projects/buck_tests/test_elp_no_private_headers/include/test_elp_no_private_headers.hrl diff --git a/test_projects/buck_tests/test_elp_no_private_headers/src/test_elp_no_private_headers.erl b/test/test_projects/buck_tests/test_elp_no_private_headers/src/test_elp_no_private_headers.erl similarity index 100% rename from test_projects/buck_tests/test_elp_no_private_headers/src/test_elp_no_private_headers.erl rename to test/test_projects/buck_tests/test_elp_no_private_headers/src/test_elp_no_private_headers.erl diff --git a/test_projects/buck_tests/test_elp_no_public_headers/src/test_elp_no_headers.erl b/test/test_projects/buck_tests/test_elp_no_public_headers/src/test_elp_no_headers.erl similarity index 100% rename from test_projects/buck_tests/test_elp_no_public_headers/src/test_elp_no_headers.erl rename to test/test_projects/buck_tests/test_elp_no_public_headers/src/test_elp_no_headers.erl diff --git a/test_projects/buck_tests/test_elp_transitive_dep/TARGETS.v2_ b/test/test_projects/buck_tests/test_elp_transitive_dep/TARGETS.v2_ similarity index 100% rename from test_projects/buck_tests/test_elp_transitive_dep/TARGETS.v2_ rename to test/test_projects/buck_tests/test_elp_transitive_dep/TARGETS.v2_ diff --git a/test_projects/buck_tests/test_elp_transitive_dep/include/test_elp_transitive_dep.hrl b/test/test_projects/buck_tests/test_elp_transitive_dep/include/test_elp_transitive_dep.hrl similarity index 100% rename from test_projects/buck_tests/test_elp_transitive_dep/include/test_elp_transitive_dep.hrl rename to test/test_projects/buck_tests/test_elp_transitive_dep/include/test_elp_transitive_dep.hrl diff --git a/test_projects/buck_tests/test_elp_transitive_dep/src/test_elp_transitive_dep.erl b/test/test_projects/buck_tests/test_elp_transitive_dep/src/test_elp_transitive_dep.erl similarity index 100% rename from test_projects/buck_tests/test_elp_transitive_dep/src/test_elp_transitive_dep.erl rename to test/test_projects/buck_tests/test_elp_transitive_dep/src/test_elp_transitive_dep.erl diff --git a/test_projects/buck_tests/test_elp_transitive_dep/src/test_elp_transitive_dep_private.hrl b/test/test_projects/buck_tests/test_elp_transitive_dep/src/test_elp_transitive_dep_private.hrl similarity index 100% rename from test_projects/buck_tests/test_elp_transitive_dep/src/test_elp_transitive_dep_private.hrl rename to test/test_projects/buck_tests/test_elp_transitive_dep/src/test_elp_transitive_dep_private.hrl diff --git a/test/test_projects/buck_tests_2/.elp.toml b/test/test_projects/buck_tests_2/.elp.toml new file mode 100644 index 0000000000..102f44c3f5 --- /dev/null +++ b/test/test_projects/buck_tests_2/.elp.toml @@ -0,0 +1,12 @@ +[buck] +enabled = true +build_deps = false +included_targets = [ + "fbcode//whatsapp/elp/test/test_projects/buck_tests_2/util/app_a/...", + "fbcode//whatsapp/elp/test/test_projects/buck_tests_2:check_include" + ] +excluded_targets = [ "fbcode//whatsapp/elp/test/test_projects/buck_tests_2:test_elp_ignored" ] +source_root = "whatsapp/elp/test/test_projects/buck_tests_2" + +[eqwalizer] +enable_all = false diff --git a/test_projects/buck_tests_2/auto_gen/auto_gen_a/include/auto_gen_a.hrl b/test/test_projects/buck_tests_2/auto_gen/auto_gen_a/include/auto_gen_a.hrl similarity index 100% rename from test_projects/buck_tests_2/auto_gen/auto_gen_a/include/auto_gen_a.hrl rename to test/test_projects/buck_tests_2/auto_gen/auto_gen_a/include/auto_gen_a.hrl diff --git a/test_projects/buck_tests_2/auto_gen/auto_gen_a/out/pretend_generated.erl b/test/test_projects/buck_tests_2/auto_gen/auto_gen_a/out/pretend_generated.erl similarity index 100% rename from test_projects/buck_tests_2/auto_gen/auto_gen_a/out/pretend_generated.erl rename to test/test_projects/buck_tests_2/auto_gen/auto_gen_a/out/pretend_generated.erl diff --git a/test_projects/buck_tests_2/auto_gen/auto_gen_a/src/auto_gen_a.erl b/test/test_projects/buck_tests_2/auto_gen/auto_gen_a/src/auto_gen_a.erl similarity index 100% rename from test_projects/buck_tests_2/auto_gen/auto_gen_a/src/auto_gen_a.erl rename to test/test_projects/buck_tests_2/auto_gen/auto_gen_a/src/auto_gen_a.erl diff --git a/test_projects/buck_tests_2/check_include/src/top_includer.erl b/test/test_projects/buck_tests_2/check_include/src/top_includer.erl similarity index 100% rename from test_projects/buck_tests_2/check_include/src/top_includer.erl rename to test/test_projects/buck_tests_2/check_include/src/top_includer.erl diff --git a/test_projects/buck_tests_2/check_include_separate_1/include/include_with_bug.hrl b/test/test_projects/buck_tests_2/check_include_separate_1/include/include_with_bug.hrl similarity index 100% rename from test_projects/buck_tests_2/check_include_separate_1/include/include_with_bug.hrl rename to test/test_projects/buck_tests_2/check_include_separate_1/include/include_with_bug.hrl diff --git a/test_projects/buck_tests_2/check_include_separate_1/include/top_includer.hrl b/test/test_projects/buck_tests_2/check_include_separate_1/include/top_includer.hrl similarity index 100% rename from test_projects/buck_tests_2/check_include_separate_1/include/top_includer.hrl rename to test/test_projects/buck_tests_2/check_include_separate_1/include/top_includer.hrl diff --git a/test_projects/buck_tests_2/check_include_separate_2/include/separate_include.hrl b/test/test_projects/buck_tests_2/check_include_separate_2/include/separate_include.hrl similarity index 100% rename from test_projects/buck_tests_2/check_include_separate_2/include/separate_include.hrl rename to test/test_projects/buck_tests_2/check_include_separate_2/include/separate_include.hrl diff --git a/test_projects/buck_tests_2/generated/out/generated_header.hrl b/test/test_projects/buck_tests_2/generated/out/generated_header.hrl similarity index 100% rename from test_projects/buck_tests_2/generated/out/generated_header.hrl rename to test/test_projects/buck_tests_2/generated/out/generated_header.hrl diff --git a/test_projects/buck_tests_2/util/app_a/include/junk.hrl b/test/test_projects/buck_tests_2/util/app_a/include/junk.hrl similarity index 100% rename from test_projects/buck_tests_2/util/app_a/include/junk.hrl rename to test/test_projects/buck_tests_2/util/app_a/include/junk.hrl diff --git a/test_projects/buck_tests_2/util/app_a/src/app_a.erl b/test/test_projects/buck_tests_2/util/app_a/src/app_a.erl similarity index 100% rename from test_projects/buck_tests_2/util/app_a/src/app_a.erl rename to test/test_projects/buck_tests_2/util/app_a/src/app_a.erl diff --git a/test/test_projects/codegen_test/.elp.toml b/test/test_projects/codegen_test/.elp.toml new file mode 100644 index 0000000000..52ec390ccb --- /dev/null +++ b/test/test_projects/codegen_test/.elp.toml @@ -0,0 +1,8 @@ +[buck] +enabled = true +build_deps = true +included_targets = [ "fbcode//whatsapp/elp/test/test_projects/codegen_test:codegen_test_app" ] +source_root = "whatsapp/elp/test/test_projects/codegen_test" + +[eqwalizer] +enable_all = true diff --git a/test_projects/codegen_test/README.md b/test/test_projects/codegen_test/README.md similarity index 93% rename from test_projects/codegen_test/README.md rename to test/test_projects/codegen_test/README.md index 1af599454c..061fb1914c 100644 --- a/test_projects/codegen_test/README.md +++ b/test/test_projects/codegen_test/README.md @@ -111,13 +111,13 @@ get_user_by_id(UserId) -> ```bash # Build just the code generation step -buck2 build fbcode//whatsapp/elp/test_projects/codegen_test:example_service_types_erl +buck2 build fbcode//whatsapp/elp/test/test_projects/codegen_test:example_service_types_erl # Build the application (automatically runs code generation) -buck2 build fbcode//whatsapp/elp/test_projects/codegen_test:codegen_test_app +buck2 build fbcode//whatsapp/elp/test/test_projects/codegen_test:codegen_test_app # Run tests -buck2 test fbcode//whatsapp/elp/test_projects/codegen_test:codegen_test_SUITE +buck2 test fbcode//whatsapp/elp/test/test_projects/codegen_test:codegen_test_SUITE # View generated files in buck-out find buck-out -path "*codegen_test*" -name "example_service_*.erl" diff --git a/test_projects/codegen_test/app_a/src/codegen_test.app.src b/test/test_projects/codegen_test/app_a/src/codegen_test.app.src similarity index 100% rename from test_projects/codegen_test/app_a/src/codegen_test.app.src rename to test/test_projects/codegen_test/app_a/src/codegen_test.app.src diff --git a/test_projects/codegen_test/app_a/src/example_usage.erl b/test/test_projects/codegen_test/app_a/src/example_usage.erl similarity index 100% rename from test_projects/codegen_test/app_a/src/example_usage.erl rename to test/test_projects/codegen_test/app_a/src/example_usage.erl diff --git a/test_projects/codegen_test/app_a/test/codegen_test_SUITE.erl b/test/test_projects/codegen_test/app_a/test/codegen_test_SUITE.erl similarity index 100% rename from test_projects/codegen_test/app_a/test/codegen_test_SUITE.erl rename to test/test_projects/codegen_test/app_a/test/codegen_test_SUITE.erl diff --git a/test_projects/codegen_test/generated/example_service_client.erl b/test/test_projects/codegen_test/generated/example_service_client.erl similarity index 100% rename from test_projects/codegen_test/generated/example_service_client.erl rename to test/test_projects/codegen_test/generated/example_service_client.erl diff --git a/test_projects/codegen_test/generated/example_service_types.erl b/test/test_projects/codegen_test/generated/example_service_types.erl similarity index 100% rename from test_projects/codegen_test/generated/example_service_types.erl rename to test/test_projects/codegen_test/generated/example_service_types.erl diff --git a/test_projects/codegen_test/generated/example_service_types.hrl b/test/test_projects/codegen_test/generated/example_service_types.hrl similarity index 100% rename from test_projects/codegen_test/generated/example_service_types.hrl rename to test/test_projects/codegen_test/generated/example_service_types.hrl diff --git a/test_projects/codegen_test/templates/example_service_client.erl b/test/test_projects/codegen_test/templates/example_service_client.erl similarity index 100% rename from test_projects/codegen_test/templates/example_service_client.erl rename to test/test_projects/codegen_test/templates/example_service_client.erl diff --git a/test_projects/codegen_test/templates/example_service_types.erl b/test/test_projects/codegen_test/templates/example_service_types.erl similarity index 100% rename from test_projects/codegen_test/templates/example_service_types.erl rename to test/test_projects/codegen_test/templates/example_service_types.erl diff --git a/test_projects/codegen_test/templates/example_service_types.hrl b/test/test_projects/codegen_test/templates/example_service_types.hrl similarity index 100% rename from test_projects/codegen_test/templates/example_service_types.hrl rename to test/test_projects/codegen_test/templates/example_service_types.hrl diff --git a/test_projects/custom_build_tool/.elp.toml b/test/test_projects/custom_build_tool/.elp.toml similarity index 100% rename from test_projects/custom_build_tool/.elp.toml rename to test/test_projects/custom_build_tool/.elp.toml diff --git a/test_projects/custom_build_tool/apps/app_a/include/app_a.hrl b/test/test_projects/custom_build_tool/apps/app_a/include/app_a.hrl similarity index 100% rename from test_projects/custom_build_tool/apps/app_a/include/app_a.hrl rename to test/test_projects/custom_build_tool/apps/app_a/include/app_a.hrl diff --git a/test_projects/custom_build_tool/apps/app_a/src/app_a.erl b/test/test_projects/custom_build_tool/apps/app_a/src/app_a.erl similarity index 100% rename from test_projects/custom_build_tool/apps/app_a/src/app_a.erl rename to test/test_projects/custom_build_tool/apps/app_a/src/app_a.erl diff --git a/test_projects/custom_build_tool/apps/app_b/src/app_b.erl b/test/test_projects/custom_build_tool/apps/app_b/src/app_b.erl similarity index 100% rename from test_projects/custom_build_tool/apps/app_b/src/app_b.erl rename to test/test_projects/custom_build_tool/apps/app_b/src/app_b.erl diff --git a/test/test_projects/diagnostics/.elp.toml b/test/test_projects/diagnostics/.elp.toml new file mode 100644 index 0000000000..44c9516105 --- /dev/null +++ b/test/test_projects/diagnostics/.elp.toml @@ -0,0 +1,8 @@ +[buck] +enabled = true +build_deps = false +included_targets = [ "fbcode//whatsapp/elp/test/test_projects/diagnostics/..." ] +source_root = "whatsapp/elp/test/test_projects/diagnostics" + +[eqwalizer] +enable_all = false diff --git a/test_projects/diagnostics/README.md b/test/test_projects/diagnostics/README.md similarity index 100% rename from test_projects/diagnostics/README.md rename to test/test_projects/diagnostics/README.md diff --git a/test_projects/diagnostics/app_a/extra/app_a.erl b/test/test_projects/diagnostics/app_a/extra/app_a.erl similarity index 100% rename from test_projects/diagnostics/app_a/extra/app_a.erl rename to test/test_projects/diagnostics/app_a/extra/app_a.erl diff --git a/test_projects/diagnostics/app_a/include/app_a.hrl b/test/test_projects/diagnostics/app_a/include/app_a.hrl similarity index 100% rename from test_projects/diagnostics/app_a/include/app_a.hrl rename to test/test_projects/diagnostics/app_a/include/app_a.hrl diff --git a/test_projects/diagnostics/app_a/include/broken_diagnostics.hrl b/test/test_projects/diagnostics/app_a/include/broken_diagnostics.hrl similarity index 100% rename from test_projects/diagnostics/app_a/include/broken_diagnostics.hrl rename to test/test_projects/diagnostics/app_a/include/broken_diagnostics.hrl diff --git a/test_projects/diagnostics/app_a/include/diagnostics.hrl b/test/test_projects/diagnostics/app_a/include/diagnostics.hrl similarity index 100% rename from test_projects/diagnostics/app_a/include/diagnostics.hrl rename to test/test_projects/diagnostics/app_a/include/diagnostics.hrl diff --git a/test_projects/diagnostics/app_a/src/app_a.app.src b/test/test_projects/diagnostics/app_a/src/app_a.app.src similarity index 100% rename from test_projects/diagnostics/app_a/src/app_a.app.src rename to test/test_projects/diagnostics/app_a/src/app_a.app.src diff --git a/test_projects/diagnostics/app_a/src/app_a.erl b/test/test_projects/diagnostics/app_a/src/app_a.erl similarity index 100% rename from test_projects/diagnostics/app_a/src/app_a.erl rename to test/test_projects/diagnostics/app_a/src/app_a.erl diff --git a/test_projects/diagnostics/app_a/src/broken_parse_trans.erl b/test/test_projects/diagnostics/app_a/src/broken_parse_trans.erl similarity index 100% rename from test_projects/diagnostics/app_a/src/broken_parse_trans.erl rename to test/test_projects/diagnostics/app_a/src/broken_parse_trans.erl diff --git a/test_projects/diagnostics/app_a/src/cascading.erl b/test/test_projects/diagnostics/app_a/src/cascading.erl similarity index 100% rename from test_projects/diagnostics/app_a/src/cascading.erl rename to test/test_projects/diagnostics/app_a/src/cascading.erl diff --git a/test_projects/diagnostics/app_a/src/crlf.erl b/test/test_projects/diagnostics/app_a/src/crlf.erl similarity index 100% rename from test_projects/diagnostics/app_a/src/crlf.erl rename to test/test_projects/diagnostics/app_a/src/crlf.erl diff --git a/test_projects/diagnostics/app_a/src/diagnostics.erl b/test/test_projects/diagnostics/app_a/src/diagnostics.erl similarity index 100% rename from test_projects/diagnostics/app_a/src/diagnostics.erl rename to test/test_projects/diagnostics/app_a/src/diagnostics.erl diff --git a/test_projects/diagnostics/app_a/src/diagnostics.escript b/test/test_projects/diagnostics/app_a/src/diagnostics.escript similarity index 100% rename from test_projects/diagnostics/app_a/src/diagnostics.escript rename to test/test_projects/diagnostics/app_a/src/diagnostics.escript diff --git a/test_projects/diagnostics/app_a/src/diagnostics_errors.escript b/test/test_projects/diagnostics/app_a/src/diagnostics_errors.escript similarity index 100% rename from test_projects/diagnostics/app_a/src/diagnostics_errors.escript rename to test/test_projects/diagnostics/app_a/src/diagnostics_errors.escript diff --git a/test_projects/diagnostics/app_a/src/diagnostics_warnings.escript b/test/test_projects/diagnostics/app_a/src/diagnostics_warnings.escript similarity index 100% rename from test_projects/diagnostics/app_a/src/diagnostics_warnings.escript rename to test/test_projects/diagnostics/app_a/src/diagnostics_warnings.escript diff --git a/test_projects/diagnostics/app_a/src/erlang_diagnostics_errors_gen.erl b/test/test_projects/diagnostics/app_a/src/erlang_diagnostics_errors_gen.erl similarity index 100% rename from test_projects/diagnostics/app_a/src/erlang_diagnostics_errors_gen.erl rename to test/test_projects/diagnostics/app_a/src/erlang_diagnostics_errors_gen.erl diff --git a/test_projects/diagnostics/app_a/src/file_attribute.erl b/test/test_projects/diagnostics/app_a/src/file_attribute.erl similarity index 100% rename from test_projects/diagnostics/app_a/src/file_attribute.erl rename to test/test_projects/diagnostics/app_a/src/file_attribute.erl diff --git a/test_projects/diagnostics/app_a/src/lint_recursive.erl b/test/test_projects/diagnostics/app_a/src/lint_recursive.erl similarity index 100% rename from test_projects/diagnostics/app_a/src/lint_recursive.erl rename to test/test_projects/diagnostics/app_a/src/lint_recursive.erl diff --git a/test_projects/diagnostics/app_a/src/lints.erl b/test/test_projects/diagnostics/app_a/src/lints.erl similarity index 100% rename from test_projects/diagnostics/app_a/src/lints.erl rename to test/test_projects/diagnostics/app_a/src/lints.erl diff --git a/test_projects/diagnostics/app_a/src/otp27_docstrings.erl b/test/test_projects/diagnostics/app_a/src/otp27_docstrings.erl similarity index 100% rename from test_projects/diagnostics/app_a/src/otp27_docstrings.erl rename to test/test_projects/diagnostics/app_a/src/otp27_docstrings.erl diff --git a/test_projects/diagnostics/app_a/src/otp27_sigils.erl b/test/test_projects/diagnostics/app_a/src/otp27_sigils.erl similarity index 100% rename from test_projects/diagnostics/app_a/src/otp27_sigils.erl rename to test/test_projects/diagnostics/app_a/src/otp27_sigils.erl diff --git a/test_projects/diagnostics/app_a/src/otp_7655.erl b/test/test_projects/diagnostics/app_a/src/otp_7655.erl similarity index 100% rename from test_projects/diagnostics/app_a/src/otp_7655.erl rename to test/test_projects/diagnostics/app_a/src/otp_7655.erl diff --git a/test_projects/diagnostics/app_a/src/parse_error_a_cascade.erl b/test/test_projects/diagnostics/app_a/src/parse_error_a_cascade.erl similarity index 100% rename from test_projects/diagnostics/app_a/src/parse_error_a_cascade.erl rename to test/test_projects/diagnostics/app_a/src/parse_error_a_cascade.erl diff --git a/test_projects/diagnostics/app_a/src/suppressed.erl b/test/test_projects/diagnostics/app_a/src/suppressed.erl similarity index 100% rename from test_projects/diagnostics/app_a/src/suppressed.erl rename to test/test_projects/diagnostics/app_a/src/suppressed.erl diff --git a/test_projects/diagnostics/app_a/src/syntax.erl b/test/test_projects/diagnostics/app_a/src/syntax.erl similarity index 100% rename from test_projects/diagnostics/app_a/src/syntax.erl rename to test/test_projects/diagnostics/app_a/src/syntax.erl diff --git a/test_projects/diagnostics/app_a/test/app_a_SUITE.erl b/test/test_projects/diagnostics/app_a/test/app_a_SUITE.erl similarity index 100% rename from test_projects/diagnostics/app_a/test/app_a_SUITE.erl rename to test/test_projects/diagnostics/app_a/test/app_a_SUITE.erl diff --git a/test_projects/diagnostics/erlang_ls.config b/test/test_projects/diagnostics/erlang_ls.config similarity index 100% rename from test_projects/diagnostics/erlang_ls.config rename to test/test_projects/diagnostics/erlang_ls.config diff --git a/test_projects/diagnostics/rebar.config b/test/test_projects/diagnostics/rebar.config similarity index 100% rename from test_projects/diagnostics/rebar.config rename to test/test_projects/diagnostics/rebar.config diff --git a/test_projects/diagnostics/test_build_info.json b/test/test_projects/diagnostics/test_build_info.json similarity index 100% rename from test_projects/diagnostics/test_build_info.json rename to test/test_projects/diagnostics/test_build_info.json diff --git a/test/test_projects/end_to_end/.elp.toml b/test/test_projects/end_to_end/.elp.toml new file mode 100644 index 0000000000..4d685a40c7 --- /dev/null +++ b/test/test_projects/end_to_end/.elp.toml @@ -0,0 +1,7 @@ +[buck] +enabled = true +build_deps = false +included_targets = ["fbcode//whatsapp/elp/test/test_projects/end_to_end/..."] + +[eqwalizer] +enable_all = false diff --git a/test_projects/end_to_end/assist_examples/src/add_doc.erl b/test/test_projects/end_to_end/assist_examples/src/add_doc.erl similarity index 100% rename from test_projects/end_to_end/assist_examples/src/add_doc.erl rename to test/test_projects/end_to_end/assist_examples/src/add_doc.erl diff --git a/test_projects/end_to_end/assist_examples/src/assist_examples.app.src b/test/test_projects/end_to_end/assist_examples/src/assist_examples.app.src similarity index 100% rename from test_projects/end_to_end/assist_examples/src/assist_examples.app.src rename to test/test_projects/end_to_end/assist_examples/src/assist_examples.app.src diff --git a/test_projects/end_to_end/assist_examples/src/code_completion.erl b/test/test_projects/end_to_end/assist_examples/src/code_completion.erl similarity index 100% rename from test_projects/end_to_end/assist_examples/src/code_completion.erl rename to test/test_projects/end_to_end/assist_examples/src/code_completion.erl diff --git a/test_projects/end_to_end/assist_examples/src/extract_function.erl b/test/test_projects/end_to_end/assist_examples/src/extract_function.erl similarity index 100% rename from test_projects/end_to_end/assist_examples/src/extract_function.erl rename to test/test_projects/end_to_end/assist_examples/src/extract_function.erl diff --git a/test_projects/end_to_end/assist_examples/src/head_mismatch.erl b/test/test_projects/end_to_end/assist_examples/src/head_mismatch.erl similarity index 100% rename from test_projects/end_to_end/assist_examples/src/head_mismatch.erl rename to test/test_projects/end_to_end/assist_examples/src/head_mismatch.erl diff --git a/test_projects/end_to_end/caf/configerator/source/whatsapp/my_caf.erl b/test/test_projects/end_to_end/caf/configerator/source/whatsapp/my_caf.erl similarity index 100% rename from test_projects/end_to_end/caf/configerator/source/whatsapp/my_caf.erl rename to test/test_projects/end_to_end/caf/configerator/source/whatsapp/my_caf.erl diff --git a/test_projects/end_to_end/definitions/README.md b/test/test_projects/end_to_end/definitions/README.md similarity index 100% rename from test_projects/end_to_end/definitions/README.md rename to test/test_projects/end_to_end/definitions/README.md diff --git a/test_projects/end_to_end/definitions/src/definitions.app.src b/test/test_projects/end_to_end/definitions/src/definitions.app.src similarity index 100% rename from test_projects/end_to_end/definitions/src/definitions.app.src rename to test/test_projects/end_to_end/definitions/src/definitions.app.src diff --git a/test_projects/end_to_end/definitions/src/local_def.erl b/test/test_projects/end_to_end/definitions/src/local_def.erl similarity index 100% rename from test_projects/end_to_end/definitions/src/local_def.erl rename to test/test_projects/end_to_end/definitions/src/local_def.erl diff --git a/test_projects/end_to_end/docs/src/docs.app.src b/test/test_projects/end_to_end/docs/src/docs.app.src similarity index 100% rename from test_projects/end_to_end/docs/src/docs.app.src rename to test/test_projects/end_to_end/docs/src/docs.app.src diff --git a/test_projects/end_to_end/docs/src/docs.erl b/test/test_projects/end_to_end/docs/src/docs.erl similarity index 100% rename from test_projects/end_to_end/docs/src/docs.erl rename to test/test_projects/end_to_end/docs/src/docs.erl diff --git a/test_projects/end_to_end/erlang_ls.config b/test/test_projects/end_to_end/erlang_ls.config similarity index 100% rename from test_projects/end_to_end/erlang_ls.config rename to test/test_projects/end_to_end/erlang_ls.config diff --git a/test_projects/end_to_end/fixtures/erlang-stacktrace.txt b/test/test_projects/end_to_end/fixtures/erlang-stacktrace.txt similarity index 100% rename from test_projects/end_to_end/fixtures/erlang-stacktrace.txt rename to test/test_projects/end_to_end/fixtures/erlang-stacktrace.txt diff --git a/test_projects/end_to_end/hover/README.md b/test/test_projects/end_to_end/hover/README.md similarity index 100% rename from test_projects/end_to_end/hover/README.md rename to test/test_projects/end_to_end/hover/README.md diff --git a/test_projects/end_to_end/hover/src/doc_examples.erl b/test/test_projects/end_to_end/hover/src/doc_examples.erl similarity index 100% rename from test_projects/end_to_end/hover/src/doc_examples.erl rename to test/test_projects/end_to_end/hover/src/doc_examples.erl diff --git a/test_projects/end_to_end/hover/src/hover.app.src b/test/test_projects/end_to_end/hover/src/hover.app.src similarity index 100% rename from test_projects/end_to_end/hover/src/hover.app.src rename to test/test_projects/end_to_end/hover/src/hover.app.src diff --git a/test_projects/end_to_end/rebar.config b/test/test_projects/end_to_end/rebar.config similarity index 100% rename from test_projects/end_to_end/rebar.config rename to test/test_projects/end_to_end/rebar.config diff --git a/test_projects/end_to_end/single_errors/README.md b/test/test_projects/end_to_end/single_errors/README.md similarity index 100% rename from test_projects/end_to_end/single_errors/README.md rename to test/test_projects/end_to_end/single_errors/README.md diff --git a/test_projects/end_to_end/single_errors/src/as_you_type.erl b/test/test_projects/end_to_end/single_errors/src/as_you_type.erl similarity index 100% rename from test_projects/end_to_end/single_errors/src/as_you_type.erl rename to test/test_projects/end_to_end/single_errors/src/as_you_type.erl diff --git a/test_projects/end_to_end/single_errors/src/compiler_diagnostics.erl b/test/test_projects/end_to_end/single_errors/src/compiler_diagnostics.erl similarity index 100% rename from test_projects/end_to_end/single_errors/src/compiler_diagnostics.erl rename to test/test_projects/end_to_end/single_errors/src/compiler_diagnostics.erl diff --git a/test_projects/end_to_end/single_errors/src/single_errors.app.src b/test/test_projects/end_to_end/single_errors/src/single_errors.app.src similarity index 100% rename from test_projects/end_to_end/single_errors/src/single_errors.app.src rename to test/test_projects/end_to_end/single_errors/src/single_errors.app.src diff --git a/test_projects/end_to_end/single_errors/src/spec_mismatch.erl b/test/test_projects/end_to_end/single_errors/src/spec_mismatch.erl similarity index 100% rename from test_projects/end_to_end/single_errors/src/spec_mismatch.erl rename to test/test_projects/end_to_end/single_errors/src/spec_mismatch.erl diff --git a/test_projects/end_to_end/single_errors/src/spec_mismatch.erl.2 b/test/test_projects/end_to_end/single_errors/src/spec_mismatch.erl.2 similarity index 100% rename from test_projects/end_to_end/single_errors/src/spec_mismatch.erl.2 rename to test/test_projects/end_to_end/single_errors/src/spec_mismatch.erl.2 diff --git a/test_projects/end_to_end/single_errors/src/types_on_hover.erl b/test/test_projects/end_to_end/single_errors/src/types_on_hover.erl similarity index 100% rename from test_projects/end_to_end/single_errors/src/types_on_hover.erl rename to test/test_projects/end_to_end/single_errors/src/types_on_hover.erl diff --git a/test_projects/end_to_end/single_errors/src/unused_macro.erl b/test/test_projects/end_to_end/single_errors/src/unused_macro.erl similarity index 100% rename from test_projects/end_to_end/single_errors/src/unused_macro.erl rename to test/test_projects/end_to_end/single_errors/src/unused_macro.erl diff --git a/test_projects/eqwalizer/src/eqwalizer.app.src b/test/test_projects/eqwalizer/src/eqwalizer.app.src similarity index 100% rename from test_projects/eqwalizer/src/eqwalizer.app.src rename to test/test_projects/eqwalizer/src/eqwalizer.app.src diff --git a/test_projects/eqwalizer/src/eqwalizer_specs.erl b/test/test_projects/eqwalizer/src/eqwalizer_specs.erl similarity index 100% rename from test_projects/eqwalizer/src/eqwalizer_specs.erl rename to test/test_projects/eqwalizer/src/eqwalizer_specs.erl diff --git a/test/test_projects/eqwalizer_callers/.elp.toml b/test/test_projects/eqwalizer_callers/.elp.toml new file mode 100644 index 0000000000..b0c2b55a5b --- /dev/null +++ b/test/test_projects/eqwalizer_callers/.elp.toml @@ -0,0 +1,5 @@ +[buck] +enabled = true +build_deps = false +included_targets = [ "fbcode//whatsapp/elp/test/test_projects/eqwalizer_callers/..." ] +source_root = "whatsapp/elp/test/test_projects/eqwalizer_callers" diff --git a/test_projects/eqwalizer_callers/.gitignore b/test/test_projects/eqwalizer_callers/.gitignore similarity index 100% rename from test_projects/eqwalizer_callers/.gitignore rename to test/test_projects/eqwalizer_callers/.gitignore diff --git a/test_projects/eqwalizer_callers/app_a/include/app_a.hrl b/test/test_projects/eqwalizer_callers/app_a/include/app_a.hrl similarity index 100% rename from test_projects/eqwalizer_callers/app_a/include/app_a.hrl rename to test/test_projects/eqwalizer_callers/app_a/include/app_a.hrl diff --git a/test_projects/eqwalizer_callers/app_a/src/app_a.app.src b/test/test_projects/eqwalizer_callers/app_a/src/app_a.app.src similarity index 100% rename from test_projects/eqwalizer_callers/app_a/src/app_a.app.src rename to test/test_projects/eqwalizer_callers/app_a/src/app_a.app.src diff --git a/test_projects/eqwalizer_callers/app_a/src/app_a.erl b/test/test_projects/eqwalizer_callers/app_a/src/app_a.erl similarity index 100% rename from test_projects/eqwalizer_callers/app_a/src/app_a.erl rename to test/test_projects/eqwalizer_callers/app_a/src/app_a.erl diff --git a/test_projects/eqwalizer_callers/app_a/src/app_b.erl b/test/test_projects/eqwalizer_callers/app_a/src/app_b.erl similarity index 100% rename from test_projects/eqwalizer_callers/app_a/src/app_b.erl rename to test/test_projects/eqwalizer_callers/app_a/src/app_b.erl diff --git a/test_projects/eqwalizer_callers/app_a/test/app_a_SUITE.erl b/test/test_projects/eqwalizer_callers/app_a/test/app_a_SUITE.erl similarity index 100% rename from test_projects/eqwalizer_callers/app_a/test/app_a_SUITE.erl rename to test/test_projects/eqwalizer_callers/app_a/test/app_a_SUITE.erl diff --git a/test_projects/eqwalizer_callers/rebar.config b/test/test_projects/eqwalizer_callers/rebar.config similarity index 100% rename from test_projects/eqwalizer_callers/rebar.config rename to test/test_projects/eqwalizer_callers/rebar.config diff --git a/test_projects/eqwalizer_ignore_modules/.elp.toml b/test/test_projects/eqwalizer_ignore_modules/.elp.toml similarity index 100% rename from test_projects/eqwalizer_ignore_modules/.elp.toml rename to test/test_projects/eqwalizer_ignore_modules/.elp.toml diff --git a/test_projects/eqwalizer_ignore_modules/apps/app_a/include/app_a.hrl b/test/test_projects/eqwalizer_ignore_modules/apps/app_a/include/app_a.hrl similarity index 100% rename from test_projects/eqwalizer_ignore_modules/apps/app_a/include/app_a.hrl rename to test/test_projects/eqwalizer_ignore_modules/apps/app_a/include/app_a.hrl diff --git a/test_projects/eqwalizer_ignore_modules/apps/app_a/src/another_module_a.erl b/test/test_projects/eqwalizer_ignore_modules/apps/app_a/src/another_module_a.erl similarity index 100% rename from test_projects/eqwalizer_ignore_modules/apps/app_a/src/another_module_a.erl rename to test/test_projects/eqwalizer_ignore_modules/apps/app_a/src/another_module_a.erl diff --git a/test_projects/eqwalizer_ignore_modules/apps/app_a/src/app_a.erl b/test/test_projects/eqwalizer_ignore_modules/apps/app_a/src/app_a.erl similarity index 100% rename from test_projects/eqwalizer_ignore_modules/apps/app_a/src/app_a.erl rename to test/test_projects/eqwalizer_ignore_modules/apps/app_a/src/app_a.erl diff --git a/test_projects/eqwalizer_ignore_modules/apps/app_b/src/app_b.erl b/test/test_projects/eqwalizer_ignore_modules/apps/app_b/src/app_b.erl similarity index 100% rename from test_projects/eqwalizer_ignore_modules/apps/app_b/src/app_b.erl rename to test/test_projects/eqwalizer_ignore_modules/apps/app_b/src/app_b.erl diff --git a/test/test_projects/eqwalizer_tests/.elp.toml b/test/test_projects/eqwalizer_tests/.elp.toml new file mode 100644 index 0000000000..9586666619 --- /dev/null +++ b/test/test_projects/eqwalizer_tests/.elp.toml @@ -0,0 +1,8 @@ +[buck] +enabled = true +build_deps = false +included_targets = [ "fbcode//whatsapp/elp/test/test_projects/eqwalizer_tests/..." ] +source_root = "whatsapp/elp/test/test_projects/eqwalizer_tests" + +[eqwalizer] +enable_all = true diff --git a/test_projects/eqwalizer_tests/.gitignore b/test/test_projects/eqwalizer_tests/.gitignore similarity index 100% rename from test_projects/eqwalizer_tests/.gitignore rename to test/test_projects/eqwalizer_tests/.gitignore diff --git a/test_projects/eqwalizer_tests/.rebar.root b/test/test_projects/eqwalizer_tests/.rebar.root similarity index 100% rename from test_projects/eqwalizer_tests/.rebar.root rename to test/test_projects/eqwalizer_tests/.rebar.root diff --git a/test_projects/eqwalizer_tests/check/include/my_header.hrl b/test/test_projects/eqwalizer_tests/check/include/my_header.hrl similarity index 100% rename from test_projects/eqwalizer_tests/check/include/my_header.hrl rename to test/test_projects/eqwalizer_tests/check/include/my_header.hrl diff --git a/test_projects/eqwalizer_tests/check/src/any_fun_type.erl b/test/test_projects/eqwalizer_tests/check/src/any_fun_type.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/any_fun_type.erl rename to test/test_projects/eqwalizer_tests/check/src/any_fun_type.erl diff --git a/test_projects/eqwalizer_tests/check/src/apply_none.erl b/test/test_projects/eqwalizer_tests/check/src/apply_none.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/apply_none.erl rename to test/test_projects/eqwalizer_tests/check/src/apply_none.erl diff --git a/test_projects/eqwalizer_tests/check/src/approx.erl b/test/test_projects/eqwalizer_tests/check/src/approx.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/approx.erl rename to test/test_projects/eqwalizer_tests/check/src/approx.erl diff --git a/test_projects/eqwalizer_tests/check/src/as_pat.erl b/test/test_projects/eqwalizer_tests/check/src/as_pat.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/as_pat.erl rename to test/test_projects/eqwalizer_tests/check/src/as_pat.erl diff --git a/test_projects/eqwalizer_tests/check/src/auto_imports.erl b/test/test_projects/eqwalizer_tests/check/src/auto_imports.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/auto_imports.erl rename to test/test_projects/eqwalizer_tests/check/src/auto_imports.erl diff --git a/test_projects/eqwalizer_tests/check/src/behave.erl b/test/test_projects/eqwalizer_tests/check/src/behave.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/behave.erl rename to test/test_projects/eqwalizer_tests/check/src/behave.erl diff --git a/test_projects/eqwalizer_tests/check/src/binaries.erl b/test/test_projects/eqwalizer_tests/check/src/binaries.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/binaries.erl rename to test/test_projects/eqwalizer_tests/check/src/binaries.erl diff --git a/test_projects/eqwalizer_tests/check/src/booleans.erl b/test/test_projects/eqwalizer_tests/check/src/booleans.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/booleans.erl rename to test/test_projects/eqwalizer_tests/check/src/booleans.erl diff --git a/test_projects/eqwalizer_tests/check/src/callbacks1_pos.erl b/test/test_projects/eqwalizer_tests/check/src/callbacks1_pos.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/callbacks1_pos.erl rename to test/test_projects/eqwalizer_tests/check/src/callbacks1_pos.erl diff --git a/test_projects/eqwalizer_tests/check/src/callbacks3_neg.erl b/test/test_projects/eqwalizer_tests/check/src/callbacks3_neg.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/callbacks3_neg.erl rename to test/test_projects/eqwalizer_tests/check/src/callbacks3_neg.erl diff --git a/test_projects/eqwalizer_tests/check/src/callbacks4_neg.erl b/test/test_projects/eqwalizer_tests/check/src/callbacks4_neg.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/callbacks4_neg.erl rename to test/test_projects/eqwalizer_tests/check/src/callbacks4_neg.erl diff --git a/test_projects/eqwalizer_tests/check/src/callbacks5_neg.erl b/test/test_projects/eqwalizer_tests/check/src/callbacks5_neg.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/callbacks5_neg.erl rename to test/test_projects/eqwalizer_tests/check/src/callbacks5_neg.erl diff --git a/test_projects/eqwalizer_tests/check/src/callbacks6_neg.erl b/test/test_projects/eqwalizer_tests/check/src/callbacks6_neg.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/callbacks6_neg.erl rename to test/test_projects/eqwalizer_tests/check/src/callbacks6_neg.erl diff --git a/test_projects/eqwalizer_tests/check/src/callbacks7_overload_pos.erl b/test/test_projects/eqwalizer_tests/check/src/callbacks7_overload_pos.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/callbacks7_overload_pos.erl rename to test/test_projects/eqwalizer_tests/check/src/callbacks7_overload_pos.erl diff --git a/test_projects/eqwalizer_tests/check/src/case_predicates.erl b/test/test_projects/eqwalizer_tests/check/src/case_predicates.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/case_predicates.erl rename to test/test_projects/eqwalizer_tests/check/src/case_predicates.erl diff --git a/test_projects/eqwalizer_tests/check/src/check.app.src b/test/test_projects/eqwalizer_tests/check/src/check.app.src similarity index 100% rename from test_projects/eqwalizer_tests/check/src/check.app.src rename to test/test_projects/eqwalizer_tests/check/src/check.app.src diff --git a/test_projects/eqwalizer_tests/check/src/compiler_macro.erl b/test/test_projects/eqwalizer_tests/check/src/compiler_macro.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/compiler_macro.erl rename to test/test_projects/eqwalizer_tests/check/src/compiler_macro.erl diff --git a/test_projects/eqwalizer_tests/check/src/complex_maps.erl b/test/test_projects/eqwalizer_tests/check/src/complex_maps.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/complex_maps.erl rename to test/test_projects/eqwalizer_tests/check/src/complex_maps.erl diff --git a/test_projects/eqwalizer_tests/check/src/comprehensions.erl b/test/test_projects/eqwalizer_tests/check/src/comprehensions.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/comprehensions.erl rename to test/test_projects/eqwalizer_tests/check/src/comprehensions.erl diff --git a/test_projects/eqwalizer_tests/check/src/contravariant.erl b/test/test_projects/eqwalizer_tests/check/src/contravariant.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/contravariant.erl rename to test/test_projects/eqwalizer_tests/check/src/contravariant.erl diff --git a/test_projects/eqwalizer_tests/check/src/custom.erl b/test/test_projects/eqwalizer_tests/check/src/custom.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/custom.erl rename to test/test_projects/eqwalizer_tests/check/src/custom.erl diff --git a/test_projects/eqwalizer_tests/check/src/detached_specs1.erl b/test/test_projects/eqwalizer_tests/check/src/detached_specs1.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/detached_specs1.erl rename to test/test_projects/eqwalizer_tests/check/src/detached_specs1.erl diff --git a/test_projects/eqwalizer_tests/check/src/detached_specs2.erl b/test/test_projects/eqwalizer_tests/check/src/detached_specs2.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/detached_specs2.erl rename to test/test_projects/eqwalizer_tests/check/src/detached_specs2.erl diff --git a/test_projects/eqwalizer_tests/check/src/dyn_calls.erl b/test/test_projects/eqwalizer_tests/check/src/dyn_calls.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/dyn_calls.erl rename to test/test_projects/eqwalizer_tests/check/src/dyn_calls.erl diff --git a/test_projects/eqwalizer_tests/check/src/dyn_remote_funs.erl b/test/test_projects/eqwalizer_tests/check/src/dyn_remote_funs.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/dyn_remote_funs.erl rename to test/test_projects/eqwalizer_tests/check/src/dyn_remote_funs.erl diff --git a/test_projects/eqwalizer_tests/check/src/dynamic_callbacks_1.erl b/test/test_projects/eqwalizer_tests/check/src/dynamic_callbacks_1.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/dynamic_callbacks_1.erl rename to test/test_projects/eqwalizer_tests/check/src/dynamic_callbacks_1.erl diff --git a/test_projects/eqwalizer_tests/check/src/dynamic_callbacks_2.erl b/test/test_projects/eqwalizer_tests/check/src/dynamic_callbacks_2.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/dynamic_callbacks_2.erl rename to test/test_projects/eqwalizer_tests/check/src/dynamic_callbacks_2.erl diff --git a/test_projects/eqwalizer_tests/check/src/dynamic_calls.erl b/test/test_projects/eqwalizer_tests/check/src/dynamic_calls.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/dynamic_calls.erl rename to test/test_projects/eqwalizer_tests/check/src/dynamic_calls.erl diff --git a/test_projects/eqwalizer_tests/check/src/dynamic_catch.erl b/test/test_projects/eqwalizer_tests/check/src/dynamic_catch.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/dynamic_catch.erl rename to test/test_projects/eqwalizer_tests/check/src/dynamic_catch.erl diff --git a/test_projects/eqwalizer_tests/check/src/dynamic_fun.erl b/test/test_projects/eqwalizer_tests/check/src/dynamic_fun.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/dynamic_fun.erl rename to test/test_projects/eqwalizer_tests/check/src/dynamic_fun.erl diff --git a/test_projects/eqwalizer_tests/check/src/dynamic_generics.erl b/test/test_projects/eqwalizer_tests/check/src/dynamic_generics.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/dynamic_generics.erl rename to test/test_projects/eqwalizer_tests/check/src/dynamic_generics.erl diff --git a/test_projects/eqwalizer_tests/check/src/dynamic_local_funs.erl b/test/test_projects/eqwalizer_tests/check/src/dynamic_local_funs.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/dynamic_local_funs.erl rename to test/test_projects/eqwalizer_tests/check/src/dynamic_local_funs.erl diff --git a/test_projects/eqwalizer_tests/check/src/dynamic_receive.erl b/test/test_projects/eqwalizer_tests/check/src/dynamic_receive.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/dynamic_receive.erl rename to test/test_projects/eqwalizer_tests/check/src/dynamic_receive.erl diff --git a/test_projects/eqwalizer_tests/check/src/dynamic_refine.erl b/test/test_projects/eqwalizer_tests/check/src/dynamic_refine.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/dynamic_refine.erl rename to test/test_projects/eqwalizer_tests/check/src/dynamic_refine.erl diff --git a/test_projects/eqwalizer_tests/check/src/dynamic_try_catch.erl b/test/test_projects/eqwalizer_tests/check/src/dynamic_try_catch.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/dynamic_try_catch.erl rename to test/test_projects/eqwalizer_tests/check/src/dynamic_try_catch.erl diff --git a/test_projects/eqwalizer_tests/check/src/elab_clause.erl b/test/test_projects/eqwalizer_tests/check/src/elab_clause.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/elab_clause.erl rename to test/test_projects/eqwalizer_tests/check/src/elab_clause.erl diff --git a/test_projects/eqwalizer_tests/check/src/error_messages.erl b/test/test_projects/eqwalizer_tests/check/src/error_messages.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/error_messages.erl rename to test/test_projects/eqwalizer_tests/check/src/error_messages.erl diff --git a/test_projects/eqwalizer_tests/check/src/fancy_generics.erl b/test/test_projects/eqwalizer_tests/check/src/fancy_generics.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/fancy_generics.erl rename to test/test_projects/eqwalizer_tests/check/src/fancy_generics.erl diff --git a/test_projects/eqwalizer_tests/check/src/format.erl b/test/test_projects/eqwalizer_tests/check/src/format.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/format.erl rename to test/test_projects/eqwalizer_tests/check/src/format.erl diff --git a/test_projects/eqwalizer_tests/check/src/fun_stats.erl b/test/test_projects/eqwalizer_tests/check/src/fun_stats.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/fun_stats.erl rename to test/test_projects/eqwalizer_tests/check/src/fun_stats.erl diff --git a/test_projects/eqwalizer_tests/check/src/fun_stats2.erl b/test/test_projects/eqwalizer_tests/check/src/fun_stats2.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/fun_stats2.erl rename to test/test_projects/eqwalizer_tests/check/src/fun_stats2.erl diff --git a/test_projects/eqwalizer_tests/check/src/funs.erl b/test/test_projects/eqwalizer_tests/check/src/funs.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/funs.erl rename to test/test_projects/eqwalizer_tests/check/src/funs.erl diff --git a/test_projects/eqwalizer_tests/check/src/funs2.erl b/test/test_projects/eqwalizer_tests/check/src/funs2.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/funs2.erl rename to test/test_projects/eqwalizer_tests/check/src/funs2.erl diff --git a/test_projects/eqwalizer_tests/check/src/funs_uncommon.erl b/test/test_projects/eqwalizer_tests/check/src/funs_uncommon.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/funs_uncommon.erl rename to test/test_projects/eqwalizer_tests/check/src/funs_uncommon.erl diff --git a/test_projects/eqwalizer_tests/check/src/generic_fun_application.erl b/test/test_projects/eqwalizer_tests/check/src/generic_fun_application.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/generic_fun_application.erl rename to test/test_projects/eqwalizer_tests/check/src/generic_fun_application.erl diff --git a/test_projects/eqwalizer_tests/check/src/generics_with_unions.erl b/test/test_projects/eqwalizer_tests/check/src/generics_with_unions.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/generics_with_unions.erl rename to test/test_projects/eqwalizer_tests/check/src/generics_with_unions.erl diff --git a/test_projects/eqwalizer_tests/check/src/gradual_bounded.erl b/test/test_projects/eqwalizer_tests/check/src/gradual_bounded.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/gradual_bounded.erl rename to test/test_projects/eqwalizer_tests/check/src/gradual_bounded.erl diff --git a/test_projects/eqwalizer_tests/check/src/gradual_complex_types.erl b/test/test_projects/eqwalizer_tests/check/src/gradual_complex_types.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/gradual_complex_types.erl rename to test/test_projects/eqwalizer_tests/check/src/gradual_complex_types.erl diff --git a/test_projects/eqwalizer_tests/check/src/gradual_custom.erl b/test/test_projects/eqwalizer_tests/check/src/gradual_custom.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/gradual_custom.erl rename to test/test_projects/eqwalizer_tests/check/src/gradual_custom.erl diff --git a/test_projects/eqwalizer_tests/check/src/gradual_lambdas.erl b/test/test_projects/eqwalizer_tests/check/src/gradual_lambdas.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/gradual_lambdas.erl rename to test/test_projects/eqwalizer_tests/check/src/gradual_lambdas.erl diff --git a/test_projects/eqwalizer_tests/check/src/gradual_maybe.erl b/test/test_projects/eqwalizer_tests/check/src/gradual_maybe.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/gradual_maybe.erl rename to test/test_projects/eqwalizer_tests/check/src/gradual_maybe.erl diff --git a/test_projects/eqwalizer_tests/check/src/gradual_misc.erl b/test/test_projects/eqwalizer_tests/check/src/gradual_misc.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/gradual_misc.erl rename to test/test_projects/eqwalizer_tests/check/src/gradual_misc.erl diff --git a/test_projects/eqwalizer_tests/check/src/gradual_overloaded.erl b/test/test_projects/eqwalizer_tests/check/src/gradual_overloaded.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/gradual_overloaded.erl rename to test/test_projects/eqwalizer_tests/check/src/gradual_overloaded.erl diff --git a/test_projects/eqwalizer_tests/check/src/gradual_regression_01.erl b/test/test_projects/eqwalizer_tests/check/src/gradual_regression_01.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/gradual_regression_01.erl rename to test/test_projects/eqwalizer_tests/check/src/gradual_regression_01.erl diff --git a/test_projects/eqwalizer_tests/check/src/gradual_untyped.erl b/test/test_projects/eqwalizer_tests/check/src/gradual_untyped.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/gradual_untyped.erl rename to test/test_projects/eqwalizer_tests/check/src/gradual_untyped.erl diff --git a/test_projects/eqwalizer_tests/check/src/guard_b_connections.erl b/test/test_projects/eqwalizer_tests/check/src/guard_b_connections.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/guard_b_connections.erl rename to test/test_projects/eqwalizer_tests/check/src/guard_b_connections.erl diff --git a/test_projects/eqwalizer_tests/check/src/guards.erl b/test/test_projects/eqwalizer_tests/check/src/guards.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/guards.erl rename to test/test_projects/eqwalizer_tests/check/src/guards.erl diff --git a/test_projects/eqwalizer_tests/check/src/guards_logic.erl b/test/test_projects/eqwalizer_tests/check/src/guards_logic.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/guards_logic.erl rename to test/test_projects/eqwalizer_tests/check/src/guards_logic.erl diff --git a/test_projects/eqwalizer_tests/check/src/guards_simple.erl b/test/test_projects/eqwalizer_tests/check/src/guards_simple.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/guards_simple.erl rename to test/test_projects/eqwalizer_tests/check/src/guards_simple.erl diff --git a/test_projects/eqwalizer_tests/check/src/hints.erl b/test/test_projects/eqwalizer_tests/check/src/hints.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/hints.erl rename to test/test_projects/eqwalizer_tests/check/src/hints.erl diff --git a/test_projects/eqwalizer_tests/check/src/index1.erl b/test/test_projects/eqwalizer_tests/check/src/index1.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/index1.erl rename to test/test_projects/eqwalizer_tests/check/src/index1.erl diff --git a/test_projects/eqwalizer_tests/check/src/index2.erl b/test/test_projects/eqwalizer_tests/check/src/index2.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/index2.erl rename to test/test_projects/eqwalizer_tests/check/src/index2.erl diff --git a/test_projects/eqwalizer_tests/check/src/iolists.erl b/test/test_projects/eqwalizer_tests/check/src/iolists.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/iolists.erl rename to test/test_projects/eqwalizer_tests/check/src/iolists.erl diff --git a/test_projects/eqwalizer_tests/check/src/kp_01.erl b/test/test_projects/eqwalizer_tests/check/src/kp_01.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/kp_01.erl rename to test/test_projects/eqwalizer_tests/check/src/kp_01.erl diff --git a/test_projects/eqwalizer_tests/check/src/kp_02.erl b/test/test_projects/eqwalizer_tests/check/src/kp_02.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/kp_02.erl rename to test/test_projects/eqwalizer_tests/check/src/kp_02.erl diff --git a/test_projects/eqwalizer_tests/check/src/lists_tests.erl b/test/test_projects/eqwalizer_tests/check/src/lists_tests.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/lists_tests.erl rename to test/test_projects/eqwalizer_tests/check/src/lists_tests.erl diff --git a/test_projects/eqwalizer_tests/check/src/misc.erl b/test/test_projects/eqwalizer_tests/check/src/misc.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/misc.erl rename to test/test_projects/eqwalizer_tests/check/src/misc.erl diff --git a/test_projects/eqwalizer_tests/check/src/misc_lib.erl b/test/test_projects/eqwalizer_tests/check/src/misc_lib.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/misc_lib.erl rename to test/test_projects/eqwalizer_tests/check/src/misc_lib.erl diff --git a/test_projects/eqwalizer_tests/check/src/my_behaviour.erl b/test/test_projects/eqwalizer_tests/check/src/my_behaviour.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/my_behaviour.erl rename to test/test_projects/eqwalizer_tests/check/src/my_behaviour.erl diff --git a/test_projects/eqwalizer_tests/check/src/my_gradual_behaviour.erl b/test/test_projects/eqwalizer_tests/check/src/my_gradual_behaviour.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/my_gradual_behaviour.erl rename to test/test_projects/eqwalizer_tests/check/src/my_gradual_behaviour.erl diff --git a/test_projects/eqwalizer_tests/check/src/my_header.erl b/test/test_projects/eqwalizer_tests/check/src/my_header.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/my_header.erl rename to test/test_projects/eqwalizer_tests/check/src/my_header.erl diff --git a/test_projects/eqwalizer_tests/check/src/neg.erl b/test/test_projects/eqwalizer_tests/check/src/neg.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/neg.erl rename to test/test_projects/eqwalizer_tests/check/src/neg.erl diff --git a/test_projects/eqwalizer_tests/check/src/nested1/misc_nested1.erl b/test/test_projects/eqwalizer_tests/check/src/nested1/misc_nested1.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/nested1/misc_nested1.erl rename to test/test_projects/eqwalizer_tests/check/src/nested1/misc_nested1.erl diff --git a/test_projects/eqwalizer_tests/check/src/nested1/nested2/misc_nested2.erl b/test/test_projects/eqwalizer_tests/check/src/nested1/nested2/misc_nested2.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/nested1/nested2/misc_nested2.erl rename to test/test_projects/eqwalizer_tests/check/src/nested1/nested2/misc_nested2.erl diff --git a/test_projects/eqwalizer_tests/check/src/nowarn.erl b/test/test_projects/eqwalizer_tests/check/src/nowarn.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/nowarn.erl rename to test/test_projects/eqwalizer_tests/check/src/nowarn.erl diff --git a/test_projects/eqwalizer_tests/check/src/number_comparisons.erl b/test/test_projects/eqwalizer_tests/check/src/number_comparisons.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/number_comparisons.erl rename to test/test_projects/eqwalizer_tests/check/src/number_comparisons.erl diff --git a/test_projects/eqwalizer_tests/check/src/numbers.erl b/test/test_projects/eqwalizer_tests/check/src/numbers.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/numbers.erl rename to test/test_projects/eqwalizer_tests/check/src/numbers.erl diff --git a/test_projects/eqwalizer_tests/check/src/opaque.erl b/test/test_projects/eqwalizer_tests/check/src/opaque.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/opaque.erl rename to test/test_projects/eqwalizer_tests/check/src/opaque.erl diff --git a/test_projects/eqwalizer_tests/check/src/other.erl b/test/test_projects/eqwalizer_tests/check/src/other.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/other.erl rename to test/test_projects/eqwalizer_tests/check/src/other.erl diff --git a/test_projects/eqwalizer_tests/check/src/otp28.erl b/test/test_projects/eqwalizer_tests/check/src/otp28.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/otp28.erl rename to test/test_projects/eqwalizer_tests/check/src/otp28.erl diff --git a/test_projects/eqwalizer_tests/check/src/otp_opaques.erl b/test/test_projects/eqwalizer_tests/check/src/otp_opaques.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/otp_opaques.erl rename to test/test_projects/eqwalizer_tests/check/src/otp_opaques.erl diff --git a/test_projects/eqwalizer_tests/check/src/overloaded.erl b/test/test_projects/eqwalizer_tests/check/src/overloaded.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/overloaded.erl rename to test/test_projects/eqwalizer_tests/check/src/overloaded.erl diff --git a/test_projects/eqwalizer_tests/check/src/overloaded_specs_union.erl b/test/test_projects/eqwalizer_tests/check/src/overloaded_specs_union.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/overloaded_specs_union.erl rename to test/test_projects/eqwalizer_tests/check/src/overloaded_specs_union.erl diff --git a/test_projects/eqwalizer_tests/check/src/parametricity.erl b/test/test_projects/eqwalizer_tests/check/src/parametricity.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/parametricity.erl rename to test/test_projects/eqwalizer_tests/check/src/parametricity.erl diff --git a/test_projects/eqwalizer_tests/check/src/pats.erl b/test/test_projects/eqwalizer_tests/check/src/pats.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/pats.erl rename to test/test_projects/eqwalizer_tests/check/src/pats.erl diff --git a/test_projects/eqwalizer_tests/check/src/pinned.erl b/test/test_projects/eqwalizer_tests/check/src/pinned.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/pinned.erl rename to test/test_projects/eqwalizer_tests/check/src/pinned.erl diff --git a/test_projects/eqwalizer_tests/check/src/pos.erl b/test/test_projects/eqwalizer_tests/check/src/pos.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/pos.erl rename to test/test_projects/eqwalizer_tests/check/src/pos.erl diff --git a/test_projects/eqwalizer_tests/check/src/records.erl b/test/test_projects/eqwalizer_tests/check/src/records.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/records.erl rename to test/test_projects/eqwalizer_tests/check/src/records.erl diff --git a/test_projects/eqwalizer_tests/check/src/recursive_aliases.erl b/test/test_projects/eqwalizer_tests/check/src/recursive_aliases.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/recursive_aliases.erl rename to test/test_projects/eqwalizer_tests/check/src/recursive_aliases.erl diff --git a/test_projects/eqwalizer_tests/check/src/refine.erl b/test/test_projects/eqwalizer_tests/check/src/refine.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/refine.erl rename to test/test_projects/eqwalizer_tests/check/src/refine.erl diff --git a/test_projects/eqwalizer_tests/check/src/scoping.erl b/test/test_projects/eqwalizer_tests/check/src/scoping.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/scoping.erl rename to test/test_projects/eqwalizer_tests/check/src/scoping.erl diff --git a/test_projects/eqwalizer_tests/check/src/skip.erl b/test/test_projects/eqwalizer_tests/check/src/skip.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/skip.erl rename to test/test_projects/eqwalizer_tests/check/src/skip.erl diff --git a/test_projects/eqwalizer_tests/check/src/static_maybe.erl b/test/test_projects/eqwalizer_tests/check/src/static_maybe.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/static_maybe.erl rename to test/test_projects/eqwalizer_tests/check/src/static_maybe.erl diff --git a/test_projects/eqwalizer_tests/check/src/strict_complex_types.erl b/test/test_projects/eqwalizer_tests/check/src/strict_complex_types.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/strict_complex_types.erl rename to test/test_projects/eqwalizer_tests/check/src/strict_complex_types.erl diff --git a/test_projects/eqwalizer_tests/check/src/strict_fun.erl b/test/test_projects/eqwalizer_tests/check/src/strict_fun.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/strict_fun.erl rename to test/test_projects/eqwalizer_tests/check/src/strict_fun.erl diff --git a/test_projects/eqwalizer_tests/check/src/strict_receive.erl b/test/test_projects/eqwalizer_tests/check/src/strict_receive.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/strict_receive.erl rename to test/test_projects/eqwalizer_tests/check/src/strict_receive.erl diff --git a/test_projects/eqwalizer_tests/check/src/subtype_neg.erl b/test/test_projects/eqwalizer_tests/check/src/subtype_neg.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/subtype_neg.erl rename to test/test_projects/eqwalizer_tests/check/src/subtype_neg.erl diff --git a/test_projects/eqwalizer_tests/check/src/subtype_pos.erl b/test/test_projects/eqwalizer_tests/check/src/subtype_pos.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/subtype_pos.erl rename to test/test_projects/eqwalizer_tests/check/src/subtype_pos.erl diff --git a/test_projects/eqwalizer_tests/check/src/t_maps.erl b/test/test_projects/eqwalizer_tests/check/src/t_maps.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/t_maps.erl rename to test/test_projects/eqwalizer_tests/check/src/t_maps.erl diff --git a/test_projects/eqwalizer_tests/check/src/tagged_tuples.erl b/test/test_projects/eqwalizer_tests/check/src/tagged_tuples.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/tagged_tuples.erl rename to test/test_projects/eqwalizer_tests/check/src/tagged_tuples.erl diff --git a/test_projects/eqwalizer_tests/check/src/test.erl b/test/test_projects/eqwalizer_tests/check/src/test.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/test.erl rename to test/test_projects/eqwalizer_tests/check/src/test.erl diff --git a/test_projects/eqwalizer_tests/check/src/tries.erl b/test/test_projects/eqwalizer_tests/check/src/tries.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/tries.erl rename to test/test_projects/eqwalizer_tests/check/src/tries.erl diff --git a/test_projects/eqwalizer_tests/check/src/tuple_union.erl b/test/test_projects/eqwalizer_tests/check/src/tuple_union.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/tuple_union.erl rename to test/test_projects/eqwalizer_tests/check/src/tuple_union.erl diff --git a/test_projects/eqwalizer_tests/check/src/type_aliases.erl b/test/test_projects/eqwalizer_tests/check/src/type_aliases.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/type_aliases.erl rename to test/test_projects/eqwalizer_tests/check/src/type_aliases.erl diff --git a/test_projects/eqwalizer_tests/check/src/type_asserts.erl b/test/test_projects/eqwalizer_tests/check/src/type_asserts.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/type_asserts.erl rename to test/test_projects/eqwalizer_tests/check/src/type_asserts.erl diff --git a/test_projects/eqwalizer_tests/check/src/type_predicates.erl b/test/test_projects/eqwalizer_tests/check/src/type_predicates.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/type_predicates.erl rename to test/test_projects/eqwalizer_tests/check/src/type_predicates.erl diff --git a/test_projects/eqwalizer_tests/check/src/united_fun.erl b/test/test_projects/eqwalizer_tests/check/src/united_fun.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/united_fun.erl rename to test/test_projects/eqwalizer_tests/check/src/united_fun.erl diff --git a/test_projects/eqwalizer_tests/check/src/unspecced.erl b/test/test_projects/eqwalizer_tests/check/src/unspecced.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/unspecced.erl rename to test/test_projects/eqwalizer_tests/check/src/unspecced.erl diff --git a/test_projects/eqwalizer_tests/check/src/use_dynamic_gradual.erl b/test/test_projects/eqwalizer_tests/check/src/use_dynamic_gradual.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/use_dynamic_gradual.erl rename to test/test_projects/eqwalizer_tests/check/src/use_dynamic_gradual.erl diff --git a/test_projects/eqwalizer_tests/check/src/use_dynamic_strict.erl b/test/test_projects/eqwalizer_tests/check/src/use_dynamic_strict.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/use_dynamic_strict.erl rename to test/test_projects/eqwalizer_tests/check/src/use_dynamic_strict.erl diff --git a/test_projects/eqwalizer_tests/check/src/vars1.erl b/test/test_projects/eqwalizer_tests/check/src/vars1.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/vars1.erl rename to test/test_projects/eqwalizer_tests/check/src/vars1.erl diff --git a/test_projects/eqwalizer_tests/check/src/vars2.erl b/test/test_projects/eqwalizer_tests/check/src/vars2.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/src/vars2.erl rename to test/test_projects/eqwalizer_tests/check/src/vars2.erl diff --git a/test_projects/eqwalizer_tests/check/test/check_SUITE.erl b/test/test_projects/eqwalizer_tests/check/test/check_SUITE.erl similarity index 100% rename from test_projects/eqwalizer_tests/check/test/check_SUITE.erl rename to test/test_projects/eqwalizer_tests/check/test/check_SUITE.erl diff --git a/test_projects/eqwalizer_tests/debug/include/debug_header.hrl b/test/test_projects/eqwalizer_tests/debug/include/debug_header.hrl similarity index 100% rename from test_projects/eqwalizer_tests/debug/include/debug_header.hrl rename to test/test_projects/eqwalizer_tests/debug/include/debug_header.hrl diff --git a/test_projects/eqwalizer_tests/debug/src/attributes.erl b/test/test_projects/eqwalizer_tests/debug/src/attributes.erl similarity index 100% rename from test_projects/eqwalizer_tests/debug/src/attributes.erl rename to test/test_projects/eqwalizer_tests/debug/src/attributes.erl diff --git a/test_projects/eqwalizer_tests/debug/src/debug.app.src b/test/test_projects/eqwalizer_tests/debug/src/debug.app.src similarity index 100% rename from test_projects/eqwalizer_tests/debug/src/debug.app.src rename to test/test_projects/eqwalizer_tests/debug/src/debug.app.src diff --git a/test_projects/eqwalizer_tests/debug/src/debug_header.erl b/test/test_projects/eqwalizer_tests/debug/src/debug_header.erl similarity index 100% rename from test_projects/eqwalizer_tests/debug/src/debug_header.erl rename to test/test_projects/eqwalizer_tests/debug/src/debug_header.erl diff --git a/test_projects/eqwalizer_tests/debug/src/expand.erl b/test/test_projects/eqwalizer_tests/debug/src/expand.erl similarity index 100% rename from test_projects/eqwalizer_tests/debug/src/expand.erl rename to test/test_projects/eqwalizer_tests/debug/src/expand.erl diff --git a/test_projects/eqwalizer_tests/debug/src/expr1.erl b/test/test_projects/eqwalizer_tests/debug/src/expr1.erl similarity index 100% rename from test_projects/eqwalizer_tests/debug/src/expr1.erl rename to test/test_projects/eqwalizer_tests/debug/src/expr1.erl diff --git a/test_projects/eqwalizer_tests/debug/src/expr2.erl b/test/test_projects/eqwalizer_tests/debug/src/expr2.erl similarity index 100% rename from test_projects/eqwalizer_tests/debug/src/expr2.erl rename to test/test_projects/eqwalizer_tests/debug/src/expr2.erl diff --git a/test_projects/eqwalizer_tests/debug/src/records_wip.erl b/test/test_projects/eqwalizer_tests/debug/src/records_wip.erl similarity index 100% rename from test_projects/eqwalizer_tests/debug/src/records_wip.erl rename to test/test_projects/eqwalizer_tests/debug/src/records_wip.erl diff --git a/test_projects/eqwalizer_tests/debug/src/types1.erl b/test/test_projects/eqwalizer_tests/debug/src/types1.erl similarity index 100% rename from test_projects/eqwalizer_tests/debug/src/types1.erl rename to test/test_projects/eqwalizer_tests/debug/src/types1.erl diff --git a/test_projects/eqwalizer_tests/debug/src/types2.erl b/test/test_projects/eqwalizer_tests/debug/src/types2.erl similarity index 100% rename from test_projects/eqwalizer_tests/debug/src/types2.erl rename to test/test_projects/eqwalizer_tests/debug/src/types2.erl diff --git a/test_projects/eqwalizer_tests/debug/src/wip_maps.erl b/test/test_projects/eqwalizer_tests/debug/src/wip_maps.erl similarity index 100% rename from test_projects/eqwalizer_tests/debug/src/wip_maps.erl rename to test/test_projects/eqwalizer_tests/debug/src/wip_maps.erl diff --git a/test_projects/eqwalizer_tests/elm_core/src/basics.erl b/test/test_projects/eqwalizer_tests/elm_core/src/basics.erl similarity index 100% rename from test_projects/eqwalizer_tests/elm_core/src/basics.erl rename to test/test_projects/eqwalizer_tests/elm_core/src/basics.erl diff --git a/test_projects/eqwalizer_tests/elm_core/src/elm_core.app.src b/test/test_projects/eqwalizer_tests/elm_core/src/elm_core.app.src similarity index 100% rename from test_projects/eqwalizer_tests/elm_core/src/elm_core.app.src rename to test/test_projects/eqwalizer_tests/elm_core/src/elm_core.app.src diff --git a/test_projects/eqwalizer_tests/elm_core/src/list.erl b/test/test_projects/eqwalizer_tests/elm_core/src/list.erl similarity index 100% rename from test_projects/eqwalizer_tests/elm_core/src/list.erl rename to test/test_projects/eqwalizer_tests/elm_core/src/list.erl diff --git a/test_projects/eqwalizer_tests/elm_core/src/map.erl b/test/test_projects/eqwalizer_tests/elm_core/src/map.erl similarity index 100% rename from test_projects/eqwalizer_tests/elm_core/src/map.erl rename to test/test_projects/eqwalizer_tests/elm_core/src/map.erl diff --git a/test_projects/eqwalizer_tests/elm_core/src/map_ffi.erl b/test/test_projects/eqwalizer_tests/elm_core/src/map_ffi.erl similarity index 100% rename from test_projects/eqwalizer_tests/elm_core/src/map_ffi.erl rename to test/test_projects/eqwalizer_tests/elm_core/src/map_ffi.erl diff --git a/test_projects/eqwalizer_tests/elm_core/src/maybe.erl b/test/test_projects/eqwalizer_tests/elm_core/src/maybe.erl similarity index 100% rename from test_projects/eqwalizer_tests/elm_core/src/maybe.erl rename to test/test_projects/eqwalizer_tests/elm_core/src/maybe.erl diff --git a/test_projects/eqwalizer_tests/elm_core/src/result.erl b/test/test_projects/eqwalizer_tests/elm_core/src/result.erl similarity index 100% rename from test_projects/eqwalizer_tests/elm_core/src/result.erl rename to test/test_projects/eqwalizer_tests/elm_core/src/result.erl diff --git a/test_projects/eqwalizer_tests/elm_core/src/tuple.erl b/test/test_projects/eqwalizer_tests/elm_core/src/tuple.erl similarity index 100% rename from test_projects/eqwalizer_tests/elm_core/src/tuple.erl rename to test/test_projects/eqwalizer_tests/elm_core/src/tuple.erl diff --git a/test_projects/eqwalizer_tests/eqwalizer/include/eqwalizer.hrl b/test/test_projects/eqwalizer_tests/eqwalizer/include/eqwalizer.hrl similarity index 100% rename from test_projects/eqwalizer_tests/eqwalizer/include/eqwalizer.hrl rename to test/test_projects/eqwalizer_tests/eqwalizer/include/eqwalizer.hrl diff --git a/test_projects/eqwalizer_tests/eqwalizer/src/eqwalizer.app.src b/test/test_projects/eqwalizer_tests/eqwalizer/src/eqwalizer.app.src similarity index 100% rename from test_projects/eqwalizer_tests/eqwalizer/src/eqwalizer.app.src rename to test/test_projects/eqwalizer_tests/eqwalizer/src/eqwalizer.app.src diff --git a/test_projects/eqwalizer_tests/eqwalizer/src/eqwalizer.erl b/test/test_projects/eqwalizer_tests/eqwalizer/src/eqwalizer.erl similarity index 100% rename from test_projects/eqwalizer_tests/eqwalizer/src/eqwalizer.erl rename to test/test_projects/eqwalizer_tests/eqwalizer/src/eqwalizer.erl diff --git a/test_projects/eqwalizer_tests/eqwalizer/src/eqwalizer_specs.erl b/test/test_projects/eqwalizer_tests/eqwalizer/src/eqwalizer_specs.erl similarity index 100% rename from test_projects/eqwalizer_tests/eqwalizer/src/eqwalizer_specs.erl rename to test/test_projects/eqwalizer_tests/eqwalizer/src/eqwalizer_specs.erl diff --git a/test_projects/eqwalizer_tests/eqwalizer_rebar3/src/eqwalizer_build_info_prv.erl b/test/test_projects/eqwalizer_tests/eqwalizer_rebar3/src/eqwalizer_build_info_prv.erl similarity index 100% rename from test_projects/eqwalizer_tests/eqwalizer_rebar3/src/eqwalizer_build_info_prv.erl rename to test/test_projects/eqwalizer_tests/eqwalizer_rebar3/src/eqwalizer_build_info_prv.erl diff --git a/test_projects/eqwalizer_tests/eqwalizer_rebar3/src/eqwalizer_rebar3.app.src b/test/test_projects/eqwalizer_tests/eqwalizer_rebar3/src/eqwalizer_rebar3.app.src similarity index 100% rename from test_projects/eqwalizer_tests/eqwalizer_rebar3/src/eqwalizer_rebar3.app.src rename to test/test_projects/eqwalizer_tests/eqwalizer_rebar3/src/eqwalizer_rebar3.app.src diff --git a/test_projects/eqwalizer_tests/eqwalizer_rebar3/src/eqwalizer_rebar3.erl b/test/test_projects/eqwalizer_tests/eqwalizer_rebar3/src/eqwalizer_rebar3.erl similarity index 100% rename from test_projects/eqwalizer_tests/eqwalizer_rebar3/src/eqwalizer_rebar3.erl rename to test/test_projects/eqwalizer_tests/eqwalizer_rebar3/src/eqwalizer_rebar3.erl diff --git a/test_projects/eqwalizer_tests/eqwater/readme.md b/test/test_projects/eqwalizer_tests/eqwater/readme.md similarity index 100% rename from test_projects/eqwalizer_tests/eqwater/readme.md rename to test/test_projects/eqwalizer_tests/eqwater/readme.md diff --git a/test_projects/eqwalizer_tests/eqwater/src/deep_tuples.erl b/test/test_projects/eqwalizer_tests/eqwater/src/deep_tuples.erl similarity index 100% rename from test_projects/eqwalizer_tests/eqwater/src/deep_tuples.erl rename to test/test_projects/eqwalizer_tests/eqwater/src/deep_tuples.erl diff --git a/test_projects/eqwalizer_tests/eqwater/src/eqwater.app.src b/test/test_projects/eqwalizer_tests/eqwater/src/eqwater.app.src similarity index 100% rename from test_projects/eqwalizer_tests/eqwater/src/eqwater.app.src rename to test/test_projects/eqwalizer_tests/eqwater/src/eqwater.app.src diff --git a/test_projects/eqwalizer_tests/eqwater/src/eqwater.erl b/test/test_projects/eqwalizer_tests/eqwater/src/eqwater.erl similarity index 100% rename from test_projects/eqwalizer_tests/eqwater/src/eqwater.erl rename to test/test_projects/eqwalizer_tests/eqwater/src/eqwater.erl diff --git a/test_projects/eqwalizer_tests/eqwater/src/eqwater_aliases.erl b/test/test_projects/eqwalizer_tests/eqwater/src/eqwater_aliases.erl similarity index 100% rename from test_projects/eqwalizer_tests/eqwater/src/eqwater_aliases.erl rename to test/test_projects/eqwalizer_tests/eqwater/src/eqwater_aliases.erl diff --git a/test_projects/eqwalizer_tests/eqwater/src/eqwater_generics.erl b/test/test_projects/eqwalizer_tests/eqwater/src/eqwater_generics.erl similarity index 100% rename from test_projects/eqwalizer_tests/eqwater/src/eqwater_generics.erl rename to test/test_projects/eqwalizer_tests/eqwater/src/eqwater_generics.erl diff --git a/test_projects/eqwalizer_tests/eqwater/src/eqwater_lists.erl b/test/test_projects/eqwalizer_tests/eqwater/src/eqwater_lists.erl similarity index 100% rename from test_projects/eqwalizer_tests/eqwater/src/eqwater_lists.erl rename to test/test_projects/eqwalizer_tests/eqwater/src/eqwater_lists.erl diff --git a/test_projects/eqwalizer_tests/eqwater/src/eqwater_maps.erl b/test/test_projects/eqwalizer_tests/eqwater/src/eqwater_maps.erl similarity index 100% rename from test_projects/eqwalizer_tests/eqwater/src/eqwater_maps.erl rename to test/test_projects/eqwalizer_tests/eqwater/src/eqwater_maps.erl diff --git a/test_projects/eqwalizer_tests/eqwater/src/eqwater_records.erl b/test/test_projects/eqwalizer_tests/eqwater/src/eqwater_records.erl similarity index 100% rename from test_projects/eqwalizer_tests/eqwater/src/eqwater_records.erl rename to test/test_projects/eqwalizer_tests/eqwater/src/eqwater_records.erl diff --git a/test_projects/eqwalizer_tests/eqwater/src/eqwater_sound.erl b/test/test_projects/eqwalizer_tests/eqwater/src/eqwater_sound.erl similarity index 100% rename from test_projects/eqwalizer_tests/eqwater/src/eqwater_sound.erl rename to test/test_projects/eqwalizer_tests/eqwater/src/eqwater_sound.erl diff --git a/test_projects/eqwalizer_tests/eqwater/src/unlimited_refinement.erl b/test/test_projects/eqwalizer_tests/eqwater/src/unlimited_refinement.erl similarity index 100% rename from test_projects/eqwalizer_tests/eqwater/src/unlimited_refinement.erl rename to test/test_projects/eqwalizer_tests/eqwater/src/unlimited_refinement.erl diff --git a/test_projects/eqwalizer_tests/fault_tolerance/.eqwalizer b/test/test_projects/eqwalizer_tests/fault_tolerance/.eqwalizer similarity index 100% rename from test_projects/eqwalizer_tests/fault_tolerance/.eqwalizer rename to test/test_projects/eqwalizer_tests/fault_tolerance/.eqwalizer diff --git a/test_projects/eqwalizer_tests/fault_tolerance/src/fault_tolerance.app.src b/test/test_projects/eqwalizer_tests/fault_tolerance/src/fault_tolerance.app.src similarity index 100% rename from test_projects/eqwalizer_tests/fault_tolerance/src/fault_tolerance.app.src rename to test/test_projects/eqwalizer_tests/fault_tolerance/src/fault_tolerance.app.src diff --git a/test_projects/eqwalizer_tests/fault_tolerance/src/fault_tolerance.erl b/test/test_projects/eqwalizer_tests/fault_tolerance/src/fault_tolerance.erl similarity index 100% rename from test_projects/eqwalizer_tests/fault_tolerance/src/fault_tolerance.erl rename to test/test_projects/eqwalizer_tests/fault_tolerance/src/fault_tolerance.erl diff --git a/test_projects/eqwalizer_tests/options/src/bad_maps.erl b/test/test_projects/eqwalizer_tests/options/src/bad_maps.erl similarity index 100% rename from test_projects/eqwalizer_tests/options/src/bad_maps.erl rename to test/test_projects/eqwalizer_tests/options/src/bad_maps.erl diff --git a/test_projects/eqwalizer_tests/options/src/dynamic_lambdas.erl b/test/test_projects/eqwalizer_tests/options/src/dynamic_lambdas.erl similarity index 100% rename from test_projects/eqwalizer_tests/options/src/dynamic_lambdas.erl rename to test/test_projects/eqwalizer_tests/options/src/dynamic_lambdas.erl diff --git a/test_projects/eqwalizer_tests/options/src/options.app.src b/test/test_projects/eqwalizer_tests/options/src/options.app.src similarity index 100% rename from test_projects/eqwalizer_tests/options/src/options.app.src rename to test/test_projects/eqwalizer_tests/options/src/options.app.src diff --git a/test_projects/eqwalizer_tests/options/src/overloaded_specs_dynamic_result.erl b/test/test_projects/eqwalizer_tests/options/src/overloaded_specs_dynamic_result.erl similarity index 100% rename from test_projects/eqwalizer_tests/options/src/overloaded_specs_dynamic_result.erl rename to test/test_projects/eqwalizer_tests/options/src/overloaded_specs_dynamic_result.erl diff --git a/test_projects/eqwalizer_tests/options/src/redundant_guards.erl b/test/test_projects/eqwalizer_tests/options/src/redundant_guards.erl similarity index 100% rename from test_projects/eqwalizer_tests/options/src/redundant_guards.erl rename to test/test_projects/eqwalizer_tests/options/src/redundant_guards.erl diff --git a/test_projects/eqwalizer_tests/options/src/uncovered_clauses.erl b/test/test_projects/eqwalizer_tests/options/src/uncovered_clauses.erl similarity index 100% rename from test_projects/eqwalizer_tests/options/src/uncovered_clauses.erl rename to test/test_projects/eqwalizer_tests/options/src/uncovered_clauses.erl diff --git a/test_projects/eqwalizer_tests/rebar.config b/test/test_projects/eqwalizer_tests/rebar.config similarity index 100% rename from test_projects/eqwalizer_tests/rebar.config rename to test/test_projects/eqwalizer_tests/rebar.config diff --git a/test/test_projects/hierarchical_config/.elp.toml b/test/test_projects/hierarchical_config/.elp.toml new file mode 100644 index 0000000000..a650134c9f --- /dev/null +++ b/test/test_projects/hierarchical_config/.elp.toml @@ -0,0 +1,5 @@ +[buck] +enabled = true +build_deps = false +included_targets = [ "fbcode//whatsapp/elp/test/test_projects/hierarchical_config/..." ] +source_root = "whatsapp/elp/test/test_projects/hierarchical_config" diff --git a/test_projects/hierarchical_config/.elp_lint.toml b/test/test_projects/hierarchical_config/.elp_lint.toml similarity index 100% rename from test_projects/hierarchical_config/.elp_lint.toml rename to test/test_projects/hierarchical_config/.elp_lint.toml diff --git a/test_projects/hierarchical_config/app_a/.elp_lint.toml b/test/test_projects/hierarchical_config/app_a/.elp_lint.toml similarity index 100% rename from test_projects/hierarchical_config/app_a/.elp_lint.toml rename to test/test_projects/hierarchical_config/app_a/.elp_lint.toml diff --git a/test_projects/hierarchical_config/app_a/src/app_a.app.src b/test/test_projects/hierarchical_config/app_a/src/app_a.app.src similarity index 100% rename from test_projects/hierarchical_config/app_a/src/app_a.app.src rename to test/test_projects/hierarchical_config/app_a/src/app_a.app.src diff --git a/test_projects/hierarchical_config/app_a/src/app_a.erl b/test/test_projects/hierarchical_config/app_a/src/app_a.erl similarity index 100% rename from test_projects/hierarchical_config/app_a/src/app_a.erl rename to test/test_projects/hierarchical_config/app_a/src/app_a.erl diff --git a/test_projects/hierarchical_config/app_b/src/app_b.app.src b/test/test_projects/hierarchical_config/app_b/src/app_b.app.src similarity index 100% rename from test_projects/hierarchical_config/app_b/src/app_b.app.src rename to test/test_projects/hierarchical_config/app_b/src/app_b.app.src diff --git a/test_projects/hierarchical_config/app_b/src/app_b.erl b/test/test_projects/hierarchical_config/app_b/src/app_b.erl similarity index 100% rename from test_projects/hierarchical_config/app_b/src/app_b.erl rename to test/test_projects/hierarchical_config/app_b/src/app_b.erl diff --git a/test_projects/hierarchical_config/rebar.config b/test/test_projects/hierarchical_config/rebar.config similarity index 100% rename from test_projects/hierarchical_config/rebar.config rename to test/test_projects/hierarchical_config/rebar.config diff --git a/test/test_projects/in_place_tests/.elp.toml b/test/test_projects/in_place_tests/.elp.toml new file mode 100644 index 0000000000..64140d2852 --- /dev/null +++ b/test/test_projects/in_place_tests/.elp.toml @@ -0,0 +1,5 @@ +[buck] +enabled = true +build_deps = false +included_targets = [ "fbcode//whatsapp/elp/test/test_projects/in_place_tests/..." ] +source_root = "whatsapp/elp/test/test_projects/in_place_tests" diff --git a/test_projects/in_place_tests/README.md b/test/test_projects/in_place_tests/README.md similarity index 100% rename from test_projects/in_place_tests/README.md rename to test/test_projects/in_place_tests/README.md diff --git a/test_projects/in_place_tests/app_a/extra/app_a.erl b/test/test_projects/in_place_tests/app_a/extra/app_a.erl similarity index 100% rename from test_projects/in_place_tests/app_a/extra/app_a.erl rename to test/test_projects/in_place_tests/app_a/extra/app_a.erl diff --git a/test_projects/in_place_tests/app_a/include/app_a.hrl b/test/test_projects/in_place_tests/app_a/include/app_a.hrl similarity index 100% rename from test_projects/in_place_tests/app_a/include/app_a.hrl rename to test/test_projects/in_place_tests/app_a/include/app_a.hrl diff --git a/test_projects/in_place_tests/app_a/include/broken_diagnostics.hrl b/test/test_projects/in_place_tests/app_a/include/broken_diagnostics.hrl similarity index 100% rename from test_projects/in_place_tests/app_a/include/broken_diagnostics.hrl rename to test/test_projects/in_place_tests/app_a/include/broken_diagnostics.hrl diff --git a/test_projects/in_place_tests/app_a/include/diagnostics.hrl b/test/test_projects/in_place_tests/app_a/include/diagnostics.hrl similarity index 100% rename from test_projects/in_place_tests/app_a/include/diagnostics.hrl rename to test/test_projects/in_place_tests/app_a/include/diagnostics.hrl diff --git a/test_projects/in_place_tests/app_a/src/app_a.app.src b/test/test_projects/in_place_tests/app_a/src/app_a.app.src similarity index 100% rename from test_projects/in_place_tests/app_a/src/app_a.app.src rename to test/test_projects/in_place_tests/app_a/src/app_a.app.src diff --git a/test_projects/in_place_tests/app_a/src/app_a.erl b/test/test_projects/in_place_tests/app_a/src/app_a.erl similarity index 100% rename from test_projects/in_place_tests/app_a/src/app_a.erl rename to test/test_projects/in_place_tests/app_a/src/app_a.erl diff --git a/test_projects/in_place_tests/app_a/src/lints.erl b/test/test_projects/in_place_tests/app_a/src/lints.erl similarity index 100% rename from test_projects/in_place_tests/app_a/src/lints.erl rename to test/test_projects/in_place_tests/app_a/src/lints.erl diff --git a/test_projects/in_place_tests/app_a/test/app_a_SUITE.erl b/test/test_projects/in_place_tests/app_a/test/app_a_SUITE.erl similarity index 100% rename from test_projects/in_place_tests/app_a/test/app_a_SUITE.erl rename to test/test_projects/in_place_tests/app_a/test/app_a_SUITE.erl diff --git a/test_projects/in_place_tests/erlang_ls.config b/test/test_projects/in_place_tests/erlang_ls.config similarity index 100% rename from test_projects/in_place_tests/erlang_ls.config rename to test/test_projects/in_place_tests/erlang_ls.config diff --git a/test_projects/in_place_tests/rebar.config b/test/test_projects/in_place_tests/rebar.config similarity index 100% rename from test_projects/in_place_tests/rebar.config rename to test/test_projects/in_place_tests/rebar.config diff --git a/test/test_projects/include_lib_dependency_test/.elp.toml b/test/test_projects/include_lib_dependency_test/.elp.toml new file mode 100644 index 0000000000..02b5b7d244 --- /dev/null +++ b/test/test_projects/include_lib_dependency_test/.elp.toml @@ -0,0 +1,5 @@ +[buck] +enabled = true +build_deps = false +included_targets = [ "fbcode//whatsapp/elp/test/test_projects/include_lib_dependency_test/..." ] +source_root = "whatsapp/elp/test/test_projects/include_lib_dependency_test" diff --git a/test_projects/include_lib_dependency_test/external_app/include/external_header.hrl b/test/test_projects/include_lib_dependency_test/external_app/include/external_header.hrl similarity index 100% rename from test_projects/include_lib_dependency_test/external_app/include/external_header.hrl rename to test/test_projects/include_lib_dependency_test/external_app/include/external_header.hrl diff --git a/test_projects/include_lib_dependency_test/external_app/src/external_app.app.src b/test/test_projects/include_lib_dependency_test/external_app/src/external_app.app.src similarity index 100% rename from test_projects/include_lib_dependency_test/external_app/src/external_app.app.src rename to test/test_projects/include_lib_dependency_test/external_app/src/external_app.app.src diff --git a/test_projects/include_lib_dependency_test/external_app/src/external_app.erl b/test/test_projects/include_lib_dependency_test/external_app/src/external_app.erl similarity index 100% rename from test_projects/include_lib_dependency_test/external_app/src/external_app.erl rename to test/test_projects/include_lib_dependency_test/external_app/src/external_app.erl diff --git a/test_projects/include_lib_dependency_test/extra_app/include/extra_header.hrl b/test/test_projects/include_lib_dependency_test/extra_app/include/extra_header.hrl similarity index 100% rename from test_projects/include_lib_dependency_test/extra_app/include/extra_header.hrl rename to test/test_projects/include_lib_dependency_test/extra_app/include/extra_header.hrl diff --git a/test_projects/include_lib_dependency_test/extra_app/src/extra_app.app.src b/test/test_projects/include_lib_dependency_test/extra_app/src/extra_app.app.src similarity index 100% rename from test_projects/include_lib_dependency_test/extra_app/src/extra_app.app.src rename to test/test_projects/include_lib_dependency_test/extra_app/src/extra_app.app.src diff --git a/test_projects/include_lib_dependency_test/extra_app/src/extra_app.erl b/test/test_projects/include_lib_dependency_test/extra_app/src/extra_app.erl similarity index 100% rename from test_projects/include_lib_dependency_test/extra_app/src/extra_app.erl rename to test/test_projects/include_lib_dependency_test/extra_app/src/extra_app.erl diff --git a/test_projects/include_lib_dependency_test/main_app/src/main_app.app.src b/test/test_projects/include_lib_dependency_test/main_app/src/main_app.app.src similarity index 100% rename from test_projects/include_lib_dependency_test/main_app/src/main_app.app.src rename to test/test_projects/include_lib_dependency_test/main_app/src/main_app.app.src diff --git a/test_projects/include_lib_dependency_test/main_app/src/main_app.erl b/test/test_projects/include_lib_dependency_test/main_app/src/main_app.erl similarity index 100% rename from test_projects/include_lib_dependency_test/main_app/src/main_app.erl rename to test/test_projects/include_lib_dependency_test/main_app/src/main_app.erl diff --git a/test_projects/include_lib_dependency_test/normal_dep/include/assert.hrl b/test/test_projects/include_lib_dependency_test/normal_dep/include/assert.hrl similarity index 100% rename from test_projects/include_lib_dependency_test/normal_dep/include/assert.hrl rename to test/test_projects/include_lib_dependency_test/normal_dep/include/assert.hrl diff --git a/test_projects/include_lib_dependency_test/normal_dep/src/normal_dep.app.src b/test/test_projects/include_lib_dependency_test/normal_dep/src/normal_dep.app.src similarity index 100% rename from test_projects/include_lib_dependency_test/normal_dep/src/normal_dep.app.src rename to test/test_projects/include_lib_dependency_test/normal_dep/src/normal_dep.app.src diff --git a/test_projects/include_lib_dependency_test/normal_dep/src/normal_dep.erl b/test/test_projects/include_lib_dependency_test/normal_dep/src/normal_dep.erl similarity index 100% rename from test_projects/include_lib_dependency_test/normal_dep/src/normal_dep.erl rename to test/test_projects/include_lib_dependency_test/normal_dep/src/normal_dep.erl diff --git a/test_projects/include_lib_dependency_test/rebar.config b/test/test_projects/include_lib_dependency_test/rebar.config similarity index 100% rename from test_projects/include_lib_dependency_test/rebar.config rename to test/test_projects/include_lib_dependency_test/rebar.config diff --git a/test/test_projects/linter/.elp.toml b/test/test_projects/linter/.elp.toml new file mode 100644 index 0000000000..bc8b86088c --- /dev/null +++ b/test/test_projects/linter/.elp.toml @@ -0,0 +1,5 @@ +[buck] +enabled = true +build_deps = false +included_targets = [ "fbcode//whatsapp/elp/test/test_projects/linter/..." ] +source_root = "whatsapp/elp/test/test_projects/linter" diff --git a/test_projects/linter/.elp_lint.toml b/test/test_projects/linter/.elp_lint.toml similarity index 100% rename from test_projects/linter/.elp_lint.toml rename to test/test_projects/linter/.elp_lint.toml diff --git a/test_projects/linter/.gitignore b/test/test_projects/linter/.gitignore similarity index 100% rename from test_projects/linter/.gitignore rename to test/test_projects/linter/.gitignore diff --git a/test_projects/linter/app_a/include/app_a.hrl b/test/test_projects/linter/app_a/include/app_a.hrl similarity index 100% rename from test_projects/linter/app_a/include/app_a.hrl rename to test/test_projects/linter/app_a/include/app_a.hrl diff --git a/test_projects/linter/app_a/src/app_a.app.src b/test/test_projects/linter/app_a/src/app_a.app.src similarity index 100% rename from test_projects/linter/app_a/src/app_a.app.src rename to test/test_projects/linter/app_a/src/app_a.app.src diff --git a/test_projects/linter/app_a/src/app_a.erl b/test/test_projects/linter/app_a/src/app_a.erl similarity index 100% rename from test_projects/linter/app_a/src/app_a.erl rename to test/test_projects/linter/app_a/src/app_a.erl diff --git a/test_projects/linter/app_a/src/app_a_edoc.erl b/test/test_projects/linter/app_a/src/app_a_edoc.erl similarity index 100% rename from test_projects/linter/app_a/src/app_a_edoc.erl rename to test/test_projects/linter/app_a/src/app_a_edoc.erl diff --git a/test_projects/linter/app_a/src/app_a_ssr.erl b/test/test_projects/linter/app_a/src/app_a_ssr.erl similarity index 100% rename from test_projects/linter/app_a/src/app_a_ssr.erl rename to test/test_projects/linter/app_a/src/app_a_ssr.erl diff --git a/test_projects/linter/app_a/src/app_a_unused_param.erl b/test/test_projects/linter/app_a/src/app_a_unused_param.erl similarity index 100% rename from test_projects/linter/app_a/src/app_a_unused_param.erl rename to test/test_projects/linter/app_a/src/app_a_unused_param.erl diff --git a/test_projects/linter/app_a/src/custom_function_matches.erl b/test/test_projects/linter/app_a/src/custom_function_matches.erl similarity index 100% rename from test_projects/linter/app_a/src/custom_function_matches.erl rename to test/test_projects/linter/app_a/src/custom_function_matches.erl diff --git a/test_projects/linter/app_a/src/expression_updates_literal.erl b/test/test_projects/linter/app_a/src/expression_updates_literal.erl similarity index 100% rename from test_projects/linter/app_a/src/expression_updates_literal.erl rename to test/test_projects/linter/app_a/src/expression_updates_literal.erl diff --git a/test_projects/linter/app_a/src/spelling.erl b/test/test_projects/linter/app_a/src/spelling.erl similarity index 100% rename from test_projects/linter/app_a/src/spelling.erl rename to test/test_projects/linter/app_a/src/spelling.erl diff --git a/test_projects/linter/app_a/test/app_a_SUITE.erl b/test/test_projects/linter/app_a/test/app_a_SUITE.erl similarity index 100% rename from test_projects/linter/app_a/test/app_a_SUITE.erl rename to test/test_projects/linter/app_a/test/app_a_SUITE.erl diff --git a/test_projects/linter/app_a/test/app_a_test_helpers.erl b/test/test_projects/linter/app_a/test/app_a_test_helpers.erl similarity index 100% rename from test_projects/linter/app_a/test/app_a_test_helpers.erl rename to test/test_projects/linter/app_a/test/app_a_test_helpers.erl diff --git a/test_projects/linter/app_a/test/app_a_test_helpers_not_opted_in.erl b/test/test_projects/linter/app_a/test/app_a_test_helpers_not_opted_in.erl similarity index 100% rename from test_projects/linter/app_a/test/app_a_test_helpers_not_opted_in.erl rename to test/test_projects/linter/app_a/test/app_a_test_helpers_not_opted_in.erl diff --git a/test_projects/linter/app_a/test/app_a_unreachable_test_SUITE.erl b/test/test_projects/linter/app_a/test/app_a_unreachable_test_SUITE.erl similarity index 100% rename from test_projects/linter/app_a/test/app_a_unreachable_test_SUITE.erl rename to test/test_projects/linter/app_a/test/app_a_unreachable_test_SUITE.erl diff --git a/test_projects/linter/app_a/test/app_test_helpers_no_errors.erl b/test/test_projects/linter/app_a/test/app_test_helpers_no_errors.erl similarity index 100% rename from test_projects/linter/app_a/test/app_test_helpers_no_errors.erl rename to test/test_projects/linter/app_a/test/app_test_helpers_no_errors.erl diff --git a/test_projects/linter/app_b/src/app_b.app.src b/test/test_projects/linter/app_b/src/app_b.app.src similarity index 100% rename from test_projects/linter/app_b/src/app_b.app.src rename to test/test_projects/linter/app_b/src/app_b.app.src diff --git a/test_projects/linter/app_b/src/app_b.erl b/test/test_projects/linter/app_b/src/app_b.erl similarity index 100% rename from test_projects/linter/app_b/src/app_b.erl rename to test/test_projects/linter/app_b/src/app_b.erl diff --git a/test_projects/linter/app_b/src/app_b_unused_param.erl b/test/test_projects/linter/app_b/src/app_b_unused_param.erl similarity index 100% rename from test_projects/linter/app_b/src/app_b_unused_param.erl rename to test/test_projects/linter/app_b/src/app_b_unused_param.erl diff --git a/test_projects/linter/elp_lint_adhoc.toml b/test/test_projects/linter/elp_lint_adhoc.toml similarity index 100% rename from test_projects/linter/elp_lint_adhoc.toml rename to test/test_projects/linter/elp_lint_adhoc.toml diff --git a/test_projects/linter/elp_lint_custom_function_matches.toml b/test/test_projects/linter/elp_lint_custom_function_matches.toml similarity index 100% rename from test_projects/linter/elp_lint_custom_function_matches.toml rename to test/test_projects/linter/elp_lint_custom_function_matches.toml diff --git a/test_projects/linter/elp_lint_ssr_adhoc.toml b/test/test_projects/linter/elp_lint_ssr_adhoc.toml similarity index 100% rename from test_projects/linter/elp_lint_ssr_adhoc.toml rename to test/test_projects/linter/elp_lint_ssr_adhoc.toml diff --git a/test_projects/linter/elp_lint_ssr_adhoc_parse_fail.toml b/test/test_projects/linter/elp_lint_ssr_adhoc_parse_fail.toml similarity index 100% rename from test_projects/linter/elp_lint_ssr_adhoc_parse_fail.toml rename to test/test_projects/linter/elp_lint_ssr_adhoc_parse_fail.toml diff --git a/test_projects/linter/elp_lint_test1.toml b/test/test_projects/linter/elp_lint_test1.toml similarity index 100% rename from test_projects/linter/elp_lint_test1.toml rename to test/test_projects/linter/elp_lint_test1.toml diff --git a/test_projects/linter/elp_lint_test2.toml b/test/test_projects/linter/elp_lint_test2.toml similarity index 100% rename from test_projects/linter/elp_lint_test2.toml rename to test/test_projects/linter/elp_lint_test2.toml diff --git a/test_projects/linter/elp_lint_test_ignore.toml b/test/test_projects/linter/elp_lint_test_ignore.toml similarity index 100% rename from test_projects/linter/elp_lint_test_ignore.toml rename to test/test_projects/linter/elp_lint_test_ignore.toml diff --git a/test_projects/linter/elp_lint_warnings_as_errors.toml b/test/test_projects/linter/elp_lint_warnings_as_errors.toml similarity index 100% rename from test_projects/linter/elp_lint_warnings_as_errors.toml rename to test/test_projects/linter/elp_lint_warnings_as_errors.toml diff --git a/test_projects/linter/rebar.config b/test/test_projects/linter/rebar.config similarity index 100% rename from test_projects/linter/rebar.config rename to test/test_projects/linter/rebar.config diff --git a/test/test_projects/linter_bad_config/.elp.toml b/test/test_projects/linter_bad_config/.elp.toml new file mode 100644 index 0000000000..bc8b86088c --- /dev/null +++ b/test/test_projects/linter_bad_config/.elp.toml @@ -0,0 +1,5 @@ +[buck] +enabled = true +build_deps = false +included_targets = [ "fbcode//whatsapp/elp/test/test_projects/linter/..." ] +source_root = "whatsapp/elp/test/test_projects/linter" diff --git a/test_projects/linter_bad_config/.elp_lint.toml b/test/test_projects/linter_bad_config/.elp_lint.toml similarity index 100% rename from test_projects/linter_bad_config/.elp_lint.toml rename to test/test_projects/linter_bad_config/.elp_lint.toml diff --git a/test_projects/linter_bad_config/.gitignore b/test/test_projects/linter_bad_config/.gitignore similarity index 100% rename from test_projects/linter_bad_config/.gitignore rename to test/test_projects/linter_bad_config/.gitignore diff --git a/test_projects/linter_bad_config/app_a/include/app_a.hrl b/test/test_projects/linter_bad_config/app_a/include/app_a.hrl similarity index 100% rename from test_projects/linter_bad_config/app_a/include/app_a.hrl rename to test/test_projects/linter_bad_config/app_a/include/app_a.hrl diff --git a/test_projects/linter_bad_config/app_a/src/app_a.app.src b/test/test_projects/linter_bad_config/app_a/src/app_a.app.src similarity index 100% rename from test_projects/linter_bad_config/app_a/src/app_a.app.src rename to test/test_projects/linter_bad_config/app_a/src/app_a.app.src diff --git a/test_projects/linter_bad_config/app_a/src/app_a.erl b/test/test_projects/linter_bad_config/app_a/src/app_a.erl similarity index 100% rename from test_projects/linter_bad_config/app_a/src/app_a.erl rename to test/test_projects/linter_bad_config/app_a/src/app_a.erl diff --git a/test/test_projects/linter_bad_config/linter/.elp.toml b/test/test_projects/linter_bad_config/linter/.elp.toml new file mode 100644 index 0000000000..bc8b86088c --- /dev/null +++ b/test/test_projects/linter_bad_config/linter/.elp.toml @@ -0,0 +1,5 @@ +[buck] +enabled = true +build_deps = false +included_targets = [ "fbcode//whatsapp/elp/test/test_projects/linter/..." ] +source_root = "whatsapp/elp/test/test_projects/linter" diff --git a/test_projects/linter_bad_config/linter/.elp_lint.toml b/test/test_projects/linter_bad_config/linter/.elp_lint.toml similarity index 100% rename from test_projects/linter_bad_config/linter/.elp_lint.toml rename to test/test_projects/linter_bad_config/linter/.elp_lint.toml diff --git a/test_projects/linter_bad_config/linter/.gitignore b/test/test_projects/linter_bad_config/linter/.gitignore similarity index 100% rename from test_projects/linter_bad_config/linter/.gitignore rename to test/test_projects/linter_bad_config/linter/.gitignore diff --git a/test_projects/linter_bad_config/linter/app_a/include/app_a.hrl b/test/test_projects/linter_bad_config/linter/app_a/include/app_a.hrl similarity index 100% rename from test_projects/linter_bad_config/linter/app_a/include/app_a.hrl rename to test/test_projects/linter_bad_config/linter/app_a/include/app_a.hrl diff --git a/test_projects/linter_bad_config/linter/app_a/src/app_a.app.src b/test/test_projects/linter_bad_config/linter/app_a/src/app_a.app.src similarity index 100% rename from test_projects/linter_bad_config/linter/app_a/src/app_a.app.src rename to test/test_projects/linter_bad_config/linter/app_a/src/app_a.app.src diff --git a/test_projects/linter_bad_config/linter/app_a/src/app_a.erl b/test/test_projects/linter_bad_config/linter/app_a/src/app_a.erl similarity index 100% rename from test_projects/linter_bad_config/linter/app_a/src/app_a.erl rename to test/test_projects/linter_bad_config/linter/app_a/src/app_a.erl diff --git a/test_projects/linter_bad_config/linter/app_a/src/app_a_unused_param.erl b/test/test_projects/linter_bad_config/linter/app_a/src/app_a_unused_param.erl similarity index 100% rename from test_projects/linter_bad_config/linter/app_a/src/app_a_unused_param.erl rename to test/test_projects/linter_bad_config/linter/app_a/src/app_a_unused_param.erl diff --git a/test_projects/linter_bad_config/linter/app_a/test/app_a_SUITE.erl b/test/test_projects/linter_bad_config/linter/app_a/test/app_a_SUITE.erl similarity index 100% rename from test_projects/linter_bad_config/linter/app_a/test/app_a_SUITE.erl rename to test/test_projects/linter_bad_config/linter/app_a/test/app_a_SUITE.erl diff --git a/test_projects/linter_bad_config/linter/app_a/test/app_a_test_helpers.erl b/test/test_projects/linter_bad_config/linter/app_a/test/app_a_test_helpers.erl similarity index 100% rename from test_projects/linter_bad_config/linter/app_a/test/app_a_test_helpers.erl rename to test/test_projects/linter_bad_config/linter/app_a/test/app_a_test_helpers.erl diff --git a/test_projects/linter_bad_config/linter/app_a/test/app_a_test_helpers_not_opted_in.erl b/test/test_projects/linter_bad_config/linter/app_a/test/app_a_test_helpers_not_opted_in.erl similarity index 100% rename from test_projects/linter_bad_config/linter/app_a/test/app_a_test_helpers_not_opted_in.erl rename to test/test_projects/linter_bad_config/linter/app_a/test/app_a_test_helpers_not_opted_in.erl diff --git a/test_projects/linter_bad_config/linter/app_a/test/app_test_helpers_no_errors.erl b/test/test_projects/linter_bad_config/linter/app_a/test/app_test_helpers_no_errors.erl similarity index 100% rename from test_projects/linter_bad_config/linter/app_a/test/app_test_helpers_no_errors.erl rename to test/test_projects/linter_bad_config/linter/app_a/test/app_test_helpers_no_errors.erl diff --git a/test_projects/linter_bad_config/linter/app_b/src/app_b.app.src b/test/test_projects/linter_bad_config/linter/app_b/src/app_b.app.src similarity index 100% rename from test_projects/linter_bad_config/linter/app_b/src/app_b.app.src rename to test/test_projects/linter_bad_config/linter/app_b/src/app_b.app.src diff --git a/test_projects/linter_bad_config/linter/app_b/src/app_b.erl b/test/test_projects/linter_bad_config/linter/app_b/src/app_b.erl similarity index 100% rename from test_projects/linter_bad_config/linter/app_b/src/app_b.erl rename to test/test_projects/linter_bad_config/linter/app_b/src/app_b.erl diff --git a/test_projects/linter_bad_config/linter/app_b/src/app_b_unused_param.erl b/test/test_projects/linter_bad_config/linter/app_b/src/app_b_unused_param.erl similarity index 100% rename from test_projects/linter_bad_config/linter/app_b/src/app_b_unused_param.erl rename to test/test_projects/linter_bad_config/linter/app_b/src/app_b_unused_param.erl diff --git a/test_projects/linter_bad_config/linter/rebar.config b/test/test_projects/linter_bad_config/linter/rebar.config similarity index 100% rename from test_projects/linter_bad_config/linter/rebar.config rename to test/test_projects/linter_bad_config/linter/rebar.config diff --git a/test_projects/linter_bad_config/rebar.config b/test/test_projects/linter_bad_config/rebar.config similarity index 100% rename from test_projects/linter_bad_config/rebar.config rename to test/test_projects/linter_bad_config/rebar.config diff --git a/test/test_projects/parse_error/.elp.toml b/test/test_projects/parse_error/.elp.toml new file mode 100644 index 0000000000..a38e24f657 --- /dev/null +++ b/test/test_projects/parse_error/.elp.toml @@ -0,0 +1,8 @@ +[buck] +enabled = true +build_deps = false +included_targets = [ "fbcode//whatsapp/elp/test/test_projects/parse_error/..." ] +source_root = "whatsapp/elp/test/test_projects/parse_error" + +[eqwalizer] +enable_all = false diff --git a/test_projects/parse_error/.gitignore b/test/test_projects/parse_error/.gitignore similarity index 100% rename from test_projects/parse_error/.gitignore rename to test/test_projects/parse_error/.gitignore diff --git a/test_projects/parse_error/.rebar.root b/test/test_projects/parse_error/.rebar.root similarity index 100% rename from test_projects/parse_error/.rebar.root rename to test/test_projects/parse_error/.rebar.root diff --git a/test_projects/parse_error/eqwalizer/src/eqwalizer.app.src b/test/test_projects/parse_error/eqwalizer/src/eqwalizer.app.src similarity index 100% rename from test_projects/parse_error/eqwalizer/src/eqwalizer.app.src rename to test/test_projects/parse_error/eqwalizer/src/eqwalizer.app.src diff --git a/test_projects/parse_error/eqwalizer/src/eqwalizer.erl b/test/test_projects/parse_error/eqwalizer/src/eqwalizer.erl similarity index 100% rename from test_projects/parse_error/eqwalizer/src/eqwalizer.erl rename to test/test_projects/parse_error/eqwalizer/src/eqwalizer.erl diff --git a/test_projects/parse_error/eqwalizer/src/eqwalizer_specs.erl b/test/test_projects/parse_error/eqwalizer/src/eqwalizer_specs.erl similarity index 100% rename from test_projects/parse_error/eqwalizer/src/eqwalizer_specs.erl rename to test/test_projects/parse_error/eqwalizer/src/eqwalizer_specs.erl diff --git a/test_projects/parse_error/parse_error_a/src/parse_error_a.app.src b/test/test_projects/parse_error/parse_error_a/src/parse_error_a.app.src similarity index 100% rename from test_projects/parse_error/parse_error_a/src/parse_error_a.app.src rename to test/test_projects/parse_error/parse_error_a/src/parse_error_a.app.src diff --git a/test_projects/parse_error/parse_error_a/src/parse_error_a.erl b/test/test_projects/parse_error/parse_error_a/src/parse_error_a.erl similarity index 100% rename from test_projects/parse_error/parse_error_a/src/parse_error_a.erl rename to test/test_projects/parse_error/parse_error_a/src/parse_error_a.erl diff --git a/test_projects/parse_error/parse_error_a/src/parse_error_a_bad.erl b/test/test_projects/parse_error/parse_error_a/src/parse_error_a_bad.erl similarity index 100% rename from test_projects/parse_error/parse_error_a/src/parse_error_a_bad.erl rename to test/test_projects/parse_error/parse_error_a/src/parse_error_a_bad.erl diff --git a/test_projects/parse_error/parse_error_a/src/parse_error_a_reference_bad.erl b/test/test_projects/parse_error/parse_error_a/src/parse_error_a_reference_bad.erl similarity index 100% rename from test_projects/parse_error/parse_error_a/src/parse_error_a_reference_bad.erl rename to test/test_projects/parse_error/parse_error_a/src/parse_error_a_reference_bad.erl diff --git a/test_projects/parse_error/parse_error_a/src/parse_error_a_syntax_error.erl b/test/test_projects/parse_error/parse_error_a/src/parse_error_a_syntax_error.erl similarity index 100% rename from test_projects/parse_error/parse_error_a/src/parse_error_a_syntax_error.erl rename to test/test_projects/parse_error/parse_error_a/src/parse_error_a_syntax_error.erl diff --git a/test_projects/parse_error/parse_error_a/src/parse_error_a_worst.erl b/test/test_projects/parse_error/parse_error_a/src/parse_error_a_worst.erl similarity index 100% rename from test_projects/parse_error/parse_error_a/src/parse_error_a_worst.erl rename to test/test_projects/parse_error/parse_error_a/src/parse_error_a_worst.erl diff --git a/test_projects/parse_error/rebar.config b/test/test_projects/parse_error/rebar.config similarity index 100% rename from test_projects/parse_error/rebar.config rename to test/test_projects/parse_error/rebar.config diff --git a/test/test_projects/standard/.elp.toml b/test/test_projects/standard/.elp.toml new file mode 100644 index 0000000000..29b2e051a4 --- /dev/null +++ b/test/test_projects/standard/.elp.toml @@ -0,0 +1,5 @@ +[buck] +enabled = true +build_deps = false +included_targets = [ "fbcode//whatsapp/elp/test/test_projects/standard/..." ] +source_root = "whatsapp/elp/test/test_projects/standard" diff --git a/test_projects/standard/.gitignore b/test/test_projects/standard/.gitignore similarity index 100% rename from test_projects/standard/.gitignore rename to test/test_projects/standard/.gitignore diff --git a/test_projects/standard/.rebar.root b/test/test_projects/standard/.rebar.root similarity index 100% rename from test_projects/standard/.rebar.root rename to test/test_projects/standard/.rebar.root diff --git a/test_projects/standard/app_a/.eqwalizer b/test/test_projects/standard/app_a/.eqwalizer similarity index 100% rename from test_projects/standard/app_a/.eqwalizer rename to test/test_projects/standard/app_a/.eqwalizer diff --git a/test_projects/standard/app_a/extra/app_a.erl b/test/test_projects/standard/app_a/extra/app_a.erl similarity index 100% rename from test_projects/standard/app_a/extra/app_a.erl rename to test/test_projects/standard/app_a/extra/app_a.erl diff --git a/test_projects/standard/app_a/include/app_a.hrl b/test/test_projects/standard/app_a/include/app_a.hrl similarity index 100% rename from test_projects/standard/app_a/include/app_a.hrl rename to test/test_projects/standard/app_a/include/app_a.hrl diff --git a/test_projects/standard/app_a/src/app_a.app.src b/test/test_projects/standard/app_a/src/app_a.app.src similarity index 100% rename from test_projects/standard/app_a/src/app_a.app.src rename to test/test_projects/standard/app_a/src/app_a.app.src diff --git a/test_projects/standard/app_a/src/app_a.erl b/test/test_projects/standard/app_a/src/app_a.erl similarity index 100% rename from test_projects/standard/app_a/src/app_a.erl rename to test/test_projects/standard/app_a/src/app_a.erl diff --git a/test_projects/standard/app_a/src/app_a_errors_generated.erl b/test/test_projects/standard/app_a/src/app_a_errors_generated.erl similarity index 100% rename from test_projects/standard/app_a/src/app_a_errors_generated.erl rename to test/test_projects/standard/app_a/src/app_a_errors_generated.erl diff --git a/test_projects/standard/app_a/src/app_a_fixme.erl b/test/test_projects/standard/app_a/src/app_a_fixme.erl similarity index 100% rename from test_projects/standard/app_a/src/app_a_fixme.erl rename to test/test_projects/standard/app_a/src/app_a_fixme.erl diff --git a/test_projects/standard/app_a/src/app_a_ignored.erl b/test/test_projects/standard/app_a/src/app_a_ignored.erl similarity index 100% rename from test_projects/standard/app_a/src/app_a_ignored.erl rename to test/test_projects/standard/app_a/src/app_a_ignored.erl diff --git a/test_projects/standard/app_a/src/app_a_lists.erl b/test/test_projects/standard/app_a/src/app_a_lists.erl similarity index 100% rename from test_projects/standard/app_a/src/app_a_lists.erl rename to test/test_projects/standard/app_a/src/app_a_lists.erl diff --git a/test_projects/standard/app_a/src/app_a_mod2.erl b/test/test_projects/standard/app_a/src/app_a_mod2.erl similarity index 100% rename from test_projects/standard/app_a/src/app_a_mod2.erl rename to test/test_projects/standard/app_a/src/app_a_mod2.erl diff --git a/test_projects/standard/app_a/src/app_a_no_errors.erl b/test/test_projects/standard/app_a/src/app_a_no_errors.erl similarity index 100% rename from test_projects/standard/app_a/src/app_a_no_errors.erl rename to test/test_projects/standard/app_a/src/app_a_no_errors.erl diff --git a/test_projects/standard/app_a/src/app_a_no_errors_generated.erl b/test/test_projects/standard/app_a/src/app_a_no_errors_generated.erl similarity index 100% rename from test_projects/standard/app_a/src/app_a_no_errors_generated.erl rename to test/test_projects/standard/app_a/src/app_a_no_errors_generated.erl diff --git a/test_projects/standard/app_a/src/app_a_no_errors_opted_in.erl b/test/test_projects/standard/app_a/src/app_a_no_errors_opted_in.erl similarity index 100% rename from test_projects/standard/app_a/src/app_a_no_errors_opted_in.erl rename to test/test_projects/standard/app_a/src/app_a_no_errors_opted_in.erl diff --git a/test_projects/standard/app_a/test/app_a_SUITE.erl b/test/test_projects/standard/app_a/test/app_a_SUITE.erl similarity index 100% rename from test_projects/standard/app_a/test/app_a_SUITE.erl rename to test/test_projects/standard/app_a/test/app_a_SUITE.erl diff --git a/test_projects/standard/app_a/test/app_a_test_helpers.erl b/test/test_projects/standard/app_a/test/app_a_test_helpers.erl similarity index 100% rename from test_projects/standard/app_a/test/app_a_test_helpers.erl rename to test/test_projects/standard/app_a/test/app_a_test_helpers.erl diff --git a/test_projects/standard/app_a/test/app_a_test_helpers_not_opted_in.erl b/test/test_projects/standard/app_a/test/app_a_test_helpers_not_opted_in.erl similarity index 100% rename from test_projects/standard/app_a/test/app_a_test_helpers_not_opted_in.erl rename to test/test_projects/standard/app_a/test/app_a_test_helpers_not_opted_in.erl diff --git a/test_projects/standard/app_a/test/app_test_helpers_no_errors.erl b/test/test_projects/standard/app_a/test/app_test_helpers_no_errors.erl similarity index 100% rename from test_projects/standard/app_a/test/app_test_helpers_no_errors.erl rename to test/test_projects/standard/app_a/test/app_test_helpers_no_errors.erl diff --git a/test_projects/standard/app_b/src/app_b.app.src b/test/test_projects/standard/app_b/src/app_b.app.src similarity index 100% rename from test_projects/standard/app_b/src/app_b.app.src rename to test/test_projects/standard/app_b/src/app_b.app.src diff --git a/test_projects/standard/app_b/src/app_b.erl b/test/test_projects/standard/app_b/src/app_b.erl similarity index 100% rename from test_projects/standard/app_b/src/app_b.erl rename to test/test_projects/standard/app_b/src/app_b.erl diff --git a/test_projects/standard/eqwalizer/src/eqwalizer.app.src b/test/test_projects/standard/eqwalizer/src/eqwalizer.app.src similarity index 100% rename from test_projects/standard/eqwalizer/src/eqwalizer.app.src rename to test/test_projects/standard/eqwalizer/src/eqwalizer.app.src diff --git a/test_projects/standard/eqwalizer/src/eqwalizer.erl b/test/test_projects/standard/eqwalizer/src/eqwalizer.erl similarity index 100% rename from test_projects/standard/eqwalizer/src/eqwalizer.erl rename to test/test_projects/standard/eqwalizer/src/eqwalizer.erl diff --git a/test_projects/standard/eqwalizer/src/eqwalizer_specs.erl b/test/test_projects/standard/eqwalizer/src/eqwalizer_specs.erl similarity index 100% rename from test_projects/standard/eqwalizer/src/eqwalizer_specs.erl rename to test/test_projects/standard/eqwalizer/src/eqwalizer_specs.erl diff --git a/test_projects/standard/erlang_ls.config b/test/test_projects/standard/erlang_ls.config similarity index 100% rename from test_projects/standard/erlang_ls.config rename to test/test_projects/standard/erlang_ls.config diff --git a/test_projects/standard/rebar.config b/test/test_projects/standard/rebar.config similarity index 100% rename from test_projects/standard/rebar.config rename to test/test_projects/standard/rebar.config diff --git a/test/test_projects/xref/.elp.toml b/test/test_projects/xref/.elp.toml new file mode 100644 index 0000000000..0b684fba14 --- /dev/null +++ b/test/test_projects/xref/.elp.toml @@ -0,0 +1,5 @@ +[buck] +enabled = true +build_deps = false +included_targets = [ "fbcode//whatsapp/elp/test/test_projects/xref/..." ] +source_root = "whatsapp/elp/test/test_projects/xref" diff --git a/test_projects/xref/app_a/src/unavailable_type.erl b/test/test_projects/xref/app_a/src/unavailable_type.erl similarity index 100% rename from test_projects/xref/app_a/src/unavailable_type.erl rename to test/test_projects/xref/app_a/src/unavailable_type.erl diff --git a/test_projects/xref/app_b/src/app_b.erl b/test/test_projects/xref/app_b/src/app_b.erl similarity index 100% rename from test_projects/xref/app_b/src/app_b.erl rename to test/test_projects/xref/app_b/src/app_b.erl diff --git a/test_projects/xref/app_c/src/app_c.erl b/test/test_projects/xref/app_c/src/app_c.erl similarity index 100% rename from test_projects/xref/app_c/src/app_c.erl rename to test/test_projects/xref/app_c/src/app_c.erl diff --git a/test_projects/xref/elp_lint_unavailable_type.toml b/test/test_projects/xref/elp_lint_unavailable_type.toml similarity index 100% rename from test_projects/xref/elp_lint_unavailable_type.toml rename to test/test_projects/xref/elp_lint_unavailable_type.toml diff --git a/test_projects/buck_bad_config/.elp.toml b/test_projects/buck_bad_config/.elp.toml deleted file mode 100644 index a891a3dfc5..0000000000 --- a/test_projects/buck_bad_config/.elp.toml +++ /dev/null @@ -1,8 +0,0 @@ -[buck] -enabled = true -build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test_projects/buck_bad_config/..." ] -source_root = "whatsapp/elp/test_projects/buck_bad_config" - -[eqwalizer] -enable_all = false diff --git a/test_projects/buck_tests/.elp.toml b/test_projects/buck_tests/.elp.toml deleted file mode 100644 index acb0799975..0000000000 --- a/test_projects/buck_tests/.elp.toml +++ /dev/null @@ -1,9 +0,0 @@ -[buck] -enabled = true -build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test_projects/buck_tests/..." ] -excluded_targets = [ "fbcode//whatsapp/elp/test_projects/buck_tests:test_elp_ignored" ] -source_root = "whatsapp/elp/test_projects/buck_tests" - -[eqwalizer] -enable_all = false diff --git a/test_projects/buck_tests_2/.elp.toml b/test_projects/buck_tests_2/.elp.toml deleted file mode 100644 index 35f02a72f3..0000000000 --- a/test_projects/buck_tests_2/.elp.toml +++ /dev/null @@ -1,12 +0,0 @@ -[buck] -enabled = true -build_deps = false -included_targets = [ - "fbcode//whatsapp/elp/test_projects/buck_tests_2/util/app_a/...", - "fbcode//whatsapp/elp/test_projects/buck_tests_2:check_include" - ] -excluded_targets = [ "fbcode//whatsapp/elp/test_projects/buck_tests_2:test_elp_ignored" ] -source_root = "whatsapp/elp/test_projects/buck_tests_2" - -[eqwalizer] -enable_all = false diff --git a/test_projects/codegen_test/.elp.toml b/test_projects/codegen_test/.elp.toml deleted file mode 100644 index f0f166bcf8..0000000000 --- a/test_projects/codegen_test/.elp.toml +++ /dev/null @@ -1,8 +0,0 @@ -[buck] -enabled = true -build_deps = true -included_targets = [ "fbcode//whatsapp/elp/test_projects/codegen_test:codegen_test_app" ] -source_root = "whatsapp/elp/test_projects/codegen_test" - -[eqwalizer] -enable_all = true diff --git a/test_projects/diagnostics/.elp.toml b/test_projects/diagnostics/.elp.toml deleted file mode 100644 index e5ee70c992..0000000000 --- a/test_projects/diagnostics/.elp.toml +++ /dev/null @@ -1,8 +0,0 @@ -[buck] -enabled = true -build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test_projects/diagnostics/..." ] -source_root = "whatsapp/elp/test_projects/diagnostics" - -[eqwalizer] -enable_all = false diff --git a/test_projects/end_to_end/.elp.toml b/test_projects/end_to_end/.elp.toml deleted file mode 100644 index f015956e45..0000000000 --- a/test_projects/end_to_end/.elp.toml +++ /dev/null @@ -1,7 +0,0 @@ -[buck] -enabled = true -build_deps = false -included_targets = ["fbcode//whatsapp/elp/test_projects/end_to_end/..."] - -[eqwalizer] -enable_all = false diff --git a/test_projects/eqwalizer_callers/.elp.toml b/test_projects/eqwalizer_callers/.elp.toml deleted file mode 100644 index f4b7f6f6ac..0000000000 --- a/test_projects/eqwalizer_callers/.elp.toml +++ /dev/null @@ -1,5 +0,0 @@ -[buck] -enabled = true -build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test_projects/eqwalizer_callers/..." ] -source_root = "whatsapp/elp/test_projects/eqwalizer_callers" diff --git a/test_projects/eqwalizer_tests/.elp.toml b/test_projects/eqwalizer_tests/.elp.toml deleted file mode 100644 index ed5c60502f..0000000000 --- a/test_projects/eqwalizer_tests/.elp.toml +++ /dev/null @@ -1,8 +0,0 @@ -[buck] -enabled = true -build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test_projects/eqwalizer_tests/..." ] -source_root = "whatsapp/elp/test_projects/eqwalizer_tests" - -[eqwalizer] -enable_all = true diff --git a/test_projects/hierarchical_config/.elp.toml b/test_projects/hierarchical_config/.elp.toml deleted file mode 100644 index 1033c181ee..0000000000 --- a/test_projects/hierarchical_config/.elp.toml +++ /dev/null @@ -1,5 +0,0 @@ -[buck] -enabled = true -build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test_projects/hierarchical_config/..." ] -source_root = "whatsapp/elp/test_projects/hierarchical_config" diff --git a/test_projects/in_place_tests/.elp.toml b/test_projects/in_place_tests/.elp.toml deleted file mode 100644 index b6c251f03c..0000000000 --- a/test_projects/in_place_tests/.elp.toml +++ /dev/null @@ -1,5 +0,0 @@ -[buck] -enabled = true -build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test_projects/in_place_tests/..." ] -source_root = "whatsapp/elp/test_projects/in_place_tests" diff --git a/test_projects/include_lib_dependency_test/.elp.toml b/test_projects/include_lib_dependency_test/.elp.toml deleted file mode 100644 index 65e106a9e2..0000000000 --- a/test_projects/include_lib_dependency_test/.elp.toml +++ /dev/null @@ -1,5 +0,0 @@ -[buck] -enabled = true -build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test_projects/include_lib_dependency_test/..." ] -source_root = "whatsapp/elp/test_projects/include_lib_dependency_test" diff --git a/test_projects/linter/.elp.toml b/test_projects/linter/.elp.toml deleted file mode 100644 index a887ab835c..0000000000 --- a/test_projects/linter/.elp.toml +++ /dev/null @@ -1,5 +0,0 @@ -[buck] -enabled = true -build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test_projects/linter/..." ] -source_root = "whatsapp/elp/test_projects/linter" diff --git a/test_projects/linter_bad_config/.elp.toml b/test_projects/linter_bad_config/.elp.toml deleted file mode 100644 index a887ab835c..0000000000 --- a/test_projects/linter_bad_config/.elp.toml +++ /dev/null @@ -1,5 +0,0 @@ -[buck] -enabled = true -build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test_projects/linter/..." ] -source_root = "whatsapp/elp/test_projects/linter" diff --git a/test_projects/linter_bad_config/linter/.elp.toml b/test_projects/linter_bad_config/linter/.elp.toml deleted file mode 100644 index a887ab835c..0000000000 --- a/test_projects/linter_bad_config/linter/.elp.toml +++ /dev/null @@ -1,5 +0,0 @@ -[buck] -enabled = true -build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test_projects/linter/..." ] -source_root = "whatsapp/elp/test_projects/linter" diff --git a/test_projects/parse_error/.elp.toml b/test_projects/parse_error/.elp.toml deleted file mode 100644 index bf2c1ad580..0000000000 --- a/test_projects/parse_error/.elp.toml +++ /dev/null @@ -1,8 +0,0 @@ -[buck] -enabled = true -build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test_projects/parse_error/..." ] -source_root = "whatsapp/elp/test_projects/parse_error" - -[eqwalizer] -enable_all = false diff --git a/test_projects/standard/.elp.toml b/test_projects/standard/.elp.toml deleted file mode 100644 index 09ff5a4d5a..0000000000 --- a/test_projects/standard/.elp.toml +++ /dev/null @@ -1,5 +0,0 @@ -[buck] -enabled = true -build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test_projects/standard/..." ] -source_root = "whatsapp/elp/test_projects/standard" diff --git a/test_projects/xref/.elp.toml b/test_projects/xref/.elp.toml deleted file mode 100644 index afda912287..0000000000 --- a/test_projects/xref/.elp.toml +++ /dev/null @@ -1,5 +0,0 @@ -[buck] -enabled = true -build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test_projects/xref/..." ] -source_root = "whatsapp/elp/test_projects/xref" From 271065da03c111d0725de341b387df7183731b02 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 17 Dec 2025 01:32:35 -0800 Subject: [PATCH 113/142] 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 --- crates/hir/src/fold.rs | 45 +++++++++++-------- crates/ide/src/codemod_helpers.rs | 4 +- .../src/diagnostics/deprecated_function.rs | 4 +- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/crates/hir/src/fold.rs b/crates/hir/src/fold.rs index 56ea3f56ae..59b12be6fd 100644 --- a/crates/hir/src/fold.rs +++ b/crates/hir/src/fold.rs @@ -339,7 +339,7 @@ pub enum ParentId { #[derive(Debug)] pub struct AnyCallBackCtx<'a> { - pub in_macro: Option, + pub in_macro: Option<(HirIdx, Option>)>, pub parents: &'a Vec, 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, + macro_stack: Vec<(HirIdx, Option>)>, parents: Vec, 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 { + fn in_macro(&self) -> Option<(HirIdx, Option>)> { 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 diff --git a/crates/ide/src/codemod_helpers.rs b/crates/ide/src/codemod_helpers.rs index e06ceafd61..2ecd21e82c 100644 --- a/crates/ide/src/codemod_helpers.rs +++ b/crates/ide/src/codemod_helpers.rs @@ -573,8 +573,8 @@ pub(crate) fn find_call_in_function( }; 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 }; diff --git a/crates/ide/src/diagnostics/deprecated_function.rs b/crates/ide/src/diagnostics/deprecated_function.rs index 469c327eb7..b353d824d2 100644 --- a/crates/ide/src/diagnostics/deprecated_function.rs +++ b/crates/ide/src/diagnostics/deprecated_function.rs @@ -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 }; From 4a83d0eddece5c60bcbf035087784c9319514693 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 17 Dec 2025 01:32:35 -0800 Subject: [PATCH 114/142] BE: extract `bound_vars_by_function` as a method for `sema` 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 --- crates/hir/src/sema.rs | 22 +++++++++++++++++++ .../ide/src/diagnostics/mutable_variable.rs | 20 +---------------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/crates/hir/src/sema.rs b/crates/hir/src/sema.rs index 7d1b416a63..68577d2a2e 100644 --- a/crates/hir/src/sema.rs +++ b/crates/hir/src/sema.rs @@ -1006,6 +1006,28 @@ impl Semantic<'_> { // Folds end // ----------------------------------------------------------------- + pub fn bound_vars_by_function( + &self, + file_id: FileId, + ) -> FxHashMap> { + let bound_vars = self.bound_vars_in_pattern_diagnostic(file_id); + let mut bound_vars_by_function: FxHashMap> = + 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, diff --git a/crates/ide/src/diagnostics/mutable_variable.rs b/crates/ide/src/diagnostics/mutable_variable.rs index 52fe32eccf..6878c90d57 100644 --- a/crates/ide/src/diagnostics/mutable_variable.rs +++ b/crates/ide/src/diagnostics/mutable_variable.rs @@ -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> = - 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)| { From 8ff602f1c3efb9324be46b767e44a36e67bfc880 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 17 Dec 2025 06:00:04 -0800 Subject: [PATCH 115/142] 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 --- crates/elp/src/snapshot.rs | 10 +++ crates/elp/src/to_proto.rs | 108 ++++++++++++++++++++++++++++---- crates/ide/src/rename.rs | 120 +++++++++++++++++++++++++++++++++++- crates/ide_db/src/rename.rs | 57 ++++++++++++++++- 4 files changed, 282 insertions(+), 13 deletions(-) diff --git a/crates/elp/src/snapshot.rs b/crates/elp/src/snapshot.rs index 9aa3e7eeb4..69ea6b97e0 100644 --- a/crates/elp/src/snapshot.rs +++ b/crates/elp/src/snapshot.rs @@ -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 { + 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, diff --git a/crates/elp/src/to_proto.rs b/crates/elp/src/to_proto.rs index 70d316d5c6..8ce887d742 100644 --- a/crates/elp/src/to_proto.rs +++ b/crates/elp/src/to_proto.rs @@ -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; @@ -142,23 +144,107 @@ pub(crate) fn text_document_edit( }) } +pub(crate) fn text_document_ops( + snap: &Snapshot, + file_system_edit: FileSystemEdit, +) -> Cancellable> { + 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 { - 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 = 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 (file_id, edit) in source_change.source_file_edits { + let edit = text_document_edit(snap, file_id, edit)?; + document_changes.push(lsp_types::DocumentChangeOperation::Edit( + lsp_types::TextDocumentEdit { + text_document: edit.text_document, + edits: edit.edits.into_iter().collect(), + }, + )); + } + + 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); + } + } + 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) diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index 0cedbe1d90..8164f0d2db 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -195,8 +195,13 @@ 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; @@ -207,12 +212,16 @@ pub(crate) mod tests { 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 (db_after, fixture_after) = RootDatabase::with_fixture(fixture_after_str); + let host_after = AnalysisHost { db: db_after }; + let analysis_after = host_after.analysis(); let (analysis, position, _) = fixture::position(fixture_before); let rename_result = analysis @@ -232,6 +241,37 @@ 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 { + 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 + ) + }); + let actual = analysis_after.file_text(*new_file.1).unwrap().to_string(); + let expected = initial_contents; + 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 + ) + }); + let actual = analysis_after.file_text(*new_file.1).unwrap().to_string(); + let expected = analysis.file_text(src).unwrap().to_string(); + assert_eq_text!(&*expected, &*actual); + } + } + } } Err(err) => { if fixture_after_str.starts_with("error:") { @@ -247,6 +287,16 @@ pub(crate) mod tests { }; } + fn find_new_file_id<'a>( + fixture_after: &'a ChangeFixture, + dst: &'a AnchoredPathBuf, + ) -> Option<(&'a VfsPath, &'a FileId)> { + fixture_after + .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 +1185,74 @@ 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). + "#, + ); + } + + #[test] + fn rename_module_with_usage() { + 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. + //- /app_a/src/other.erl + -module(other). + -export([bar/0]). + bar() -> main:foo(). + "#, + r#"error: module 'main_2' already exists"#, + ); + } + // --------------------------------- #[track_caller] diff --git a/crates/ide_db/src/rename.rs b/crates/ide_db/src/rename.rs index 889569ca4e..7e44d0bf34 100644 --- a/crates/ide_db/src/rename.rs +++ b/crates/ide_db/src/rename.rs @@ -15,8 +15,10 @@ use std::fmt; use std::iter::once; +use elp_base_db::AnchoredPathBuf; use elp_base_db::FileId; use elp_base_db::FileRange; +use elp_base_db::ModuleName; use elp_syntax::AstNode; use elp_syntax::ast; use elp_syntax::ast::in_erlang_module; @@ -26,6 +28,7 @@ use hir::Semantic; use crate::SymbolDefinition; use crate::helpers::get_call; use crate::search::NameLike; +use crate::source_change::FileSystemEdit; use crate::source_change::SourceChange; use crate::text_edit::TextEdit; @@ -106,6 +109,18 @@ pub fn is_valid_type_name(new_name: &String) -> bool { false } +// Delegate checking module name validity to the parser +pub fn is_valid_module_name(new_name: &String) -> bool { + let parse = ast::SourceFile::parse_text(format!("-module({}).", new_name).as_str()); + match parse.tree().forms().next() { + Some(ast::Form::ModuleAttribute(ma)) => match ma.name() { + Some(ast::Name::Atom(atom)) => atom.syntax().text().to_string() == *new_name, + _ => false, + }, + _ => false, + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SafetyChecks { Yes, @@ -122,7 +137,10 @@ impl SymbolDefinition { ) -> RenameResult { match self.clone() { SymbolDefinition::Module(_) => { - rename_error!("Cannot rename module") + if safety_check == SafetyChecks::Yes && !is_valid_module_name(new_name) { + rename_error!("Invalid new module name: '{}'", new_name); + } + self.rename_module(sema, new_name, safety_check) } SymbolDefinition::Function(fun) => { if safety_check == SafetyChecks::Yes && !is_valid_function_name(new_name) { @@ -382,6 +400,43 @@ impl SymbolDefinition { } } } + + fn rename_module( + &self, + sema: &Semantic, + new_name: &str, + safety_check: SafetyChecks, + ) -> RenameResult { + let file_id = self.file().file_id; + if let Some(project_id) = sema.db.file_project_id(file_id) { + let module_index = sema.db.module_index(project_id); + if safety_check == SafetyChecks::Yes { + let new_name_module = ModuleName::new(new_name); + if module_index + .all_modules() + .iter() + .any(|name| name == &new_name_module) + { + rename_error!("module '{}' already exists", new_name); + } + } + + // RA based version + let mut source_change = SourceChange::default(); + let anchor = file_id; + + let path = format!("{new_name}.erl"); + let dst = AnchoredPathBuf { anchor, path }; + source_change.push_file_system_edit(FileSystemEdit::MoveFile { src: anchor, dst }); + + Ok(source_change) + } else { + rename_error!( + "Could not find project for '{:?}'", + self.file().name(sema.db.upcast()) + ) + } + } } fn source_edit_from_usages( From a0e076cb1762bff8cdbf5e69b158b3d77be9d58d Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 17 Dec 2025 06:00:04 -0800 Subject: [PATCH 116/142] 2/n: rename module: simple rename works Summary: First case, just rename the module Reviewed By: robertoaloi Differential Revision: D87860412 fbshipit-source-id: 0427f09bd2176778e0280b0f45f9331fa279f778 --- crates/ide/src/rename.rs | 6 +++--- crates/ide_db/src/rename.rs | 22 +++++++++++++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index 8164f0d2db..bebd439216 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -288,10 +288,10 @@ pub(crate) mod tests { } fn find_new_file_id<'a>( - fixture_after: &'a ChangeFixture, + fixture: &'a ChangeFixture, dst: &'a AnchoredPathBuf, ) -> Option<(&'a VfsPath, &'a FileId)> { - fixture_after + fixture .files_by_path .iter() .find(|(name, _)| name.as_path().unwrap().to_string().ends_with(&dst.path)) @@ -1226,7 +1226,7 @@ pub(crate) mod tests { "#, r#" //- /app_a/src/main_2.erl - -module(main). + -module(main_2). "#, ); } diff --git a/crates/ide_db/src/rename.rs b/crates/ide_db/src/rename.rs index 7e44d0bf34..141055565f 100644 --- a/crates/ide_db/src/rename.rs +++ b/crates/ide_db/src/rename.rs @@ -421,13 +421,33 @@ impl SymbolDefinition { } } + let mut renamed_module_edits: Vec = Vec::new(); + + let form_list = sema.form_list(file_id); + if let Some(module_attribute) = form_list.module_attribute() { + let ast = module_attribute.form_id.get_ast(sema.db, file_id); + if let Some(name) = ast.name() { + let range = name.syntax().text_range(); + let mut builder = TextEdit::builder(); + builder.replace(range, new_name.to_string()); + renamed_module_edits.push(builder.finish()); + } + } + // RA based version let mut source_change = SourceChange::default(); let anchor = file_id; let path = format!("{new_name}.erl"); let dst = AnchoredPathBuf { anchor, path }; - source_change.push_file_system_edit(FileSystemEdit::MoveFile { src: anchor, dst }); + let mut initial_contents = sema.db.file_text(anchor).to_string(); + for edit in renamed_module_edits { + edit.apply(&mut initial_contents); + } + source_change.push_file_system_edit(FileSystemEdit::CreateFile { + dst, + initial_contents, + }); Ok(source_change) } else { From 56e66e2cd561519a580650a118d65362266eef18 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 17 Dec 2025 06:00:04 -0800 Subject: [PATCH 117/142] 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 --- crates/ide/src/rename.rs | 34 ++++++++++++++++---- crates/ide_db/src/rename.rs | 64 ++++++++++++++++++++++++++++++++----- 2 files changed, 83 insertions(+), 15 deletions(-) diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index bebd439216..cb7c66292c 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -194,6 +194,7 @@ 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; @@ -207,6 +208,7 @@ pub(crate) mod tests { use elp_syntax::AstNode; use elp_syntax::algo; use elp_syntax::ast; + use fxhash::FxHashSet; use hir::AnyExprId; use hir::InFile; use hir::Semantic; @@ -219,19 +221,26 @@ pub(crate) mod tests { 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 (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 (analysis, position, _) = fixture::position(fixture_before); 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 = 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); } @@ -242,6 +251,8 @@ pub(crate) mod tests { assert_eq_text!(&*expected, &*result); } for op in source_change.file_system_edits { + let expected; + let new_file_id; match op { FileSystemEdit::CreateFile { dst, @@ -254,9 +265,8 @@ pub(crate) mod tests { &dst.path ) }); - let actual = analysis_after.file_text(*new_file.1).unwrap().to_string(); - let expected = initial_contents; - assert_eq_text!(&*expected, &*actual); + new_file_id = *new_file.1; + expected = initial_contents; } FileSystemEdit::MoveFile { src, dst } => { let new_file = @@ -266,11 +276,21 @@ pub(crate) mod tests { &dst.path ) }); - let actual = analysis_after.file_text(*new_file.1).unwrap().to_string(); - let expected = analysis.file_text(src).unwrap().to_string(); - assert_eq_text!(&*expected, &*actual); + new_file_id = *new_file.1; + expected = analysis.file_text(src).unwrap().to_string(); } } + file_ids.insert(new_file_id); + let actual = analysis_after.file_text(new_file_id).unwrap().to_string(); + assert_eq_text!(&*expected, &*actual); + } + // 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 = analysis.file_text(*file_id).unwrap().to_string(); + assert_eq_text!(&*expected, &*actual); + } } } Err(err) => { diff --git a/crates/ide_db/src/rename.rs b/crates/ide_db/src/rename.rs index 141055565f..2ad0cd8ff6 100644 --- a/crates/ide_db/src/rename.rs +++ b/crates/ide_db/src/rename.rs @@ -31,6 +31,7 @@ use crate::search::NameLike; use crate::source_change::FileSystemEdit; use crate::source_change::SourceChange; use crate::text_edit::TextEdit; +use crate::text_edit::TextEditBuilder; pub type RenameResult = Result; @@ -421,8 +422,25 @@ impl SymbolDefinition { } } - let mut renamed_module_edits: Vec = Vec::new(); + let mut source_change = SourceChange::default(); + // Step 1, rename all references + let def_map = sema.def_map(file_id); + // process anything that could be used. functions, types, ?? + def_map.get_functions().for_each(|(_name, f)| { + if f.exported { + let usages = SymbolDefinition::Function(f.clone()).usages(sema).all(); + usages.iter().for_each(|(usage_file_id, refs)| { + if usage_file_id != file_id + && let Some(edit) = rename_call_module_in_refs(refs, new_name) + { + source_change.insert_source_edit(usage_file_id, edit); + }; + }); + } + }); + // Make changes in the module being renamed + let mut renamed_module_edit: TextEdit = TextEdit::default(); let form_list = sema.form_list(file_id); if let Some(module_attribute) = form_list.module_attribute() { let ast = module_attribute.form_id.get_ast(sema.db, file_id); @@ -430,20 +448,29 @@ impl SymbolDefinition { let range = name.syntax().text_range(); let mut builder = TextEdit::builder(); builder.replace(range, new_name.to_string()); - renamed_module_edits.push(builder.finish()); + renamed_module_edit + .union(builder.finish()) + .expect("Could not combine TextEdits"); } + def_map.get_functions().for_each(|(_name, f)| { + let usages = SymbolDefinition::Function(f.clone()).usages(sema).all(); + usages.iter().for_each(|(usage_file_id, refs)| { + if usage_file_id == file_id + && let Some(edit) = rename_call_module_in_refs(refs, new_name) + { + renamed_module_edit + .union(edit) + .expect("Could not combine TextEdits"); + } + }); + }); } - // RA based version - let mut source_change = SourceChange::default(); let anchor = file_id; - let path = format!("{new_name}.erl"); let dst = AnchoredPathBuf { anchor, path }; let mut initial_contents = sema.db.file_text(anchor).to_string(); - for edit in renamed_module_edits { - edit.apply(&mut initial_contents); - } + renamed_module_edit.apply(&mut initial_contents); source_change.push_file_system_edit(FileSystemEdit::CreateFile { dst, initial_contents, @@ -459,6 +486,27 @@ impl SymbolDefinition { } } +fn rename_call_module_in_refs(refs: &[NameLike], new_name: &str) -> Option { + let mut builder = TextEdit::builder(); + for usage in refs { + let _ = rename_call_module_in_ref(usage, &mut builder, new_name); + } + Some(builder.finish()) +} + +fn rename_call_module_in_ref( + usage: &NameLike, + builder: &mut TextEditBuilder, + new_name: &str, +) -> Option<()> { + let call = get_call(usage.syntax())?; + let _: () = if let Some(ast::Expr::Remote(remote)) = call.expr() { + let module = remote.module()?.module()?; + builder.replace(module.syntax().text_range(), new_name.to_string()); + }; + Some(()) +} + fn source_edit_from_usages( source_change: &mut SourceChange, usages: Vec<(FileId, &[NameLike])>, From bef3dd15f26cbe6ff3d1c07a3598c37e9faff112 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 17 Dec 2025 06:00:04 -0800 Subject: [PATCH 118/142] 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 --- crates/ide/src/rename.rs | 47 +++++++++++++++++++++--- crates/ide_db/src/rename.rs | 71 ++++++++++++++++++++++++++++--------- 2 files changed, 97 insertions(+), 21 deletions(-) diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index cb7c66292c..a7d963d77f 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -288,7 +288,11 @@ pub(crate) mod tests { 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 = analysis.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); } } @@ -1261,18 +1265,51 @@ pub(crate) mod tests { -export([foo/0]). foo() -> ok. //- /app_a/src/main.erl - -module(ma~in). - -export([foo/0]). - foo() -> ok. + -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_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. + "#, + ); + } + // --------------------------------- #[track_caller] diff --git a/crates/ide_db/src/rename.rs b/crates/ide_db/src/rename.rs index 2ad0cd8ff6..ab6829be3d 100644 --- a/crates/ide_db/src/rename.rs +++ b/crates/ide_db/src/rename.rs @@ -429,13 +429,13 @@ impl SymbolDefinition { def_map.get_functions().for_each(|(_name, f)| { if f.exported { let usages = SymbolDefinition::Function(f.clone()).usages(sema).all(); - usages.iter().for_each(|(usage_file_id, refs)| { - if usage_file_id != file_id - && let Some(edit) = rename_call_module_in_refs(refs, new_name) - { - source_change.insert_source_edit(usage_file_id, edit); - }; - }); + rename_remote_module_call_refs(usages, file_id, new_name, &mut source_change); + } + }); + def_map.get_types().iter().for_each(|(_name, t)| { + if t.exported { + let usages = SymbolDefinition::Type(t.clone()).usages(sema).all(); + rename_remote_module_call_refs(usages, file_id, new_name, &mut source_change); } }); @@ -454,15 +454,21 @@ impl SymbolDefinition { } def_map.get_functions().for_each(|(_name, f)| { let usages = SymbolDefinition::Function(f.clone()).usages(sema).all(); - usages.iter().for_each(|(usage_file_id, refs)| { - if usage_file_id == file_id - && let Some(edit) = rename_call_module_in_refs(refs, new_name) - { - renamed_module_edit - .union(edit) - .expect("Could not combine TextEdits"); - } - }); + rename_own_module_call_refs( + usages, + file_id, + new_name, + &mut renamed_module_edit, + ); + }); + def_map.get_types().iter().for_each(|(_name, t)| { + let usages = SymbolDefinition::Type(t.clone()).usages(sema).all(); + rename_own_module_call_refs( + usages, + file_id, + new_name, + &mut renamed_module_edit, + ); }); } @@ -486,6 +492,38 @@ impl SymbolDefinition { } } +fn rename_remote_module_call_refs( + usages: crate::UsageSearchResult, + file_id: FileId, + new_name: &str, + source_change: &mut SourceChange, +) { + usages.iter().for_each(|(usage_file_id, refs)| { + if usage_file_id != file_id + && let Some(edit) = rename_call_module_in_refs(refs, new_name) + { + source_change.insert_source_edit(usage_file_id, edit); + }; + }); +} + +fn rename_own_module_call_refs( + usages: crate::UsageSearchResult, + file_id: FileId, + new_name: &str, + renamed_module_edit: &mut TextEdit, +) { + usages.iter().for_each(|(usage_file_id, refs)| { + if usage_file_id == file_id + && let Some(edit) = rename_call_module_in_refs(refs, new_name) + { + renamed_module_edit + .union(edit) + .expect("Could not combine TextEdits"); + } + }); +} + fn rename_call_module_in_refs(refs: &[NameLike], new_name: &str) -> Option { let mut builder = TextEdit::builder(); for usage in refs { @@ -500,6 +538,7 @@ fn rename_call_module_in_ref( new_name: &str, ) -> Option<()> { let call = get_call(usage.syntax())?; + // Note: `ast` uses the same syntax for a function call and a type let _: () = if let Some(ast::Expr::Remote(remote)) = call.expr() { let module = remote.module()?.module()?; builder.replace(module.syntax().text_range(), new_name.to_string()); From 8997372e1a02e0b292b6121a5d7ce630df5db149 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 17 Dec 2025 06:00:04 -0800 Subject: [PATCH 119/142] 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 --- crates/elp/src/to_proto.rs | 39 ++++++++++------- crates/ide/src/rename.rs | 70 ++++++++++++++++++++++++++++-- crates/ide_db/src/helpers.rs | 13 +++--- crates/ide_db/src/rename.rs | 40 ++++++----------- crates/ide_db/src/source_change.rs | 58 ++++++++++++++++++++++++- 5 files changed, 169 insertions(+), 51 deletions(-) diff --git a/crates/elp/src/to_proto.rs b/crates/elp/src/to_proto.rs index 8ce887d742..3e5b1fd1e1 100644 --- a/crates/elp/src/to_proto.rs +++ b/crates/elp/src/to_proto.rs @@ -123,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 { - 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> = edit @@ -133,11 +133,6 @@ 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, @@ -225,8 +220,16 @@ pub(crate) fn workspace_edit( } } + 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 edit = text_document_edit(snap, file_id, edit)?; + 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, @@ -235,10 +238,20 @@ pub(crate) fn workspace_edit( )); } - 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); + // 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); } } @@ -268,10 +281,6 @@ pub(crate) fn code_action( ) -> Result { 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, diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index a7d963d77f..884556585c 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -267,8 +267,10 @@ pub(crate) mod tests { }); 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 } => { + FileSystemEdit::MoveFile { src: _, dst } => { let new_file = find_new_file_id(&fixture_after, &dst).unwrap_or_else(|| { panic!( @@ -277,12 +279,40 @@ pub(crate) mod tests { ) }); new_file_id = *new_file.1; - expected = analysis.file_text(src).unwrap().to_string(); + // 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); - let actual = analysis_after.file_text(new_file_id).unwrap().to_string(); - assert_eq_text!(&*expected, &*actual); + } + 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 { @@ -1310,6 +1340,38 @@ pub(crate) mod tests { ); } + #[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()}). + "#, + ); + } + // --------------------------------- #[track_caller] diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs index c92be86f5f..0b26a23b6a 100644 --- a/crates/ide_db/src/helpers.rs +++ b/crates/ide_db/src/helpers.rs @@ -43,12 +43,15 @@ pub fn pick_best_token( tokens.max_by_key(move |t| f(t.kind())) } +/// Given a syntax node, check it it is immediately enclosed in a call, +/// which can represent a function call or a type. +/// For a remote call, the node can be the module or the function name. +/// In the former case, there is an extra level of nesting, so we need +/// to check up to 3 steps up pub fn get_call(syntax: &SyntaxNode) -> Option { - if let Some(call) = ast::Call::cast(syntax.parent()?) { - Some(call) - } else { - ast::Call::cast(syntax.parent()?.parent()?) - } + ast::Call::cast(syntax.parent()?) + .or_else(|| ast::Call::cast(syntax.parent()?.parent()?)) + .or_else(|| ast::Call::cast(syntax.parent()?.parent()?.parent()?)) } /// Find the first position at the top of the file to add a new diff --git a/crates/ide_db/src/rename.rs b/crates/ide_db/src/rename.rs index ab6829be3d..40273d6577 100644 --- a/crates/ide_db/src/rename.rs +++ b/crates/ide_db/src/rename.rs @@ -141,7 +141,7 @@ impl SymbolDefinition { if safety_check == SafetyChecks::Yes && !is_valid_module_name(new_name) { rename_error!("Invalid new module name: '{}'", new_name); } - self.rename_module(sema, new_name, safety_check) + self.rename_reference(sema, new_name, parens_needed_in_context, safety_check) } SymbolDefinition::Function(fun) => { if safety_check == SafetyChecks::Yes && !is_valid_function_name(new_name) { @@ -394,6 +394,7 @@ impl SymbolDefinition { ); Ok(source_change) } + SymbolDefinition::Module(_module) => self.rename_module(sema, new_name, safety_check), // Note: This is basically an internal error, this function is called from // SymbolDefinition::rename which already weeds them out _ => { @@ -424,22 +425,10 @@ impl SymbolDefinition { let mut source_change = SourceChange::default(); // Step 1, rename all references - let def_map = sema.def_map(file_id); - // process anything that could be used. functions, types, ?? - def_map.get_functions().for_each(|(_name, f)| { - if f.exported { - let usages = SymbolDefinition::Function(f.clone()).usages(sema).all(); - rename_remote_module_call_refs(usages, file_id, new_name, &mut source_change); - } - }); - def_map.get_types().iter().for_each(|(_name, t)| { - if t.exported { - let usages = SymbolDefinition::Type(t.clone()).usages(sema).all(); - rename_remote_module_call_refs(usages, file_id, new_name, &mut source_change); - } - }); + let usages = self.clone().usages(sema).all(); + rename_remote_module_call_refs(usages, file_id, new_name, &mut source_change); - // Make changes in the module being renamed + // Step 2: Make changes in the module being renamed let mut renamed_module_edit: TextEdit = TextEdit::default(); let form_list = sema.form_list(file_id); if let Some(module_attribute) = form_list.module_attribute() { @@ -452,6 +441,7 @@ impl SymbolDefinition { .union(builder.finish()) .expect("Could not combine TextEdits"); } + let def_map = sema.def_map(file_id); def_map.get_functions().for_each(|(_name, f)| { let usages = SymbolDefinition::Function(f.clone()).usages(sema).all(); rename_own_module_call_refs( @@ -475,13 +465,8 @@ impl SymbolDefinition { let anchor = file_id; let path = format!("{new_name}.erl"); let dst = AnchoredPathBuf { anchor, path }; - let mut initial_contents = sema.db.file_text(anchor).to_string(); - renamed_module_edit.apply(&mut initial_contents); - source_change.push_file_system_edit(FileSystemEdit::CreateFile { - dst, - initial_contents, - }); - + source_change.insert_new_source_edit(dst.clone().into(), renamed_module_edit); + source_change.push_file_system_edit(FileSystemEdit::MoveFile { src: anchor, dst }); Ok(source_change) } else { rename_error!( @@ -500,7 +485,7 @@ fn rename_remote_module_call_refs( ) { usages.iter().for_each(|(usage_file_id, refs)| { if usage_file_id != file_id - && let Some(edit) = rename_call_module_in_refs(refs, new_name) + && let Some(edit) = rename_module_in_refs(refs, new_name) { source_change.insert_source_edit(usage_file_id, edit); }; @@ -515,7 +500,7 @@ fn rename_own_module_call_refs( ) { usages.iter().for_each(|(usage_file_id, refs)| { if usage_file_id == file_id - && let Some(edit) = rename_call_module_in_refs(refs, new_name) + && let Some(edit) = rename_module_in_refs(refs, new_name) { renamed_module_edit .union(edit) @@ -524,9 +509,12 @@ fn rename_own_module_call_refs( }); } -fn rename_call_module_in_refs(refs: &[NameLike], new_name: &str) -> Option { +fn rename_module_in_refs(refs: &[NameLike], new_name: &str) -> Option { let mut builder = TextEdit::builder(); for usage in refs { + // Note: we cannot blindly replace all occurrences of an + // atom that happens to be a module name + // We will flesh out other usages as we need them let _ = rename_call_module_in_ref(usage, &mut builder, new_name); } Some(builder.finish()) diff --git a/crates/ide_db/src/source_change.rs b/crates/ide_db/src/source_change.rs index e4afbd8742..60b62279fe 100644 --- a/crates/ide_db/src/source_change.rs +++ b/crates/ide_db/src/source_change.rs @@ -30,9 +30,36 @@ use crate::text_edit::TextEdit; use crate::text_edit::TextEditBuilder; use crate::tree_diff::diff; +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug)] +pub struct HashableAnchoredPathBuf { + /// File that this path is relative to. + pub anchor: FileId, + /// Path relative to `anchor`'s containing directory. + pub path: String, +} + +impl From for HashableAnchoredPathBuf { + fn from(value: AnchoredPathBuf) -> Self { + HashableAnchoredPathBuf { + anchor: value.anchor, + path: value.path, + } + } +} + +impl From for AnchoredPathBuf { + fn from(value: HashableAnchoredPathBuf) -> Self { + AnchoredPathBuf { + anchor: value.anchor, + path: value.path, + } + } +} + #[derive(Default, Debug, Clone)] pub struct SourceChange { pub source_file_edits: FxHashMap, + pub new_file_edits: FxHashMap, pub file_system_edits: Vec, pub is_snippet: bool, } @@ -46,6 +73,7 @@ impl SourceChange { ) -> Self { SourceChange { source_file_edits, + new_file_edits: FxHashMap::default(), file_system_edits, is_snippet: false, } @@ -74,6 +102,22 @@ impl SourceChange { } } + /// Inserts a [`TextEdit`] for the given [`AnchoredPathBuf`]. This properly handles merging existing + /// edits for a file if some already exist. + pub fn insert_new_source_edit(&mut self, file_id: HashableAnchoredPathBuf, edit: TextEdit) { + match self.new_file_edits.entry(file_id) { + Entry::Occupied(mut entry) => { + never!( + entry.get_mut().union(edit).is_err(), + "overlapping edits for same file" + ); + } + Entry::Vacant(entry) => { + entry.insert(edit); + } + } + } + pub fn push_file_system_edit(&mut self, edit: FileSystemEdit) { self.file_system_edits.push(edit); } @@ -85,12 +129,15 @@ impl SourceChange { pub fn merge(mut self, other: SourceChange) -> SourceChange { self.extend(other.source_file_edits); self.extend(other.file_system_edits); + self.extend(other.new_file_edits); self.is_snippet |= other.is_snippet; self } pub fn is_empty(&self) -> bool { - self.source_file_edits.is_empty() && self.file_system_edits.is_empty() + self.source_file_edits.is_empty() + && self.file_system_edits.is_empty() + && self.new_file_edits.is_empty() } pub fn text_range(&self, file_id: FileId) -> Option { @@ -116,10 +163,18 @@ impl Extend for SourceChange { } } +impl Extend<(HashableAnchoredPathBuf, TextEdit)> for SourceChange { + fn extend>(&mut self, iter: T) { + iter.into_iter() + .for_each(|(file_id, edit)| self.insert_new_source_edit(file_id, edit)); + } +} + impl From> for SourceChange { fn from(source_file_edits: FxHashMap) -> SourceChange { SourceChange { source_file_edits, + new_file_edits: FxHashMap::default(), file_system_edits: Vec::new(), is_snippet: false, } @@ -265,6 +320,7 @@ impl From for SourceChange { fn from(edit: FileSystemEdit) -> SourceChange { SourceChange { source_file_edits: Default::default(), + new_file_edits: Default::default(), file_system_edits: vec![edit], is_snippet: false, } From 9015d80659bdcea35dd3ac5b7148fccfaaf644ef Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 17 Dec 2025 06:00:04 -0800 Subject: [PATCH 120/142] 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 --- crates/ide/src/rename.rs | 33 +++++++++++++++++++++++++++++++++ crates/ide_db/src/helpers.rs | 8 ++++++++ crates/ide_db/src/rename.rs | 13 +++++++++++++ 3 files changed, 54 insertions(+) diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index 884556585c..992573c770 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -1372,6 +1372,39 @@ pub(crate) mod tests { ); } + #[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. + "#, + ); + } + // --------------------------------- #[track_caller] diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs index 0b26a23b6a..038b41f0be 100644 --- a/crates/ide_db/src/helpers.rs +++ b/crates/ide_db/src/helpers.rs @@ -54,6 +54,14 @@ pub fn get_call(syntax: &SyntaxNode) -> Option { .or_else(|| ast::Call::cast(syntax.parent()?.parent()?.parent()?)) } +pub fn get_external_fun(syntax: &SyntaxNode) -> Option { + if let Some(external_fun) = ast::ExternalFun::cast(syntax.parent()?) { + Some(external_fun) + } else { + ast::ExternalFun::cast(syntax.parent()?.parent()?) + } +} + /// Find the first position at the top of the file to add a new /// form. It will be just after the module attribute, if there is one. pub fn top_insert_position(form_list: &FormList, source: &SourceFile) -> TextSize { diff --git a/crates/ide_db/src/rename.rs b/crates/ide_db/src/rename.rs index 40273d6577..31c9b551b6 100644 --- a/crates/ide_db/src/rename.rs +++ b/crates/ide_db/src/rename.rs @@ -27,6 +27,7 @@ use hir::Semantic; use crate::SymbolDefinition; use crate::helpers::get_call; +use crate::helpers::get_external_fun; use crate::search::NameLike; use crate::source_change::FileSystemEdit; use crate::source_change::SourceChange; @@ -516,6 +517,7 @@ fn rename_module_in_refs(refs: &[NameLike], new_name: &str) -> Option // atom that happens to be a module name // We will flesh out other usages as we need them let _ = rename_call_module_in_ref(usage, &mut builder, new_name); + let _ = rename_external_fun_module_in_ref(usage, &mut builder, new_name); } Some(builder.finish()) } @@ -534,6 +536,17 @@ fn rename_call_module_in_ref( Some(()) } +fn rename_external_fun_module_in_ref( + usage: &NameLike, + builder: &mut TextEditBuilder, + new_name: &str, +) -> Option<()> { + let external_fun = get_external_fun(usage.syntax())?; + let module = external_fun.module()?; + builder.replace(module.name()?.syntax().text_range(), new_name.to_string()); + Some(()) +} + fn source_edit_from_usages( source_change: &mut SourceChange, usages: Vec<(FileId, &[NameLike])>, From 83d5b4dfd0902bacf67b3ecdb0edc0205e1d2e16 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 17 Dec 2025 06:00:04 -0800 Subject: [PATCH 121/142] 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 --- crates/ide/src/rename.rs | 69 +++++++++++++++++++++++++++++++++++++ crates/ide_db/src/rename.rs | 13 ++++--- crates/ide_db/src/search.rs | 2 +- 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index 992573c770..01d26b0f87 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -1372,6 +1372,42 @@ pub(crate) mod tests { ); } + #[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]), + 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, [passthrough]), + ok. + -record(main, {field :: main_3:foo()}). + "#, + ); + } + #[test] fn rename_module_with_usage_fun() { check_rename( @@ -1405,6 +1441,39 @@ pub(crate) mod tests { ); } + #[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. + "#, + ); + } + // --------------------------------- #[track_caller] diff --git a/crates/ide_db/src/rename.rs b/crates/ide_db/src/rename.rs index 31c9b551b6..a22e2b0acf 100644 --- a/crates/ide_db/src/rename.rs +++ b/crates/ide_db/src/rename.rs @@ -529,10 +529,15 @@ fn rename_call_module_in_ref( ) -> Option<()> { let call = get_call(usage.syntax())?; // Note: `ast` uses the same syntax for a function call and a type - let _: () = if let Some(ast::Expr::Remote(remote)) = call.expr() { - let module = remote.module()?.module()?; - builder.replace(module.syntax().text_range(), new_name.to_string()); - }; + if let Some(ast::Expr::Remote(remote)) = call.expr() { + let module = remote.module()?; + if let Some(ast::ExprMax::Atom(atom)) = module.module() + && let NameLike::Name(ast::Name::Atom(name_atom)) = usage + && atom.syntax() == name_atom.syntax() + { + builder.replace(atom.syntax().text_range(), new_name.to_string()); + } + } Some(()) } diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs index 20b4f050b4..1e66598359 100644 --- a/crates/ide_db/src/search.rs +++ b/crates/ide_db/src/search.rs @@ -325,7 +325,7 @@ impl<'a> FindUsages<'a> { /// Represents possible ast reference points - /// a string for header, or ast::Name for everything else -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum NameLike { Name(ast::Name), String(ast::String), From cc0a9c26d50e7038008e0ad587dcf6e0144297ef Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 17 Dec 2025 06:00:04 -0800 Subject: [PATCH 122/142] 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 --- crates/hir/src/sema/to_def.rs | 177 +++++++++++++++++++++++++++++++++- crates/ide/src/rename.rs | 8 +- crates/ide_db/src/rename.rs | 72 ++++++++++++-- 3 files changed, 247 insertions(+), 10 deletions(-) diff --git a/crates/hir/src/sema/to_def.rs b/crates/hir/src/sema/to_def.rs index 0c8eba227e..01d17278a2 100644 --- a/crates/hir/src/sema/to_def.rs +++ b/crates/hir/src/sema/to_def.rs @@ -885,7 +885,155 @@ fn add_dynamic_call_patterns(patterns: &mut FxHashMap 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) { + // 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 = { let mut patterns = FxHashMap::default(); @@ -893,12 +1041,39 @@ lazy_static! { // @fb-only: meta_only::add_dynamic_call_patterns(&mut patterns); patterns }; + static ref MODULE_ARGUMENT_PATTERNS: FxHashMap = { + 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 = { + let mut patterns: FxHashMap = 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 + }; } fn get_dynamic_call_patterns() -> &'static FxHashMap { &DYNAMIC_CALL_PATTERNS } +pub fn get_module_arg_patterns() -> &'static FxHashMap { + &COMBINED_MODULE_ARG_PATTERNS +} + fn look_for_dynamic_call( sema: &Semantic, file_id: FileId, diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index 01d26b0f87..92e5125063 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -1387,6 +1387,9 @@ pub(crate) mod tests { -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()}). "#, @@ -1401,7 +1404,10 @@ pub(crate) mod tests { -export([bar/0]). -spec bar() -> main_3:foo(). bar() -> - meck:new(main, [passthrough]), + 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()}). "#, diff --git a/crates/ide_db/src/rename.rs b/crates/ide_db/src/rename.rs index a22e2b0acf..e9ec7d96f1 100644 --- a/crates/ide_db/src/rename.rs +++ b/crates/ide_db/src/rename.rs @@ -528,16 +528,72 @@ fn rename_call_module_in_ref( new_name: &str, ) -> Option<()> { let call = get_call(usage.syntax())?; - // Note: `ast` uses the same syntax for a function call and a type - if let Some(ast::Expr::Remote(remote)) = call.expr() { - let module = remote.module()?; - if let Some(ast::ExprMax::Atom(atom)) = module.module() - && let NameLike::Name(ast::Name::Atom(name_atom)) = usage - && atom.syntax() == name_atom.syntax() - { - builder.replace(atom.syntax().text_range(), new_name.to_string()); + // We can only rename an atom usage + let usage_atom = match usage { + NameLike::Name(ast::Name::Atom(atom)) => atom, + _ => return Some(()), + }; + + // First check if this is the module part of a remote call (e.g., module:function()) + if let Some(ast::Expr::Remote(remote)) = call.expr() + && let Some(module) = remote.module() + && let Some(ast::ExprMax::Atom(mod_atom)) = module.module() + && mod_atom.syntax() == usage_atom.syntax() + { + builder.replace(usage_atom.syntax().text_range(), new_name.to_string()); + return Some(()); + } + + // Check if this is a known function call that takes a module as an argument + // Extract function name and optional module name based on call type + let (module_name, function_name) = match call.expr()? { + ast::Expr::Remote(remote) => { + let module = remote.module()?; + let mod_atom = match module.module()? { + ast::ExprMax::Atom(atom) => atom, + _ => return Some(()), + }; + let fun_atom = match remote.fun()? { + ast::ExprMax::Atom(atom) => atom, + _ => return Some(()), + }; + (Some(mod_atom.text()?), fun_atom.text()?) + } + ast::Expr::ExprMax(ast::ExprMax::Atom(fun_atom)) => (None, fun_atom.text()?), + _ => return Some(()), + }; + + let args = call.args()?; + let args_vec: Vec<_> = args.args().collect(); + let arity = args_vec.len(); + let pattern_key = (module_name.as_deref(), function_name.as_str(), arity); + + // Use combined patterns that merge dynamic call patterns and module argument patterns + let combined_patterns = hir::sema::to_def::get_module_arg_patterns(); + if let Some(pattern) = combined_patterns.get(&pattern_key) + && let Some(arg) = args_vec.get(pattern.index) + { + match arg { + ast::Expr::ExprMax(ast::ExprMax::Atom(arg_atom)) + if pattern.accepts_atom() && arg_atom.syntax() == usage_atom.syntax() => + { + builder.replace(usage_atom.syntax().text_range(), new_name.to_string()); + } + ast::Expr::ExprMax(ast::ExprMax::List(list)) if pattern.accepts_list() => { + // Handle list of modules (e.g., meck:new([mod1, mod2], Options)) + for expr in list.exprs() { + if let ast::Expr::ExprMax(ast::ExprMax::Atom(list_atom)) = expr + && list_atom.syntax() == usage_atom.syntax() + { + builder.replace(usage_atom.syntax().text_range(), new_name.to_string()); + break; + } + } + } + _ => {} } } + Some(()) } From 91a0d968dbcef5f6b05426f50d518946d450564f Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 17 Dec 2025 06:00:04 -0800 Subject: [PATCH 123/142] 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 --- crates/ide/src/rename.rs | 35 ++++++++++++++++++++- crates/ide_db/src/rename.rs | 61 +++++++++++-------------------------- 2 files changed, 52 insertions(+), 44 deletions(-) diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index 92e5125063..4a7b566bcf 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -1286,7 +1286,7 @@ pub(crate) mod tests { } #[test] - fn rename_module_with_usage() { + fn rename_module_fails_dup_name() { check_rename( "main_2", r#" @@ -1310,6 +1310,39 @@ pub(crate) mod tests { ); } + #[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 diff --git a/crates/ide_db/src/rename.rs b/crates/ide_db/src/rename.rs index e9ec7d96f1..bd5a49f8cf 100644 --- a/crates/ide_db/src/rename.rs +++ b/crates/ide_db/src/rename.rs @@ -427,10 +427,16 @@ impl SymbolDefinition { let mut source_change = SourceChange::default(); // Step 1, rename all references let usages = self.clone().usages(sema).all(); - rename_remote_module_call_refs(usages, file_id, new_name, &mut source_change); - - // Step 2: Make changes in the module being renamed let mut renamed_module_edit: TextEdit = TextEdit::default(); + rename_remote_module_call_refs( + usages, + file_id, + new_name, + &mut source_change, + &mut renamed_module_edit, + ); + + // Step 2: Rename the module attribute in the module being renamed let form_list = sema.form_list(file_id); if let Some(module_attribute) = form_list.module_attribute() { let ast = module_attribute.form_id.get_ast(sema.db, file_id); @@ -442,25 +448,6 @@ impl SymbolDefinition { .union(builder.finish()) .expect("Could not combine TextEdits"); } - let def_map = sema.def_map(file_id); - def_map.get_functions().for_each(|(_name, f)| { - let usages = SymbolDefinition::Function(f.clone()).usages(sema).all(); - rename_own_module_call_refs( - usages, - file_id, - new_name, - &mut renamed_module_edit, - ); - }); - def_map.get_types().iter().for_each(|(_name, t)| { - let usages = SymbolDefinition::Type(t.clone()).usages(sema).all(); - rename_own_module_call_refs( - usages, - file_id, - new_name, - &mut renamed_module_edit, - ); - }); } let anchor = file_id; @@ -483,30 +470,18 @@ fn rename_remote_module_call_refs( file_id: FileId, new_name: &str, source_change: &mut SourceChange, -) { - usages.iter().for_each(|(usage_file_id, refs)| { - if usage_file_id != file_id - && let Some(edit) = rename_module_in_refs(refs, new_name) - { - source_change.insert_source_edit(usage_file_id, edit); - }; - }); -} - -fn rename_own_module_call_refs( - usages: crate::UsageSearchResult, - file_id: FileId, - new_name: &str, renamed_module_edit: &mut TextEdit, ) { usages.iter().for_each(|(usage_file_id, refs)| { - if usage_file_id == file_id - && let Some(edit) = rename_module_in_refs(refs, new_name) - { - renamed_module_edit - .union(edit) - .expect("Could not combine TextEdits"); - } + if let Some(edit) = rename_module_in_refs(refs, new_name) { + if usage_file_id == file_id { + renamed_module_edit + .union(edit) + .expect("Could not combine TextEdits"); + } else { + source_change.insert_source_edit(usage_file_id, edit); + } + }; }); } From 4a051d1af88ebb2ae80d31f85fba061a10c7e098 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 17 Dec 2025 06:00:04 -0800 Subject: [PATCH 124/142] 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 --- crates/ide/src/rename.rs | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index 4a7b566bcf..ee5bfb7219 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -1513,6 +1513,52 @@ pub(crate) mod tests { ); } + #[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] From 7057624c7339b554d099a2e4f38bcc72b3d33a0e Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 17 Dec 2025 06:36:56 -0800 Subject: [PATCH 125/142] `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 --- crates/elp/src/bin/args.rs | 2 +- crates/elp/src/bin/lint_cli.rs | 81 ++++++++--- crates/elp/src/bin/main.rs | 12 ++ .../test/diagnostics/lint_no_stream.stdout | 135 ++++++++++++++++++ .../parse_elp_lint_ignore_apps_b.stdout | 2 +- 5 files changed, 207 insertions(+), 25 deletions(-) create mode 100644 crates/elp/src/resources/test/diagnostics/lint_no_stream.stdout diff --git a/crates/elp/src/bin/args.rs b/crates/elp/src/bin/args.rs index 2d97f50562..c9790e9314 100644 --- a/crates/elp/src/bin/args.rs +++ b/crates/elp/src/bin/args.rs @@ -911,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 } } diff --git a/crates/elp/src/bin/lint_cli.rs b/crates/elp/src/bin/lint_cli.rs index f7ac4ff114..241a3d4481 100644 --- a/crates/elp/src/bin/lint_cli.rs +++ b/crates/elp/src/bin/lint_cli.rs @@ -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") } } diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index 38ef3c3880..7a649f0c4a 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -1591,6 +1591,18 @@ mod tests { ); } + #[test_case(false ; "rebar")] + #[test_case(true ; "buck")] + fn lint_no_stream_produces_output(buck: bool) { + 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) { diff --git a/crates/elp/src/resources/test/diagnostics/lint_no_stream.stdout b/crates/elp/src/resources/test/diagnostics/lint_no_stream.stdout new file mode 100644 index 0000000000..b1c2f07150 --- /dev/null +++ b/crates/elp/src/resources/test/diagnostics/lint_no_stream.stdout @@ -0,0 +1,135 @@ +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: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_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 ')' diff --git a/crates/elp/src/resources/test/linter/parse_elp_lint_ignore_apps_b.stdout b/crates/elp/src/resources/test/linter/parse_elp_lint_ignore_apps_b.stdout index 3d59c453e3..5a42009118 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_lint_ignore_apps_b.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_lint_ignore_apps_b.stdout @@ -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 From 79c33480c54371f105433f0b5374cb9f6f6e143c Mon Sep 17 00:00:00 2001 From: Victor Lanvin Date: Wed, 17 Dec 2025 08:54:32 -0800 Subject: [PATCH 126/142] Support for OTP version specific tests 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 --- crates/elp/src/bin/main.rs | 47 +- .../check/callbacks3_neg-OTP-27.pretty | 27 - .../check/callbacks3_neg-OTP-28.pretty | 26 - ...eg-OTP-26.pretty => callbacks3_neg.pretty} | 0 .../check/custom-OTP-27.pretty | 2540 ----------------- .../check/custom-OTP-28.pretty | 2540 ----------------- .../{custom-OTP-26.pretty => custom.pretty} | 0 .../check/src/callbacks3_neg.erl | 2 +- .../eqwalizer_tests/check/src/custom.erl | 2 +- 9 files changed, 26 insertions(+), 5158 deletions(-) delete mode 100644 crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-27.pretty delete mode 100644 crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-28.pretty rename crates/elp/src/resources/test/eqwalizer_tests/check/{callbacks3_neg-OTP-26.pretty => callbacks3_neg.pretty} (100%) delete mode 100644 crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-27.pretty delete mode 100644 crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-28.pretty rename crates/elp/src/resources/test/eqwalizer_tests/check/{custom-OTP-26.pretty => custom.pretty} (100%) diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index 7a649f0c4a..e7ed9051a9 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -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 } => { diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-27.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-27.pretty deleted file mode 100644 index 6cb2012096..0000000000 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-27.pretty +++ /dev/null @@ -1,27 +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: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 diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-28.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-28.pretty deleted file mode 100644 index 849039f298..0000000000 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-28.pretty +++ /dev/null @@ -1,26 +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:13:1 - │ -13 │ -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' - -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(), gen_server:action()} | {'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: gen_server:action() - } - -2 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-26.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg.pretty similarity index 100% rename from crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg-OTP-26.pretty rename to crates/elp/src/resources/test/eqwalizer_tests/check/callbacks3_neg.pretty diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-27.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-27.pretty deleted file mode 100644 index efc8c47aa0..0000000000 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-27.pretty +++ /dev/null @@ -1,2540 +0,0 @@ -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:26:5 - │ -26 │ element(4, Tup). - │ ^^^^^^^^^^^^^^^ erlang:element(4, Tup). -Expression has type: #{dynamic() => dynamic()} -Context expected type: atom() - -error: index_out_of_bounds (See https://fb.me/eqwalizer_errors#index_out_of_bounds) - ┌─ check/src/custom.erl:30:5 - │ -30 │ element(42, Tup). - │ ^^^^^^^^^^^^^^^^ 42. -Tried to access element 42 of a tuple with 3 elements - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:40:5 - │ -40 │ element(2, Tup). - │ ^^^^^^^^^^^^^^^ erlang:element(2, Tup). -Expression has type: number() | string() | atom() -Context expected type: #{dynamic() => dynamic()} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:44:16 - │ -44 │ element(2, Tup). - │ ^^^ - │ │ - │ Tup. -Expression has type: {atom(), string()} | [dynamic()] -Context expected type: tuple() - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: {atom(), string()} - However the following candidate: [dynamic()] - Differs from the expected type: tuple() - -error: index_out_of_bounds (See https://fb.me/eqwalizer_errors#index_out_of_bounds) - ┌─ check/src/custom.erl:48:5 - │ -48 │ element(42, Tup). - │ ^^^^^^^^^^^^^^^^ 42. -Tried to access element 42 of a tuple with 2 elements - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:54:5 - │ -54 │ element(N, Tup). - │ ^^^^^^^^^^^^^^^ erlang:element(N, Tup). -Expression has type: atom() | number() | string() -Context expected type: #{dynamic() => dynamic()} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:58:5 - │ -58 │ element(N, Tup). - │ ^^^^^^^^^^^^^^^ - │ │ - │ erlang:element(N, Tup). -Expression has type: atom() | number() -Context expected type: atom() - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: atom() - However the following candidate: number() - Differs from the expected type: atom() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:68:5 - │ -68 │ element(1, Tup). - │ ^^^^^^^^^^^^^^^ - │ │ - │ erlang:element(1, Tup). -Expression has type: dynamic() | number() -Context expected type: atom() - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: dynamic() - However the following candidate: number() - Differs from the expected type: atom() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:86:5 - │ -86 │ element(N, Rec). - │ ^^^^^^^^^^^^^^^ - │ │ - │ erlang:element(N, Rec). -Expression has type: 'foo' | 'ok' | 'error' | number() | string() -Context expected type: atom() - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: 'error' | 'foo' | 'ok' - However the following candidate: string() - Differs from the expected type: atom() - -error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) - ┌─ check/src/custom.erl:91:1 - │ -91 │ ╭ element_2_none_1(Tup) -> -92 │ │ element(42, Tup). - │ ╰────────────────────^ Clause is not covered by spec - -error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) - ┌─ check/src/custom.erl:95:1 - │ -95 │ ╭ element_2_none_2(N, Tup) -> -96 │ │ element(N, Tup). - │ ╰───────────────────^ Clause is not covered by spec - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:108:5 - │ -108 │ maps:get(K, M). - │ ^^^^^^^^^^^^^^ maps:get(K, M). -Expression has type: number() -Context expected type: atom() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:128:5 - │ -128 │ Res. - │ ^^^ Res. -Expression has type: number() -Context expected type: atom() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:154:12 - │ -154 │ get(a, M). - │ ^ M. -Expression has type: term() -Context expected type: #{term() => term()} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:160:23 - │ -160 │ Res = maps:get(a, M), - │ ^ M. -Expression has type: term() -Context expected type: #{term() => term()} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:167:17 - │ -167 │ maps:get(a, M, false). - │ ^ M. -Expression has type: term() -Context expected type: #{term() => term()} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:173:23 - │ -173 │ Res = maps:get(a, M, false), - │ ^ M. -Expression has type: term() -Context expected type: #{term() => term()} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:193:5 - │ -193 │ maps:get(K, M, 0). - │ ^^^^^^^^^^^^^^^^^ - │ │ - │ maps:get(K, M, 0). -Expression has type: number() | atom() -Context expected type: atom() - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: atom() - However the following candidate: number() - Differs from the expected type: atom() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:200:5 - │ -200 │ Res. - │ ^^^ - │ │ - │ Res. -Expression has type: number() | atom() -Context expected type: atom() - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: atom() - However the following candidate: number() - Differs from the expected type: atom() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:206:5 - │ -206 │ ╭ ╭ { -207 │ │ │ maps:get(a, M, undefined), -208 │ │ │ maps:get(n, M, undefined) -209 │ │ │ }. - │ ╰─│─────^ {maps:get('a', M, 'undefined'), maps:get('n', M, 'undefined')}. -Expression has type: {atom(), 'undefined' | number()} -Context expected type: {atom(), number()} - │ ╰─────' - -Because in the expression's type: - { atom(), - Here the type is a union type with some valid candidates: number() - However the following candidate: 'undefined' - Differs from the expected type: number() - } - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:221:27 - │ -221 │ map_get_2_17_neg(V, M) -> maps:get(V, M). - │ ^^^^^^^^^^^^^^ - │ │ - │ maps:get(V, M). -Expression has type: 'a_v' | 'c_v' -Context expected type: 'a_v' | 'b_v' - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: 'a_v' - However the following candidate: 'c_v' - Differs from the expected type: 'a_v' | 'b_v' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:233:27 - │ -233 │ map_get_3_19_neg(V, M) -> maps:get(V, M, undefined). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ maps:get(V, M, 'undefined'). -Expression has type: 'undefined' | 'a_v' | 'c_v' -Context expected type: 'a_v' | 'b_v' | 'undefined' - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: 'undefined' | 'a_v' - However the following candidate: 'c_v' - Differs from the expected type: 'a_v' | 'b_v' | 'undefined' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:309:5 - │ -309 │ Res. - │ ^^^ Res. -Expression has type: {'value', #{}} | 'false' -Context expected type: 'nok' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:363:23 - │ -363 │ fun (_, _) -> self() end, - │ ^^^^^^ erlang:self(). -Expression has type: pid() -Context expected type: boolean() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:382:17 - │ -382 │ maps:filter(F, M). - │ ^ F. -Expression has type: fun(() -> pid()) -Context expected type: fun((number(), 'a' | 'b') -> boolean()) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:389:17 - │ -389 │ maps:filter(F, M). - │ ^ - │ │ - │ F. -Expression has type: fun((fun((T) -> boolean()), [T]) -> [T]) with 1 type parameter -Context expected type: fun((number(), 'a' | 'b') -> boolean()) with 0 type parameters - │ - -Because in the expression's type: - Here the type is: fun()/2 with 1 type parameter - Context expects type: fun((number(), 'a' | 'b') -> boolean()) with 0 type parameters - The number of type parameters doesn't match. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:413:37 - │ -413 │ maps:filter(fun erlang:'=:='/2, X). - │ ^ - │ │ - │ X. -Expression has type: #{K => V} | 'a' -Context expected type: #{term() => term()} | maps:iterator() - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: #{K => V} - However the following candidate: 'a' - Differs from the expected type: #{term() => term()} | maps:iterator() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:425:20 - │ -425 │ maps:filter(F, non_kv), - │ ^^^^^^ 'non_kv'. -Expression has type: 'non_kv' -Context expected type: #{term() => term()} | maps:iterator() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:454:5 - │ -454 │ ╭ ╭ maps:map( -455 │ │ │ fun (_, _) -> self() end, -456 │ │ │ M -457 │ │ │ ). - │ ╰─│─────^ maps:map(fun, M). -Expression has type: #{number() => pid()} -Context expected type: #{number() => boolean()} - │ ╰─────' - -Because in the expression's type: - #{ number() => - Here the type is: pid() - Context expects type: boolean() - , ... } - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:474:14 - │ -474 │ maps:map(F, M). - │ ^ F. -Expression has type: fun(() -> pid()) -Context expected type: fun((number(), 'a' | 'b') -> term()) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:481:14 - │ -481 │ maps:map(F, M). - │ ^ - │ │ - │ F. -Expression has type: fun((fun((A) -> B), [A]) -> [B]) with 2 type parameters -Context expected type: fun((number(), 'a' | 'b') -> term()) with 0 type parameters - │ - -Because in the expression's type: - Here the type is: fun((fun((A) -> B), [A]) -> [B]) with 2 type parameters - Context expects type: fun((number(), 'a' | 'b') -> term()) with 0 type parameters - The number of type parameters doesn't match. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:497:5 - │ -497 │ maps:map(F, M). - │ ^^^^^^^^^^^^^^ - │ │ - │ maps:map(F, M). -Expression has type: #{a := boolean(), b := boolean()} -Context expected type: #{a => 'a', b => 'b'} - │ - -Because in the expression's type: - #{ a => - Here the type is: boolean() - Context expects type: 'a' - , ... } - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:503:17 - │ -503 │ maps:map(F, non_kv), - │ ^^^^^^ 'non_kv'. -Expression has type: 'non_kv' -Context expected type: #{term() => term()} | maps:iterator() - -error: fun_arity_mismatch (See https://fb.me/eqwalizer_errors#fun_arity_mismatch) - ┌─ check/src/custom.erl:538:9 - │ -538 │ fun (K, V) -> [K, V] end, [], M). - │ ^^^^^^^^^^^^^^^^^^^^^^^^ fun. -fun with arity 2 used as fun with 3 arguments - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:544:5 - │ -544 │ ╭ ╭ maps:fold( -545 │ │ │ fun (_, _, Acc) -> [Acc] end, -546 │ │ │ [], -547 │ │ │ M -548 │ │ │ ). - │ ╰─│─────^ maps:fold(fun, [], M). -Expression has type: [[[]]] -Context expected type: [number() | 'a' | 'b'] - │ ╰─────' - -Because in the expression's type: - [ - Here the type is: [[]] - Context expects type: number() | 'a' | 'b' - No candidate matches in the expected union. - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:545:28 - │ -545 │ fun (_, _, Acc) -> [Acc] end, - │ ^^^^^ - │ │ - │ [Acc]. -Expression has type: [[[[]]]] -Context expected type: [[[]]] - │ - -Because in the expression's type: - [ - [ - [ - Here the type is: [] - Context expects type: none() - ] - ] - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:552:5 - │ -552 │ ╭ maps:fold( -553 │ │ fun (_, _, Acc) -> Acc end, -554 │ │ [], -555 │ │ non_kv -556 │ │ ). - │ ╰─────^ maps:fold(fun, [], 'non_kv'). -Expression has type: [] -Context expected type: 'nok' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:555:9 - │ -555 │ non_kv - │ ^^^^^^ 'non_kv'. -Expression has type: 'non_kv' -Context expected type: #{term() => term()} | maps:iterator() - -error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) - ┌─ check/src/custom.erl:560:1 - │ -560 │ ╭ maps_fold_3_6(None) -> -561 │ │ maps:fold( -562 │ │ None, -563 │ │ #{}, -564 │ │ #{1 => 1} -565 │ │ ). - │ ╰─────^ Clause is not covered by spec - -error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) - ┌─ check/src/custom.erl:568:1 - │ -568 │ ╭ maps_fold_3_7(None) -> -569 │ │ maps:fold( -570 │ │ None, -571 │ │ None, -572 │ │ None -573 │ │ ). - │ ╰─────^ Clause is not covered by spec - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:578:5 - │ -578 │ ╭ ╭ maps:fold( -579 │ │ │ fun (_K, A, _Acc) -> A end, -580 │ │ │ [], -581 │ │ │ M -582 │ │ │ ). - │ ╰─│─────^ maps:fold(fun, [], M). -Expression has type: [] | atom() -Context expected type: atom() - │ ╰─────' - -Because in the expression's type: - Here the type is a union type with some valid candidates: atom() - However the following candidate: [] - Differs from the expected type: atom() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:601:9 - │ -601 │ fun folder_bad/3, - │ ^^^^^^^^^^^^^^^^ - │ │ - │ folder_bad/3. -Expression has type: fun((term(), term(), Acc) -> [Acc]) with 1 type parameter -Context expected type: fun((number(), 'a', []) -> term()) with 0 type parameters - │ - -Because in the expression's type: - Here the type is: fun((term(), term(), Acc) -> [Acc]) with 1 type parameter - Context expects type: fun((number(), 'a', []) -> term()) with 0 type parameters - The number of type parameters doesn't match. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:601:9 - │ -601 │ fun folder_bad/3, - │ ^^^^^^^^^^^^^^^^ - │ │ - │ folder_bad/3. -Expression has type: fun((term(), term(), Acc) -> [Acc]) with 1 type parameter -Context expected type: fun((number(), 'a', [] | dynamic()) -> term()) with 0 type parameters - │ - -Because in the expression's type: - Here the type is: fun((term(), term(), Acc) -> [Acc]) with 1 type parameter - Context expects type: fun()/3 with 0 type parameters - The number of type parameters doesn't match. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:601:9 - │ -601 │ fun folder_bad/3, - │ ^^^^^^^^^^^^^^^^ - │ │ - │ folder_bad/3. -Expression has type: fun((term(), term(), Acc) -> [Acc]) with 1 type parameter -Context expected type: fun((number(), 'a', [] | dynamic()) -> [] | dynamic()) with 0 type parameters - │ - -Because in the expression's type: - Here the type is: fun((term(), term(), Acc) -> [Acc]) with 1 type parameter - Context expects type: fun()/3 with 0 type parameters - The number of type parameters doesn't match. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:636:5 - │ -636 │ ╭ ╭ maps:fold( -637 │ │ │ fun -638 │ │ │ (_K, {i, I}, Acc) -> -639 │ │ │ [I | Acc]; - · │ │ -646 │ │ │ M -647 │ │ │ ). - │ ╰─│─────^ maps:fold(fun, [], M). -Expression has type: [number() | binary() | atom()] -Context expected type: [binary()] | [number()] | [atom()] - │ ╰─────' - -Because in the expression's type: - [ - Here the type is a union type with some valid candidates: binary() - However the following candidate: number() - Differs from the expected type: binary() - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:671:41 - │ -671 │ maps_to_list_7_neg(Num) -> maps:to_list(Num). - │ ^^^ Num. -Expression has type: number() -Context expected type: #{term() => term()} | maps:iterator() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:675:25 - │ -675 │ maps_merge_1(M1, M2) -> maps:merge(M1, M2). - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ maps:merge(M1, M2). -Expression has type: #{'b' | 'a' | 'c' => number() | string() | atom()} -Context expected type: #{a => string(), b => number(), c => atom()} - │ - -Because in the expression's type: - Here the type is: #{'b' | 'a' | 'c' => number() | string() | atom()} - Context expects type: #{...} (no default association) - The expected map has no default association while the type of the expression has one. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:679:25 - │ -679 │ maps_merge_2(M1, M2) -> maps:merge(M1, M2). - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ maps:merge(M1, M2). -Expression has type: #{'b' | 'a' | 'c' => number() | string() | atom()} -Context expected type: #{a => string(), b => number() | boolean(), c => atom()} - │ - -Because in the expression's type: - Here the type is: #{'b' | 'a' | 'c' => number() | string() | atom()} - Context expects type: #{...} (no default association) - The expected map has no default association while the type of the expression has one. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:683:25 - │ -683 │ maps_merge_3(M1, M2) -> maps:merge(M1, M2). - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ maps:merge(M1, M2). -Expression has type: #{'b' | 'a' | 'c' => number() | string() | atom()} -Context expected type: #{a := string(), b := boolean(), c => atom()} - │ - -Because in the expression's type: - Here the type is: #{...} - Context expects type: #{a := ..., b := ..., ...} - The type of the expression is missing the following required keys: a, b. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:697:44 - │ -697 │ maps_merge_7_neg(M1, M2) -> maps:merge(M1, M2). - │ ^^ M2. -Expression has type: number() -Context expected type: #{a => binary()} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:701:25 - │ -701 │ maps_merge_8(M1, M2) -> maps:merge(M1, M2). - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ maps:merge(M1, M2). -Expression has type: #{'a' | 'b' => atom() | number()} -Context expected type: #{a := atom(), b := number()} | #{a := atom()} - │ - -Because in the expression's type: - Here the type is: #{...} - Context expects type: #{a := ..., b := ..., ...} - The type of the expression is missing the following required keys: a, b. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:721:9 - │ -721 │ fun erlang:binary_to_list/1, - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:binary_to_list/1. -Expression has type: fun((binary()) -> [number()]) -Context expected type: fun((number()) -> boolean() | {'true', term()}) - │ - -Because in the expression's type: - fun((binary()) -> - Here the type is: [number()] - Context expects type: 'false' | 'true' | {'true', term()} - No candidate matches in the expected union. - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:739:20 - │ -739 │ (3) -> wrong_ret end, - │ ^^^^^^^^^ - │ │ - │ 'wrong_ret'. -Expression has type: 'wrong_ret' -Context expected type: boolean() | {'true', term()} - │ - -Because in the expression's type: - Here the type is: 'wrong_ret' - Context expects type: 'false' | 'true' | {'true', term()} - No candidate matches in the expected union. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:749:9 - │ -749 │ not_a_list - │ ^^^^^^^^^^ 'not_a_list'. -Expression has type: 'not_a_list' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:791:17 - │ -791 │ Res - │ ^^^ - │ │ - │ Res. -Expression has type: {'true', 'a'} | 'wrong_ret' -Context expected type: boolean() | {'true', term()} - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: {'true', 'a'} - However the following candidate: 'wrong_ret' - Differs from the expected type: 'false' | 'true' | {'true', term()} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:827:9 - │ -827 │ ╭ ╭ fun (a) -> [a]; -828 │ │ │ (b) -> true; -829 │ │ │ (c) -> wrong_ret end, - │ ╰─│────────────────────────────────^ fun. -Expression has type: fun(('a' | 'b') -> ['a'] | 'true' | 'wrong_ret') -Context expected type: fun(('a' | 'b') -> boolean() | ['a' | 'b']) - │ ╰────────────────────────────────' - -Because in the expression's type: - fun(('a' | 'b') -> - Here the type is a union type with some valid candidates: ['a'] | 'true' - However the following candidate: 'wrong_ret' - Differs from the expected type: 'false' | 'true' | ['a' | 'b'] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:829:20 - │ -829 │ (c) -> wrong_ret end, - │ ^^^^^^^^^ - │ │ - │ 'wrong_ret'. -Expression has type: 'wrong_ret' -Context expected type: boolean() | ['a' | 'b'] - │ - -Because in the expression's type: - Here the type is: 'wrong_ret' - Context expects type: 'false' | 'true' | ['a' | 'b'] - No candidate matches in the expected union. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:837:9 - │ -837 │ ╭ ╭ fun (1) -> {true, a}; -838 │ │ │ (2) -> true end, - │ ╰─│───────────────────────────^ fun. -Expression has type: fun((dynamic()) -> {'true', 'a'} | 'true') -Context expected type: fun((Item) -> boolean() | [Item]) - │ ╰───────────────────────────' - -Because in the expression's type: - fun((dynamic()) -> - Here the type is a union type with some valid candidates: 'true' - However the following candidate: {'true', 'a'} - Differs from the expected type: 'false' | 'true' | [Item] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:837:20 - │ -837 │ fun (1) -> {true, a}; - │ ^^^^^^^^^ - │ │ - │ {'true', 'a'}. -Expression has type: {'true', 'a'} -Context expected type: boolean() | [dynamic()] - │ - -Because in the expression's type: - Here the type is: {'true', 'a'} - Context expects type: 'false' | 'true' | [dynamic()] - No candidate matches in the expected union. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:839:9 - │ -839 │ not_a_queue - │ ^^^^^^^^^^^ - │ │ - │ 'not_a_queue'. -Expression has type: 'not_a_queue' -Context expected type: queue:queue(Item) - │ - -Because in the expression's type: - Here the type is: 'not_a_queue' - Context expects type: {[Item], [Item]} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:839:9 - │ -839 │ not_a_queue - │ ^^^^^^^^^^^ - │ │ - │ 'not_a_queue'. -Expression has type: 'not_a_queue' -Context expected type: queue:queue(dynamic()) - │ - -Because in the expression's type: - Here the type is: 'not_a_queue' - Context expects type: {[dynamic()], [dynamic()]} - -error: fun_arity_mismatch (See https://fb.me/eqwalizer_errors#fun_arity_mismatch) - ┌─ check/src/custom.erl:846:9 - │ -846 │ ╭ fun (wrong, arity) -> -847 │ │ [a] -848 │ │ end, - │ ╰───────────^ fun. -fun with arity 2 used as fun with 1 arguments - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:857:9 - │ -857 │ ╭ ╭ fun (1) -> {true, a}; -858 │ │ │ (X) -> case X of -859 │ │ │ true -> -860 │ │ │ [a]; - · │ │ -863 │ │ │ end -864 │ │ │ end, - │ ╰─│───────────^ fun. -Expression has type: fun(('a' | 'b') -> ['a'] | 'false' | {'true', 'a'}) -Context expected type: fun(('a' | 'b') -> boolean() | ['a' | 'b']) - │ ╰───────────' - -Because in the expression's type: - fun(('a' | 'b') -> - Here the type is a union type with some valid candidates: ['a'] | 'false' - However the following candidate: {'true', 'a'} - Differs from the expected type: 'false' | 'true' | ['a' | 'b'] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:857:20 - │ -857 │ fun (1) -> {true, a}; - │ ^^^^^^^^^ - │ │ - │ {'true', 'a'}. -Expression has type: {'true', 'a'} -Context expected type: boolean() | ['a' | 'b'] - │ - -Because in the expression's type: - Here the type is: {'true', 'a'} - Context expects type: 'false' | 'true' | ['a' | 'b'] - No candidate matches in the expected union. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:873:9 - │ -873 │ ╭ ╭ fun (a) -> [a]; -874 │ │ │ (X) -> -875 │ │ │ Res = case X of -876 │ │ │ true -> - · │ │ -881 │ │ │ Res -882 │ │ │ end, - │ ╰─│───────────^ fun. -Expression has type: fun(('a' | 'b') -> ['a'] | 'wrong_ret') -Context expected type: fun(('a' | 'b') -> boolean() | ['a' | 'b']) - │ ╰───────────' - -Because in the expression's type: - fun(('a' | 'b') -> - Here the type is a union type with some valid candidates: ['a'] - However the following candidate: 'wrong_ret' - Differs from the expected type: 'false' | 'true' | ['a' | 'b'] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:881:17 - │ -881 │ Res - │ ^^^ - │ │ - │ Res. -Expression has type: ['a'] | 'wrong_ret' -Context expected type: boolean() | ['a' | 'b'] - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: ['a'] - However the following candidate: 'wrong_ret' - Differs from the expected type: 'false' | 'true' | ['a' | 'b'] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:891:9 - │ -891 │ fun list_to_atom/1, - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:list_to_atom/1. -Expression has type: fun((string()) -> atom()) -Context expected type: fun((dynamic()) -> boolean() | [dynamic()]) - │ - -Because in the expression's type: - fun((string()) -> - Here the type is: atom() - Context expects type: 'false' | 'true' | [dynamic()] - No candidate matches in the expected union. - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:891:9 - │ -891 │ fun list_to_atom/1, - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:list_to_atom/1. -Expression has type: fun((string()) -> atom()) -Context expected type: fun(('a' | 'b') -> boolean() | ['a' | 'b']) - │ - -Because in the expression's type: - fun((string()) -> - Here the type is: atom() - Context expects type: 'false' | 'true' | ['a' | 'b'] - No candidate matches in the expected union. - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:900:9 - │ -900 │ fun atom_to_list/1, - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:atom_to_list/1. -Expression has type: fun((atom()) -> string()) -Context expected type: fun((atom()) -> boolean() | [atom()]) - │ - -Because in the expression's type: - fun((atom()) -> - Here the type is: string() - Context expects type: boolean() | [atom()] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:900:9 - │ -900 │ fun atom_to_list/1, - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:atom_to_list/1. -Expression has type: fun((atom()) -> string()) -Context expected type: fun((dynamic(atom())) -> boolean() | [dynamic(atom())]) - │ - -Because in the expression's type: - fun((atom()) -> - Here the type is: string() - Context expects type: boolean() | [dynamic(atom())] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:909:9 - │ -909 │ fun atom_to_list/1, - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:atom_to_list/1. -Expression has type: fun((atom()) -> string()) -Context expected type: fun((atom()) -> boolean() | [atom()]) - │ - -Because in the expression's type: - fun((atom()) -> - Here the type is: string() - Context expects type: boolean() | [atom()] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:909:9 - │ -909 │ fun atom_to_list/1, - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:atom_to_list/1. -Expression has type: fun((atom()) -> string()) -Context expected type: fun((dynamic(atom())) -> boolean() | [dynamic(atom())]) - │ - -Because in the expression's type: - fun((atom()) -> - Here the type is: string() - Context expects type: boolean() | [dynamic(atom())] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:919:9 - │ -919 │ fun atom_to_list/1, - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:atom_to_list/1. -Expression has type: fun((atom()) -> string()) -Context expected type: fun((atom()) -> boolean() | [atom()]) - │ - -Because in the expression's type: - fun((atom()) -> - Here the type is: string() - Context expects type: boolean() | [atom()] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:919:9 - │ -919 │ fun atom_to_list/1, - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:atom_to_list/1. -Expression has type: fun((atom()) -> string()) -Context expected type: fun((dynamic(atom())) -> boolean() | [dynamic(atom())]) - │ - -Because in the expression's type: - fun((atom()) -> - Here the type is: string() - Context expects type: boolean() | [dynamic(atom())] - ) - -error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) - ┌─ check/src/custom.erl:925:1 - │ -925 │ ╭ queue_filter_13_neg(Q) -> -926 │ │ queue:filter( -927 │ │ fun atom_to_list/1, -928 │ │ Q -929 │ │ ), -930 │ │ ok. - │ ╰──────^ Clause is not covered by spec - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:927:9 - │ -927 │ fun atom_to_list/1, - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:atom_to_list/1. -Expression has type: fun((atom()) -> string()) -Context expected type: fun((atom()) -> boolean() | [atom()]) - │ - -Because in the expression's type: - fun((atom()) -> - Here the type is: string() - Context expects type: boolean() | [atom()] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:927:9 - │ -927 │ fun atom_to_list/1, - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:atom_to_list/1. -Expression has type: fun((atom()) -> string()) -Context expected type: fun((dynamic(atom())) -> boolean() | [dynamic(atom())]) - │ - -Because in the expression's type: - fun((atom()) -> - Here the type is: string() - Context expects type: boolean() | [dynamic(atom())] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:955:5 - │ -955 │ ╭ lists:keystore( -956 │ │ a, 1, -957 │ │ [{foo, b}, {c, d}], -958 │ │ non_tuple -959 │ │ ). - │ ╰─────^ lists:keystore('a', 1, [{'foo', 'b'}, {'c', 'd'}], 'non_tuple'). -Expression has type: [{'foo', 'b'} | {'c', 'd'} | dynamic()] -Context expected type: 'nok' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:958:9 - │ -958 │ non_tuple - │ ^^^^^^^^^ 'non_tuple'. -Expression has type: 'non_tuple' -Context expected type: tuple() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:966:5 - │ -966 │ ╭ lists:keystore( -967 │ │ a, 1, -968 │ │ [non_tuple], -969 │ │ {replacement} -970 │ │ ). - │ ╰─────^ lists:keystore('a', 1, ['non_tuple'], {'replacement'}). -Expression has type: [dynamic() | {'replacement'}] -Context expected type: 'nok' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:968:9 - │ -968 │ [non_tuple], - │ ^^^^^^^^^^^ - │ │ - │ ['non_tuple']. -Expression has type: ['non_tuple'] -Context expected type: [tuple()] - │ - -Because in the expression's type: - [ - Here the type is: 'non_tuple' - Context expects type: tuple() - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:975:5 - │ -975 │ ╭ lists:keystore( -976 │ │ a, 1, -977 │ │ non_list, -978 │ │ {replacement} -979 │ │ ). - │ ╰─────^ lists:keystore('a', 1, 'non_list', {'replacement'}). -Expression has type: [dynamic() | {'replacement'}] -Context expected type: 'nok' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:977:9 - │ -977 │ non_list, - │ ^^^^^^^^ 'non_list'. -Expression has type: 'non_list' -Context expected type: [tuple()] - -error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) - ┌─ check/src/custom.erl:983:1 - │ -983 │ ╭ keystore_7(None) -> -984 │ │ lists:keystore( -985 │ │ a, 1, -986 │ │ None, -987 │ │ {replacement} -988 │ │ ). - │ ╰─────^ Clause is not covered by spec - -error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) - ┌─ check/src/custom.erl:991:1 - │ -991 │ ╭ keystore_8(None) -> -992 │ │ lists:keystore( -993 │ │ a, 1, -994 │ │ None, -995 │ │ None -996 │ │ ). - │ ╰─────^ Clause is not covered by spec - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1011:25 - │ -1011 │ lists:keytake(a, 1, non_tup), - │ ^^^^^^^ 'non_tup'. -Expression has type: 'non_tup' -Context expected type: [Tuple] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1011:25 - │ -1011 │ lists:keytake(a, 1, non_tup), - │ ^^^^^^^ 'non_tup'. -Expression has type: 'non_tup' -Context expected type: [dynamic()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1016:25 - │ -1016 │ lists:keytake(a, 1, non_list), - │ ^^^^^^^^ 'non_list'. -Expression has type: 'non_list' -Context expected type: [Tuple] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1016:25 - │ -1016 │ lists:keytake(a, 1, non_list), - │ ^^^^^^^^ 'non_list'. -Expression has type: 'non_list' -Context expected type: [dynamic()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1021:22 - │ -1021 │ lists:keytake(a, non_num, []), - │ ^^^^^^^ 'non_num'. -Expression has type: 'non_num' -Context expected type: number() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1038:15 - │ -1038 │ lists:sum([a, 1]). - │ ^^^^^^ - │ │ - │ ['a', 1]. -Expression has type: ['a' | number()] -Context expected type: [number()] - │ - -Because in the expression's type: - [ - Here the type is a union type with some valid candidates: number() - However the following candidate: 'a' - Differs from the expected type: number() - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1042:15 - │ -1042 │ lists:sum(not_a_list). - │ ^^^^^^^^^^ 'not_a_list'. -Expression has type: 'not_a_list' -Context expected type: [number()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1062:15 - │ -1062 │ lists:max(not_a_list). - │ ^^^^^^^^^^ 'not_a_list'. -Expression has type: 'not_a_list' -Context expected type: [T] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1062:15 - │ -1062 │ lists:max(not_a_list). - │ ^^^^^^^^^^ 'not_a_list'. -Expression has type: 'not_a_list' -Context expected type: [dynamic()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1082:15 - │ -1082 │ lists:min(not_a_list). - │ ^^^^^^^^^^ 'not_a_list'. -Expression has type: 'not_a_list' -Context expected type: [T] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1082:15 - │ -1082 │ lists:min(not_a_list). - │ ^^^^^^^^^^ 'not_a_list'. -Expression has type: 'not_a_list' -Context expected type: [dynamic()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1100:5 - │ -1100 │ proplists:get_value(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L). -Expression has type: term() -Context expected type: pid() | 'undefined' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1106:5 - │ -1106 │ proplists:get_value(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L). -Expression has type: term() -Context expected type: pid() | 'undefined' | 'v' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1111:5 - │ -1111 │ proplists:get_value(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L). -Expression has type: term() -Context expected type: pid() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1117:5 - │ -1117 │ proplists:get_value(k, L, 3). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L, 3). -Expression has type: term() -Context expected type: pid() | number() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1123:5 - │ -1123 │ proplists:get_value(k, L, my_default). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L, 'my_default'). -Expression has type: term() -Context expected type: 'v1' | 'v2' | 'my_default' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1129:5 - │ -1129 │ proplists:get_value(k, L, my_default). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L, 'my_default'). -Expression has type: term() -Context expected type: 'v' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1134:28 - │ -1134 │ proplists:get_value(k, b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1139:5 - │ -1139 │ proplists:get_value(k, []). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', []). -Expression has type: term() -Context expected type: 'default' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1144:1 - │ -1144 │ proplists:get_value(k, [], my_default). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', [], 'my_default'). -Expression has type: term() -Context expected type: 'my_default' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1149:28 - │ -1149 │ proplists:get_value(k, b, my_default). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1154:5 - │ -1154 │ proplists:get_bool(b, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_bool('b', L). -Expression has type: boolean() -Context expected type: 'true' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1160:5 - │ -1160 │ proplists:get_all_values(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_all_values('k', L). -Expression has type: [term()] -Context expected type: [pid() | 'default'] - │ - -Because in the expression's type: - [ - Here the type is: term() - Context expects type: pid() | 'default' - No candidate matches in the expected union. - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1166:5 - │ -1166 │ proplists:get_all_values(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_all_values('k', L). -Expression has type: [term()] -Context expected type: [pid() | 'default' | 'v'] - │ - -Because in the expression's type: - [ - Here the type is: term() - Context expects type: pid() | 'default' | 'v' - No candidate matches in the expected union. - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1171:5 - │ -1171 │ proplists:get_all_values(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_all_values('k', L). -Expression has type: [term()] -Context expected type: [pid()] - │ - -Because in the expression's type: - [ - Here the type is: term() - Context expects type: pid() - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1176:5 - │ -1176 │ proplists:get_all_values(k, []). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_all_values('k', []). -Expression has type: [term()] -Context expected type: [] - │ - -Because in the expression's type: - [ - Here the type is: term() - Context expects type: none() - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1181:5 - │ -1181 │ proplists:get_all_values(k, b). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_all_values('k', 'b'). -Expression has type: [term()] -Context expected type: [] - │ - -Because in the expression's type: - [ - Here the type is: term() - Context expects type: none() - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1181:33 - │ -1181 │ proplists:get_all_values(k, b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1186:27 - │ -1186 │ proplists:get_bool(b, b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1191:5 - │ -1191 │ proplists:get_keys(L). - │ ^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_keys(L). -Expression has type: [term()] -Context expected type: ['c'] - │ - -Because in the expression's type: - [ - Here the type is: term() - Context expects type: 'c' - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1197:5 - │ -1197 │ proplists:get_keys(L). - │ ^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_keys(L). -Expression has type: [term()] -Context expected type: ['a' | 'b' | 'c'] - │ - -Because in the expression's type: - [ - Here the type is: term() - Context expects type: 'a' | 'b' | 'c' - No candidate matches in the expected union. - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1202:24 - │ -1202 │ proplists:get_keys(a). - │ ^ 'a'. -Expression has type: 'a' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1207:5 - │ -1207 │ ╭ ╭ proplists:get_keys( -1208 │ │ │ [{a, b, c}] -1209 │ │ │ ). - │ ╰─│─────^ proplists:get_keys([{'a', 'b', 'c'}]). -Expression has type: [term()] -Context expected type: ['a'] - │ ╰─────' - -Because in the expression's type: - [ - Here the type is: term() - Context expects type: 'a' - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1215:5 - │ -1215 │ proplists:get_value(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L). -Expression has type: term() -Context expected type: 'a' | pid() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1220:5 - │ -1220 │ proplists:get_value(k, b). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', 'b'). -Expression has type: term() -Context expected type: 'a' | pid() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1220:28 - │ -1220 │ proplists:get_value(k, b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1225:1 - │ -1225 │ proplists:lookup(self(), [a, {b, true}]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:lookup(erlang:self(), ['a', {'b', 'true'}]). -Expression has type: 'none' | tuple() -Context expected type: {'b', 'true'} - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: tuple() - However the following candidate: 'none' - Differs from the expected type: {'b', 'true'} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1229:25 - │ -1229 │ proplists:lookup(a, b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1239:5 - │ -1239 │ proplists:lookup(k, []). - │ ^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:lookup('k', []). -Expression has type: 'none' | tuple() -Context expected type: 'none' - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: 'none' - However the following candidate: tuple() - Differs from the expected type: 'none' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1252:29 - │ -1252 │ proplists:lookup_all(a, b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1265:5 - │ -1265 │ ╭ ╭ proplists:lookup_all( -1266 │ │ │ self(), -1267 │ │ │ [] -1268 │ │ │ ). - │ ╰─│─────^ proplists:lookup_all(erlang:self(), []). -Expression has type: [tuple()] -Context expected type: [] - │ ╰─────' - -Because in the expression's type: - [ - Here the type is: tuple() - Context expects type: none() - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1291:34 - │ -1291 │ proplists:is_defined(self(), b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1295:25 - │ -1295 │ proplists:delete(k, b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [A] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1295:25 - │ -1295 │ proplists:delete(k, b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [dynamic()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1311:5 - │ -1311 │ proplists:split(L, Ks). - │ ^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:split(L, Ks). -Expression has type: {[[term()]], [term()]} -Context expected type: {[plist('a', 'b')], plist('a', 'b')} - │ - -Because in the expression's type: - { - [ - [ - Here the type is: term() - Context expects type: 'a' | {'a', 'b'} - No candidate matches in the expected union. - ] - ] - , [term()]} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1315:21 - │ -1315 │ proplists:split(b, []). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1319:25 - │ -1319 │ proplists:split([], b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1324:22 - │ -1324 │ proplists:to_map(b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [atom() | {term(), term()} | term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1358:24 - │ -1358 │ proplists:from_map(b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: #{K => V} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1358:24 - │ -1358 │ proplists:from_map(b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: #{dynamic() => dynamic()} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1363:5 - │ -1363 │ proplists:get_value(a, [a]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('a', ['a']). -Expression has type: term() -Context expected type: 'true' | 'undefined' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1368:5 - │ -1368 │ proplists:get_value(X, [a]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value(X, ['a']). -Expression has type: term() -Context expected type: 'true' | 'undefined' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1373:5 - │ -1373 │ proplists:get_value(a, [a], b). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('a', ['a'], 'b'). -Expression has type: term() -Context expected type: 'true' | 'b' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1416:5 - │ -1416 │ file:consult(some_file). - │ ^^^^^^^^^^^^^^^^^^^^^^^ file:consult('some_file'). -Expression has type: {'ok', [dynamic()]} | {'error', {number(), atom(), term()} | 'terminated' | 'badarg' | file:posix() | 'system_limit'} -Context expected type: 'nok' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1428:5 - │ -1428 │ lists:keysort(2, [{a, c}, {b, d}]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lists:keysort(2, [{'a', 'c'}, {'b', 'd'}]). -Expression has type: [{'a', 'c'} | {'b', 'd'}] -Context expected type: none() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1432:22 - │ -1432 │ lists:keysort(1, [3]). - │ ^^^ - │ │ - │ [3]. -Expression has type: [number()] -Context expected type: [tuple()] - │ - -Because in the expression's type: - [ - Here the type is: number() - Context expects type: tuple() - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1448:5 - │ -1448 │ ╭ ╭ lists:filtermap( -1449 │ │ │ fun(X) when X div 2 =:= 0 -> -1450 │ │ │ {true, integer_to_list(X)}; -1451 │ │ │ (X) -> - · │ │ -1454 │ │ │ [1, 2, 3, 4] -1455 │ │ │ ). - │ ╰─│─────^ lists:filtermap(fun, [1, 2, 3, 4]). -Expression has type: [string() | number()] -Context expected type: [number()] - │ ╰─────' - -Because in the expression's type: - [ - Here the type is a union type with some valid candidates: number() - However the following candidate: string() - Differs from the expected type: number() - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1482:5 - │ -1482 │ erlang:min(X, Y). - │ ^^^^^^^^^^^^^^^^ erlang:min(X, Y). -Expression has type: number() -Context expected type: none() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1497:5 - │ -1497 │ erlang:max(X, Y). - │ ^^^^^^^^^^^^^^^^ erlang:max(X, Y). -Expression has type: atom() | number() -Context expected type: none() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1523:9 - │ -1523 │ abs(Atom). - │ ^^^^ Atom. -Expression has type: 'a' -Context expected type: number() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1536:31 - │ -1536 │ seq3_4_wip_neg() -> lists:seq(a, 2, 1). - │ ^ 'a'. -Expression has type: 'a' -Context expected type: number() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1539:30 - │ -1539 │ seq3_5_neg() -> lists:seq(1, a, 1). - │ ^ 'a'. -Expression has type: 'a' -Context expected type: number() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1542:33 - │ -1542 │ seq3_6_neg() -> lists:seq(1, 2, a). - │ ^ 'a'. -Expression has type: 'a' -Context expected type: number() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1570:31 - │ -1570 │ seq2_4_wip_neg() -> lists:seq(a, 2). - │ ^ 'a'. -Expression has type: 'a' -Context expected type: number() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1573:30 - │ -1573 │ seq2_5_neg() -> lists:seq(1, a). - │ ^ 'a'. -Expression has type: 'a' -Context expected type: number() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1622:5 - │ -1622 │ ╭ timer:tc( -1623 │ │ fun() -> -1624 │ │ err -1625 │ │ end -1626 │ │ ). - │ ╰─────^ timer:tc(fun). -Expression has type: {number(), 'err'} -Context expected type: pid() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1635:5 - │ -1635 │ ets:lookup(tab, Any). - │ ^^^^^^^^^^^^^^^^^^^^ ets:lookup('tab', Any). -Expression has type: [dynamic()] -Context expected type: pid() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1639:5 - │ -1639 │ ets:lookup("not atom", Any). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ets:lookup(string_lit, Any). -Expression has type: [dynamic()] -Context expected type: pid() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1639:16 - │ -1639 │ ets:lookup("not atom", Any). - │ ^^^^^^^^^^ string_lit. -Expression has type: string() -Context expected type: ets:tab() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1663:18 - │ -1663 │ ets:tab2list("not atom"). - │ ^^^^^^^^^^ string_lit. -Expression has type: string() -Context expected type: ets:tab() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1702:23 - │ -1702 │ lists:flatten([], 1). - │ ^ 1. -Expression has type: number() -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1711:19 - │ -1711 │ lists:flatten(3). - │ ^ 3. -Expression has type: number() -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1724:8 - │ -1724 │ -> lists:flatten(X). - │ ^^^^^^^^^^^^^^^^ - │ │ - │ lists:flatten(X). -Expression has type: [{A, B} | {B, A}] -Context expected type: [{A, B}] - │ - -Because in the expression's type: - [ - { - Here the type is: B - Context expects type: A - , A} - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1757:5 - │ -1757 │ ╭ ╭ maps:without( -1758 │ │ │ [a, c, DOrE], -1759 │ │ │ #{ -1760 │ │ │ a => ka, - · │ │ -1764 │ │ │ } -1765 │ │ │ ). - │ ╰─│─────^ maps:without(['a', 'c', DOrE], #{..}). -Expression has type: #{a => 'ka', b => atom(), c => pid(), d => 'kd'} -Context expected type: #{b => atom()} - │ ╰─────' - -Because in the expression's type: - Here the type is: #{a => ...} - Context expects type: #{...} - The expected map has no corresponding key for: a. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1769:18 - │ -1769 │ maps:without(non_list, #{}). - │ ^^^^^^^^ 'non_list'. -Expression has type: 'non_list' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1773:22 - │ -1773 │ maps:without([], non_map). - │ ^^^^^^^ 'non_map'. -Expression has type: 'non_map' -Context expected type: #{term() => term()} - -error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) - ┌─ check/src/custom.erl:1808:1 - │ -1808 │ ╭ maps_without_12_neg(None) -> -1809 │ │ maps:without( -1810 │ │ [a, b], -1811 │ │ None -1812 │ │ ). - │ ╰─────^ Clause is not covered by spec - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1818:5 - │ -1818 │ ╭ ╭ maps:without( -1819 │ │ │ Keys, -1820 │ │ │ #{a => self(), b => self()} -1821 │ │ │ ). - │ ╰─│─────^ maps:without(Keys, #{..}). -Expression has type: #{a => pid(), b => pid()} -Context expected type: #{b := pid()} - │ ╰─────' - -Because in the expression's type: - Here the type is: #{b => ..., ...} - Context expects type: #{b := ..., ...} - The type of the expression is missing the following required keys: b. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1826:5 - │ -1826 │ ╭ ╭ maps:without( -1827 │ │ │ [a | improper_tail], -1828 │ │ │ #{a => self(), b => self()} -1829 │ │ │ ). - │ ╰─│─────^ maps:without(['a' | 'improper_tail'], #{..}). -Expression has type: #{a => pid(), b => pid()} -Context expected type: #{b := pid()} - │ ╰─────' - -Because in the expression's type: - Here the type is: #{b => ..., ...} - Context expects type: #{b := ..., ...} - The type of the expression is missing the following required keys: b. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1827:12 - │ -1827 │ [a | improper_tail], - │ ^^^^^^^^^^^^^^^^ 'improper_tail'. -Expression has type: 'improper_tail' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1837:5 - │ -1837 │ ╭ ╭ maps:without( -1838 │ │ │ Keys, -1839 │ │ │ #{a => ka, b => self()} -1840 │ │ │ ). - │ ╰─│─────^ maps:without(Keys, #{..}). -Expression has type: #{a => 'ka', b => pid()} -Context expected type: #{b := pid()} - │ ╰─────' - -Because in the expression's type: - Here the type is: #{b => ..., ...} - Context expects type: #{b := ..., ...} - The type of the expression is missing the following required keys: b. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1854:5 - │ -1854 │ maps:without([a, b], M). - │ ^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ maps:without(['a', 'b'], M). -Expression has type: #{c := 'cv', d := 'dv'} | #{c := 'cv', e => 'ev'} -Context expected type: #{c := atom()} - │ - -Because in the expression's type: - Here the type is: #{d := ...} - Context expects type: #{...} - The expected map has no corresponding key for: d. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1916:23 - │ -1916 │ custom_overloaded(X). - │ ^ X. -Expression has type: term() -Context expected type: atom() | binary() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1939:5 - │ -1939 │ {A, N}. - │ ^^^^^^ - │ │ - │ {A, N}. -Expression has type: {atom(), number() | pid()} -Context expected type: {atom(), number()} - │ - -Because in the expression's type: - { atom(), - Here the type is a union type with some valid candidates: number() - However the following candidate: pid() - Differs from the expected type: number() - } - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2041:5 - │ -2041 │ filename:join(["server", "erl"]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ filename:join([string_lit, string_lit]). -Expression has type: file:filename_all() -Context expected type: file:filename() - │ - -Because in the expression's type: - Here the type is: string() | binary() - Context expects type: string() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2046:5 - │ -2046 │ filename:join(["server", <<>>]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ filename:join([string_lit, <<..>>]). -Expression has type: file:filename_all() -Context expected type: file:filename() - │ - -Because in the expression's type: - Here the type is: string() | binary() - Context expects type: string() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2051:5 - │ -2051 │ filename:join([<<>>, ""]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ filename:join([<<..>>, string_lit]). -Expression has type: file:filename_all() -Context expected type: file:filename() - │ - -Because in the expression's type: - Here the type is: string() | binary() - Context expects type: string() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2066:5 - │ -2066 │ filename:join([<<>>, <<>>]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ filename:join([<<..>>, <<..>>]). -Expression has type: file:filename_all() -Context expected type: binary() - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: binary() - However the following candidate: string() - Differs from the expected type: binary() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2081:19 - │ -2081 │ filename:join([<<>>, self()]). - │ ^^^^^^^^^^^^^^ - │ │ - │ [<<..>>, erlang:self()]. -Expression has type: [binary() | pid()] -Context expected type: [file:name_all()] - │ - -Because in the expression's type: - [ - Here the type is a union type with some valid candidates: binary() - However the following candidate: pid() - Differs from the expected type: string() | atom() | file:deep_list() | binary() - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2086:5 - │ -2086 │ filename:join("server", "erl"). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ filename:join(string_lit, string_lit). -Expression has type: file:filename_all() -Context expected type: file:filename() - │ - -Because in the expression's type: - Here the type is: string() | binary() - Context expects type: string() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2091:5 - │ -2091 │ filename:join("server", <<>>). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ filename:join(string_lit, <<..>>). -Expression has type: file:filename_all() -Context expected type: file:filename() - │ - -Because in the expression's type: - Here the type is: string() | binary() - Context expects type: string() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2096:5 - │ -2096 │ filename:join(<<>>, ""). - │ ^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ filename:join(<<..>>, string_lit). -Expression has type: file:filename_all() -Context expected type: file:filename() - │ - -Because in the expression's type: - Here the type is: string() | binary() - Context expects type: string() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2111:5 - │ -2111 │ filename:join(atom, <<>>). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ filename:join('atom', <<..>>). -Expression has type: file:filename_all() -Context expected type: binary() - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: binary() - However the following candidate: string() - Differs from the expected type: binary() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2121:25 - │ -2121 │ filename:join(<<>>, self()). - │ ^^^^^^ - │ │ - │ erlang:self(). -Expression has type: pid() -Context expected type: file:name_all() - │ - -Because in the expression's type: - Here the type is: pid() - Context expects type: string() | atom() | file:deep_list() | binary() - No candidate matches in the expected union. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2150:5 - │ -2150 │ ╭ ╭ queue:filter( -2151 │ │ │ fun my_filter1/1, -2152 │ │ │ Q -2153 │ │ │ ). - │ ╰─│─────^ queue:filter(my_filter1/1, Q). -Expression has type: queue:queue(atom() | number()) -Context expected type: queue:queue(number()) - │ ╰─────' - -Because in the expression's type: - { - [ - Here the type is a union type with some valid candidates: number() - However the following candidate: atom() - Differs from the expected type: number() - ] - , [atom() | number()]} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2191:5 - │ -2191 │ M3. - │ ^^ - │ │ - │ M3. -Expression has type: #{count := number(), module := 'foo'} | #{module := 'foo'} -Context expected type: state1() - │ - -Because in the expression's type: - The type is a union type with some valid candidates: #{count := number(), module := 'foo'} - However, the following candidate doesn't match: - Here the type is: #{...} - Context expects type: #{count := ..., ...} - The type of the expression is missing the following required keys: count. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2223:13 - │ -2223 │ Atom + Sum - │ ^^^^ Atom. -Expression has type: atom() -Context expected type: number() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2279:5 - │ -2279 │ maps:remove(A, M). - │ ^^^^^^^^^^^^^^^^^ - │ │ - │ maps:remove(A, M). -Expression has type: #{a => number()} -Context expected type: #{a := number()} - │ - -Because in the expression's type: - Here the type is: #{a => ..., ...} - Context expects type: #{a := ..., ...} - The type of the expression is missing the following required keys: a. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2320:5 - │ -2320 │ ╭ ╭ maps:filtermap( -2321 │ │ │ fun -2322 │ │ │ (a, V) -> true; -2323 │ │ │ (b, V) -> {true, atom_to_binary(V)}; - · │ │ -2326 │ │ │ M -2327 │ │ │ ). - │ ╰─│─────^ maps:filtermap(fun, M). -Expression has type: #{a => atom() | binary(), b => atom() | binary(), c => atom() | binary()} -Context expected type: #{a := atom(), b := binary()} - │ ╰─────' - -Because in the expression's type: - Here the type is: #{a => ..., b => ..., ...} - Context expects type: #{a := ..., b := ..., ...} - The type of the expression is missing the following required keys: a, b. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2342:5 - │ -2342 │ ╭ ╭ maps:filtermap( -2343 │ │ │ fun (_, V) -> -2344 │ │ │ {true, atom_to_binary(V)} -2345 │ │ │ end, -2346 │ │ │ M -2347 │ │ │ ). - │ ╰─│─────^ maps:filtermap(fun, M). -Expression has type: #{atom() => binary()} -Context expected type: #{atom() => atom()} - │ ╰─────' - -Because in the expression's type: - #{ atom() => - Here the type is: binary() - Context expects type: atom() - , ... } - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2354:23 - │ -2354 │ fun (_, _) -> err end, - │ ^^^ - │ │ - │ 'err'. -Expression has type: 'err' -Context expected type: boolean() | {'true', term()} - │ - -Because in the expression's type: - Here the type is: 'err' - Context expects type: 'false' | 'true' | {'true', term()} - No candidate matches in the expected union. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2362:5 - │ -2362 │ ╭ ╭ maps:filtermap( -2363 │ │ │ fun (_, V) -> {true, atom_to_binary(V)} end, -2364 │ │ │ M -2365 │ │ │ ). - │ ╰─│─────^ maps:filtermap(fun, M). -Expression has type: #{atom() => binary()} -Context expected type: #{atom() => atom()} - │ ╰─────' - -Because in the expression's type: - #{ atom() => - Here the type is: binary() - Context expects type: atom() - , ... } - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2363:45 - │ -2363 │ fun (_, V) -> {true, atom_to_binary(V)} end, - │ ^ V. -Expression has type: binary() -Context expected type: atom() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2377:5 - │ -2377 │ re:replace(Subj, "+", "-", [{return, binary}]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ re:replace(Subj, string_lit, string_lit, [{'return', 'binary'}]). -Expression has type: binary() -Context expected type: string() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2381:5 - │ -2381 │ re:replace(Subj, "+", "-", [{return, list}]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ re:replace(Subj, string_lit, string_lit, [{'return', 'list'}]). -Expression has type: string() -Context expected type: binary() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2385:22 - │ -2385 │ Res = re:replace(Subj, "+", "-", [{return, list}]), - │ ^^^^ - │ │ - │ Subj. -Expression has type: atom() -Context expected type: iodata() | unicode:charlist() - │ - -Because in the expression's type: - Here the type is: atom() - Context expects type: iolist() | binary() - No candidate matches in the expected union. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2386:5 - │ -2386 │ Res. - │ ^^^ Res. -Expression has type: string() -Context expected type: binary() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2390:38 - │ -2390 │ Res = re:replace(Subj, "+", "-", [{return, something}]), - │ ^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ [{'return', 'something'}]. -Expression has type: [{'return', 'something'}] -Context expected type: [{'newline', 'cr' | 'lf' | 'any' | 'crlf' | 'anycrlf'} | 'notempty_atstart' | 'noteol' | 'bsr_unicode' | 'notbol' | 'global' | {'match_limit_recursion', number()} | 'bsr_anycrlf' | re:compile_option() | {'match_limit', number()} | {'offset', number()} | 'notempty' | {'return', 'iodata' | 'list' | 'binary'} | 'anchored'] - │ - -Because in the expression's type: - [ - { 'return', - Here the type is: 'something' - Context expects type: 'iodata' | 'list' | 'binary' - No candidate matches in the expected union. - } - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2506:5 - │ -2506 │ lists:partition(fun is_number/1, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ lists:partition(fun, L). -Expression has type: {[number()], [atom()]} -Context expected type: {[atom()], [number()]} - │ - -Because in the expression's type: - { - [ - Here the type is: number() - Context expects type: atom() - ] - , [atom()]} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2518:5 - │ -2518 │ lists:partition(fun({_Term, V}) -> is_number(V) end, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ lists:partition(fun, L). -Expression has type: {[{term(), number()}], [{term(), atom()}]} -Context expected type: {[{term(), atom()}], [{term(), number()}]} - │ - -Because in the expression's type: - { - [ - { term(), - Here the type is: number() - Context expects type: atom() - } - ] - , [{term(), atom()}]} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2536:5 - │ -2536 │ lists:partition(fun({ok, _}) -> true; (_) -> false end, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ lists:partition(fun, L). -Expression has type: {[{'ok', atom()}], [{'ok', atom()} | {'error', term()}]} -Context expected type: {[{'ok', atom()}], [{'error', term()}]} - │ - -Because in the expression's type: - { [{'ok', atom()}], - [ - { - Here the type is: 'ok' - Context expects type: 'error' - , atom()} - ] - } - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2576:33 - │ -2576 │ maps_intersect_2_neg(M1, M2) -> maps:intersect(M1, M2). - │ ^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ maps:intersect(M1, M2). -Expression has type: #{a => number()} -Context expected type: #{a := number()} - │ - -Because in the expression's type: - Here the type is: #{a => ..., ...} - Context expects type: #{a := ..., ...} - The type of the expression is missing the following required keys: a. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2586:33 - │ -2586 │ maps_intersect_4_neg(M1, M2) -> maps:intersect(M1, M2). - │ ^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ maps:intersect(M1, M2). -Expression has type: #{a => number()} -Context expected type: #{a => 'true'} - │ - -Because in the expression's type: - #{ a => - Here the type is: number() - Context expects type: 'true' - , ... } - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2596:33 - │ -2596 │ maps_intersect_6_neg(M1, M2) -> maps:intersect(M1, M2). - │ ^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ maps:intersect(M1, M2). -Expression has type: #{a => number()} -Context expected type: #{a := number()} - │ - -Because in the expression's type: - Here the type is: #{a => ..., ...} - Context expects type: #{a := ..., ...} - The type of the expression is missing the following required keys: a. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2709:40 - │ -2709 │ (foo, A) -> binary_to_atom(A); - │ ^ A. -Expression has type: atom() -Context expected type: binary() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2710:40 - │ -2710 │ (bar, B) -> atom_to_binary(B); - │ ^ B. -Expression has type: binary() -Context expected type: atom() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2711:50 - │ -2711 │ ({foo, bar}, I) -> binary_to_integer(I); - │ ^ I. -Expression has type: number() -Context expected type: binary() - -202 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-28.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-28.pretty deleted file mode 100644 index 5220990b8f..0000000000 --- a/crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-28.pretty +++ /dev/null @@ -1,2540 +0,0 @@ -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:26:5 - │ -26 │ element(4, Tup). - │ ^^^^^^^^^^^^^^^ erlang:element(4, Tup). -Expression has type: #{dynamic() => dynamic()} -Context expected type: atom() - -error: index_out_of_bounds (See https://fb.me/eqwalizer_errors#index_out_of_bounds) - ┌─ check/src/custom.erl:30:5 - │ -30 │ element(42, Tup). - │ ^^^^^^^^^^^^^^^^ 42. -Tried to access element 42 of a tuple with 3 elements - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:40:5 - │ -40 │ element(2, Tup). - │ ^^^^^^^^^^^^^^^ erlang:element(2, Tup). -Expression has type: number() | string() | atom() -Context expected type: #{dynamic() => dynamic()} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:44:16 - │ -44 │ element(2, Tup). - │ ^^^ - │ │ - │ Tup. -Expression has type: {atom(), string()} | [dynamic()] -Context expected type: tuple() - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: {atom(), string()} - However the following candidate: [dynamic()] - Differs from the expected type: tuple() - -error: index_out_of_bounds (See https://fb.me/eqwalizer_errors#index_out_of_bounds) - ┌─ check/src/custom.erl:48:5 - │ -48 │ element(42, Tup). - │ ^^^^^^^^^^^^^^^^ 42. -Tried to access element 42 of a tuple with 2 elements - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:54:5 - │ -54 │ element(N, Tup). - │ ^^^^^^^^^^^^^^^ erlang:element(N, Tup). -Expression has type: atom() | number() | string() -Context expected type: #{dynamic() => dynamic()} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:58:5 - │ -58 │ element(N, Tup). - │ ^^^^^^^^^^^^^^^ - │ │ - │ erlang:element(N, Tup). -Expression has type: atom() | number() -Context expected type: atom() - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: atom() - However the following candidate: number() - Differs from the expected type: atom() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:68:5 - │ -68 │ element(1, Tup). - │ ^^^^^^^^^^^^^^^ - │ │ - │ erlang:element(1, Tup). -Expression has type: dynamic() | number() -Context expected type: atom() - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: dynamic() - However the following candidate: number() - Differs from the expected type: atom() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:86:5 - │ -86 │ element(N, Rec). - │ ^^^^^^^^^^^^^^^ - │ │ - │ erlang:element(N, Rec). -Expression has type: 'foo' | 'ok' | 'error' | number() | string() -Context expected type: atom() - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: 'error' | 'foo' | 'ok' - However the following candidate: string() - Differs from the expected type: atom() - -error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) - ┌─ check/src/custom.erl:91:1 - │ -91 │ ╭ element_2_none_1(Tup) -> -92 │ │ element(42, Tup). - │ ╰────────────────────^ Clause is not covered by spec - -error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) - ┌─ check/src/custom.erl:95:1 - │ -95 │ ╭ element_2_none_2(N, Tup) -> -96 │ │ element(N, Tup). - │ ╰───────────────────^ Clause is not covered by spec - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:108:5 - │ -108 │ maps:get(K, M). - │ ^^^^^^^^^^^^^^ maps:get(K, M). -Expression has type: number() -Context expected type: atom() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:128:5 - │ -128 │ Res. - │ ^^^ Res. -Expression has type: number() -Context expected type: atom() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:154:12 - │ -154 │ get(a, M). - │ ^ M. -Expression has type: term() -Context expected type: #{term() => term()} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:160:23 - │ -160 │ Res = maps:get(a, M), - │ ^ M. -Expression has type: term() -Context expected type: #{term() => term()} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:167:17 - │ -167 │ maps:get(a, M, false). - │ ^ M. -Expression has type: term() -Context expected type: #{term() => term()} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:173:23 - │ -173 │ Res = maps:get(a, M, false), - │ ^ M. -Expression has type: term() -Context expected type: #{term() => term()} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:193:5 - │ -193 │ maps:get(K, M, 0). - │ ^^^^^^^^^^^^^^^^^ - │ │ - │ maps:get(K, M, 0). -Expression has type: number() | atom() -Context expected type: atom() - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: atom() - However the following candidate: number() - Differs from the expected type: atom() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:200:5 - │ -200 │ Res. - │ ^^^ - │ │ - │ Res. -Expression has type: number() | atom() -Context expected type: atom() - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: atom() - However the following candidate: number() - Differs from the expected type: atom() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:206:5 - │ -206 │ ╭ ╭ { -207 │ │ │ maps:get(a, M, undefined), -208 │ │ │ maps:get(n, M, undefined) -209 │ │ │ }. - │ ╰─│─────^ {maps:get('a', M, 'undefined'), maps:get('n', M, 'undefined')}. -Expression has type: {atom(), 'undefined' | number()} -Context expected type: {atom(), number()} - │ ╰─────' - -Because in the expression's type: - { atom(), - Here the type is a union type with some valid candidates: number() - However the following candidate: 'undefined' - Differs from the expected type: number() - } - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:221:27 - │ -221 │ map_get_2_17_neg(V, M) -> maps:get(V, M). - │ ^^^^^^^^^^^^^^ - │ │ - │ maps:get(V, M). -Expression has type: 'a_v' | 'c_v' -Context expected type: 'a_v' | 'b_v' - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: 'a_v' - However the following candidate: 'c_v' - Differs from the expected type: 'a_v' | 'b_v' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:233:27 - │ -233 │ map_get_3_19_neg(V, M) -> maps:get(V, M, undefined). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ maps:get(V, M, 'undefined'). -Expression has type: 'undefined' | 'a_v' | 'c_v' -Context expected type: 'a_v' | 'b_v' | 'undefined' - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: 'undefined' | 'a_v' - However the following candidate: 'c_v' - Differs from the expected type: 'a_v' | 'b_v' | 'undefined' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:309:5 - │ -309 │ Res. - │ ^^^ Res. -Expression has type: {'value', #{}} | 'false' -Context expected type: 'nok' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:363:23 - │ -363 │ fun (_, _) -> self() end, - │ ^^^^^^ erlang:self(). -Expression has type: pid() -Context expected type: boolean() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:382:17 - │ -382 │ maps:filter(F, M). - │ ^ F. -Expression has type: fun(() -> pid()) -Context expected type: fun((number(), 'a' | 'b') -> boolean()) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:389:17 - │ -389 │ maps:filter(F, M). - │ ^ - │ │ - │ F. -Expression has type: fun((fun((T) -> boolean()), [T]) -> [T]) with 1 type parameter -Context expected type: fun((number(), 'a' | 'b') -> boolean()) with 0 type parameters - │ - -Because in the expression's type: - Here the type is: fun()/2 with 1 type parameter - Context expects type: fun((number(), 'a' | 'b') -> boolean()) with 0 type parameters - The number of type parameters doesn't match. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:413:37 - │ -413 │ maps:filter(fun erlang:'=:='/2, X). - │ ^ - │ │ - │ X. -Expression has type: #{K => V} | 'a' -Context expected type: #{term() => term()} | maps:iterator() - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: #{K => V} - However the following candidate: 'a' - Differs from the expected type: #{term() => term()} | maps:iterator() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:425:20 - │ -425 │ maps:filter(F, non_kv), - │ ^^^^^^ 'non_kv'. -Expression has type: 'non_kv' -Context expected type: #{term() => term()} | maps:iterator() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:454:5 - │ -454 │ ╭ ╭ maps:map( -455 │ │ │ fun (_, _) -> self() end, -456 │ │ │ M -457 │ │ │ ). - │ ╰─│─────^ maps:map(fun, M). -Expression has type: #{number() => pid()} -Context expected type: #{number() => boolean()} - │ ╰─────' - -Because in the expression's type: - #{ number() => - Here the type is: pid() - Context expects type: boolean() - , ... } - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:474:14 - │ -474 │ maps:map(F, M). - │ ^ F. -Expression has type: fun(() -> pid()) -Context expected type: fun((number(), 'a' | 'b') -> term()) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:481:14 - │ -481 │ maps:map(F, M). - │ ^ - │ │ - │ F. -Expression has type: fun((fun((A) -> B), [A]) -> [B]) with 2 type parameters -Context expected type: fun((number(), 'a' | 'b') -> term()) with 0 type parameters - │ - -Because in the expression's type: - Here the type is: fun((fun((A) -> B), [A]) -> [B]) with 2 type parameters - Context expects type: fun((number(), 'a' | 'b') -> term()) with 0 type parameters - The number of type parameters doesn't match. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:497:5 - │ -497 │ maps:map(F, M). - │ ^^^^^^^^^^^^^^ - │ │ - │ maps:map(F, M). -Expression has type: #{a := boolean(), b := boolean()} -Context expected type: #{a => 'a', b => 'b'} - │ - -Because in the expression's type: - #{ a => - Here the type is: boolean() - Context expects type: 'a' - , ... } - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:503:17 - │ -503 │ maps:map(F, non_kv), - │ ^^^^^^ 'non_kv'. -Expression has type: 'non_kv' -Context expected type: #{term() => term()} | maps:iterator() - -error: fun_arity_mismatch (See https://fb.me/eqwalizer_errors#fun_arity_mismatch) - ┌─ check/src/custom.erl:538:9 - │ -538 │ fun (K, V) -> [K, V] end, [], M). - │ ^^^^^^^^^^^^^^^^^^^^^^^^ fun. -fun with arity 2 used as fun with 3 arguments - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:544:5 - │ -544 │ ╭ ╭ maps:fold( -545 │ │ │ fun (_, _, Acc) -> [Acc] end, -546 │ │ │ [], -547 │ │ │ M -548 │ │ │ ). - │ ╰─│─────^ maps:fold(fun, [], M). -Expression has type: [[[]]] -Context expected type: [number() | 'a' | 'b'] - │ ╰─────' - -Because in the expression's type: - [ - Here the type is: [[]] - Context expects type: number() | 'a' | 'b' - No candidate matches in the expected union. - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:545:28 - │ -545 │ fun (_, _, Acc) -> [Acc] end, - │ ^^^^^ - │ │ - │ [Acc]. -Expression has type: [[[[]]]] -Context expected type: [[[]]] - │ - -Because in the expression's type: - [ - [ - [ - Here the type is: [] - Context expects type: none() - ] - ] - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:552:5 - │ -552 │ ╭ maps:fold( -553 │ │ fun (_, _, Acc) -> Acc end, -554 │ │ [], -555 │ │ non_kv -556 │ │ ). - │ ╰─────^ maps:fold(fun, [], 'non_kv'). -Expression has type: [] -Context expected type: 'nok' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:555:9 - │ -555 │ non_kv - │ ^^^^^^ 'non_kv'. -Expression has type: 'non_kv' -Context expected type: #{term() => term()} | maps:iterator() - -error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) - ┌─ check/src/custom.erl:560:1 - │ -560 │ ╭ maps_fold_3_6(None) -> -561 │ │ maps:fold( -562 │ │ None, -563 │ │ #{}, -564 │ │ #{1 => 1} -565 │ │ ). - │ ╰─────^ Clause is not covered by spec - -error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) - ┌─ check/src/custom.erl:568:1 - │ -568 │ ╭ maps_fold_3_7(None) -> -569 │ │ maps:fold( -570 │ │ None, -571 │ │ None, -572 │ │ None -573 │ │ ). - │ ╰─────^ Clause is not covered by spec - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:578:5 - │ -578 │ ╭ ╭ maps:fold( -579 │ │ │ fun (_K, A, _Acc) -> A end, -580 │ │ │ [], -581 │ │ │ M -582 │ │ │ ). - │ ╰─│─────^ maps:fold(fun, [], M). -Expression has type: [] | atom() -Context expected type: atom() - │ ╰─────' - -Because in the expression's type: - Here the type is a union type with some valid candidates: atom() - However the following candidate: [] - Differs from the expected type: atom() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:601:9 - │ -601 │ fun folder_bad/3, - │ ^^^^^^^^^^^^^^^^ - │ │ - │ folder_bad/3. -Expression has type: fun((term(), term(), Acc) -> [Acc]) with 1 type parameter -Context expected type: fun((number(), 'a', []) -> term()) with 0 type parameters - │ - -Because in the expression's type: - Here the type is: fun((term(), term(), Acc) -> [Acc]) with 1 type parameter - Context expects type: fun((number(), 'a', []) -> term()) with 0 type parameters - The number of type parameters doesn't match. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:601:9 - │ -601 │ fun folder_bad/3, - │ ^^^^^^^^^^^^^^^^ - │ │ - │ folder_bad/3. -Expression has type: fun((term(), term(), Acc) -> [Acc]) with 1 type parameter -Context expected type: fun((number(), 'a', [] | dynamic()) -> term()) with 0 type parameters - │ - -Because in the expression's type: - Here the type is: fun((term(), term(), Acc) -> [Acc]) with 1 type parameter - Context expects type: fun()/3 with 0 type parameters - The number of type parameters doesn't match. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:601:9 - │ -601 │ fun folder_bad/3, - │ ^^^^^^^^^^^^^^^^ - │ │ - │ folder_bad/3. -Expression has type: fun((term(), term(), Acc) -> [Acc]) with 1 type parameter -Context expected type: fun((number(), 'a', [] | dynamic()) -> [] | dynamic()) with 0 type parameters - │ - -Because in the expression's type: - Here the type is: fun((term(), term(), Acc) -> [Acc]) with 1 type parameter - Context expects type: fun()/3 with 0 type parameters - The number of type parameters doesn't match. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:636:5 - │ -636 │ ╭ ╭ maps:fold( -637 │ │ │ fun -638 │ │ │ (_K, {i, I}, Acc) -> -639 │ │ │ [I | Acc]; - · │ │ -646 │ │ │ M -647 │ │ │ ). - │ ╰─│─────^ maps:fold(fun, [], M). -Expression has type: [number() | binary() | atom()] -Context expected type: [binary()] | [number()] | [atom()] - │ ╰─────' - -Because in the expression's type: - [ - Here the type is a union type with some valid candidates: binary() - However the following candidate: number() - Differs from the expected type: binary() - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:671:41 - │ -671 │ maps_to_list_7_neg(Num) -> maps:to_list(Num). - │ ^^^ Num. -Expression has type: number() -Context expected type: #{term() => term()} | maps:iterator() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:675:25 - │ -675 │ maps_merge_1(M1, M2) -> maps:merge(M1, M2). - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ maps:merge(M1, M2). -Expression has type: #{'b' | 'a' | 'c' => number() | string() | atom()} -Context expected type: #{a => string(), b => number(), c => atom()} - │ - -Because in the expression's type: - Here the type is: #{'b' | 'a' | 'c' => number() | string() | atom()} - Context expects type: #{...} (no default association) - The expected map has no default association while the type of the expression has one. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:679:25 - │ -679 │ maps_merge_2(M1, M2) -> maps:merge(M1, M2). - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ maps:merge(M1, M2). -Expression has type: #{'b' | 'a' | 'c' => number() | string() | atom()} -Context expected type: #{a => string(), b => number() | boolean(), c => atom()} - │ - -Because in the expression's type: - Here the type is: #{'b' | 'a' | 'c' => number() | string() | atom()} - Context expects type: #{...} (no default association) - The expected map has no default association while the type of the expression has one. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:683:25 - │ -683 │ maps_merge_3(M1, M2) -> maps:merge(M1, M2). - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ maps:merge(M1, M2). -Expression has type: #{'b' | 'a' | 'c' => number() | string() | atom()} -Context expected type: #{a := string(), b := boolean(), c => atom()} - │ - -Because in the expression's type: - Here the type is: #{...} - Context expects type: #{a := ..., b := ..., ...} - The type of the expression is missing the following required keys: a, b. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:697:44 - │ -697 │ maps_merge_7_neg(M1, M2) -> maps:merge(M1, M2). - │ ^^ M2. -Expression has type: number() -Context expected type: #{a => binary()} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:701:25 - │ -701 │ maps_merge_8(M1, M2) -> maps:merge(M1, M2). - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ maps:merge(M1, M2). -Expression has type: #{'a' | 'b' => atom() | number()} -Context expected type: #{a := atom(), b := number()} | #{a := atom()} - │ - -Because in the expression's type: - Here the type is: #{...} - Context expects type: #{a := ..., b := ..., ...} - The type of the expression is missing the following required keys: a, b. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:721:9 - │ -721 │ fun erlang:binary_to_list/1, - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:binary_to_list/1. -Expression has type: fun((binary()) -> [number()]) -Context expected type: fun((number()) -> boolean() | {'true', term()}) - │ - -Because in the expression's type: - fun((binary()) -> - Here the type is: [number()] - Context expects type: 'false' | 'true' | {'true', term()} - No candidate matches in the expected union. - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:739:20 - │ -739 │ (3) -> wrong_ret end, - │ ^^^^^^^^^ - │ │ - │ 'wrong_ret'. -Expression has type: 'wrong_ret' -Context expected type: boolean() | {'true', term()} - │ - -Because in the expression's type: - Here the type is: 'wrong_ret' - Context expects type: 'false' | 'true' | {'true', term()} - No candidate matches in the expected union. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:749:9 - │ -749 │ not_a_list - │ ^^^^^^^^^^ 'not_a_list'. -Expression has type: 'not_a_list' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:791:17 - │ -791 │ Res - │ ^^^ - │ │ - │ Res. -Expression has type: {'true', 'a'} | 'wrong_ret' -Context expected type: boolean() | {'true', term()} - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: {'true', 'a'} - However the following candidate: 'wrong_ret' - Differs from the expected type: 'false' | 'true' | {'true', term()} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:827:9 - │ -827 │ ╭ ╭ fun (a) -> [a]; -828 │ │ │ (b) -> true; -829 │ │ │ (c) -> wrong_ret end, - │ ╰─│────────────────────────────────^ fun. -Expression has type: fun(('a' | 'b') -> ['a'] | 'true' | 'wrong_ret') -Context expected type: fun(('a' | 'b') -> boolean() | ['a' | 'b']) - │ ╰────────────────────────────────' - -Because in the expression's type: - fun(('a' | 'b') -> - Here the type is a union type with some valid candidates: ['a'] | 'true' - However the following candidate: 'wrong_ret' - Differs from the expected type: 'false' | 'true' | ['a' | 'b'] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:829:20 - │ -829 │ (c) -> wrong_ret end, - │ ^^^^^^^^^ - │ │ - │ 'wrong_ret'. -Expression has type: 'wrong_ret' -Context expected type: boolean() | ['a' | 'b'] - │ - -Because in the expression's type: - Here the type is: 'wrong_ret' - Context expects type: 'false' | 'true' | ['a' | 'b'] - No candidate matches in the expected union. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:837:9 - │ -837 │ ╭ ╭ fun (1) -> {true, a}; -838 │ │ │ (2) -> true end, - │ ╰─│───────────────────────────^ fun. -Expression has type: fun((dynamic()) -> {'true', 'a'} | 'true') -Context expected type: fun((Item) -> boolean() | [Item]) - │ ╰───────────────────────────' - -Because in the expression's type: - fun((dynamic()) -> - Here the type is a union type with some valid candidates: 'true' - However the following candidate: {'true', 'a'} - Differs from the expected type: 'false' | 'true' | [Item] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:837:20 - │ -837 │ fun (1) -> {true, a}; - │ ^^^^^^^^^ - │ │ - │ {'true', 'a'}. -Expression has type: {'true', 'a'} -Context expected type: boolean() | [dynamic()] - │ - -Because in the expression's type: - Here the type is: {'true', 'a'} - Context expects type: 'false' | 'true' | [dynamic()] - No candidate matches in the expected union. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:839:9 - │ -839 │ not_a_queue - │ ^^^^^^^^^^^ - │ │ - │ 'not_a_queue'. -Expression has type: 'not_a_queue' -Context expected type: queue:queue(Item) - │ - -Because in the expression's type: - Here the type is: 'not_a_queue' - Context expects type: {[Item], [Item]} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:839:9 - │ -839 │ not_a_queue - │ ^^^^^^^^^^^ - │ │ - │ 'not_a_queue'. -Expression has type: 'not_a_queue' -Context expected type: queue:queue(dynamic()) - │ - -Because in the expression's type: - Here the type is: 'not_a_queue' - Context expects type: {[dynamic()], [dynamic()]} - -error: fun_arity_mismatch (See https://fb.me/eqwalizer_errors#fun_arity_mismatch) - ┌─ check/src/custom.erl:846:9 - │ -846 │ ╭ fun (wrong, arity) -> -847 │ │ [a] -848 │ │ end, - │ ╰───────────^ fun. -fun with arity 2 used as fun with 1 arguments - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:857:9 - │ -857 │ ╭ ╭ fun (1) -> {true, a}; -858 │ │ │ (X) -> case X of -859 │ │ │ true -> -860 │ │ │ [a]; - · │ │ -863 │ │ │ end -864 │ │ │ end, - │ ╰─│───────────^ fun. -Expression has type: fun(('a' | 'b') -> ['a'] | 'false' | {'true', 'a'}) -Context expected type: fun(('a' | 'b') -> boolean() | ['a' | 'b']) - │ ╰───────────' - -Because in the expression's type: - fun(('a' | 'b') -> - Here the type is a union type with some valid candidates: ['a'] | 'false' - However the following candidate: {'true', 'a'} - Differs from the expected type: 'false' | 'true' | ['a' | 'b'] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:857:20 - │ -857 │ fun (1) -> {true, a}; - │ ^^^^^^^^^ - │ │ - │ {'true', 'a'}. -Expression has type: {'true', 'a'} -Context expected type: boolean() | ['a' | 'b'] - │ - -Because in the expression's type: - Here the type is: {'true', 'a'} - Context expects type: 'false' | 'true' | ['a' | 'b'] - No candidate matches in the expected union. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:873:9 - │ -873 │ ╭ ╭ fun (a) -> [a]; -874 │ │ │ (X) -> -875 │ │ │ Res = case X of -876 │ │ │ true -> - · │ │ -881 │ │ │ Res -882 │ │ │ end, - │ ╰─│───────────^ fun. -Expression has type: fun(('a' | 'b') -> ['a'] | 'wrong_ret') -Context expected type: fun(('a' | 'b') -> boolean() | ['a' | 'b']) - │ ╰───────────' - -Because in the expression's type: - fun(('a' | 'b') -> - Here the type is a union type with some valid candidates: ['a'] - However the following candidate: 'wrong_ret' - Differs from the expected type: 'false' | 'true' | ['a' | 'b'] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:881:17 - │ -881 │ Res - │ ^^^ - │ │ - │ Res. -Expression has type: ['a'] | 'wrong_ret' -Context expected type: boolean() | ['a' | 'b'] - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: ['a'] - However the following candidate: 'wrong_ret' - Differs from the expected type: 'false' | 'true' | ['a' | 'b'] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:891:9 - │ -891 │ fun list_to_atom/1, - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:list_to_atom/1. -Expression has type: fun((string()) -> atom()) -Context expected type: fun((dynamic()) -> boolean() | [dynamic()]) - │ - -Because in the expression's type: - fun((string()) -> - Here the type is: atom() - Context expects type: 'false' | 'true' | [dynamic()] - No candidate matches in the expected union. - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:891:9 - │ -891 │ fun list_to_atom/1, - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:list_to_atom/1. -Expression has type: fun((string()) -> atom()) -Context expected type: fun(('a' | 'b') -> boolean() | ['a' | 'b']) - │ - -Because in the expression's type: - fun((string()) -> - Here the type is: atom() - Context expects type: 'false' | 'true' | ['a' | 'b'] - No candidate matches in the expected union. - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:900:9 - │ -900 │ fun atom_to_list/1, - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:atom_to_list/1. -Expression has type: fun((atom()) -> string()) -Context expected type: fun((atom()) -> boolean() | [atom()]) - │ - -Because in the expression's type: - fun((atom()) -> - Here the type is: string() - Context expects type: boolean() | [atom()] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:900:9 - │ -900 │ fun atom_to_list/1, - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:atom_to_list/1. -Expression has type: fun((atom()) -> string()) -Context expected type: fun((dynamic(atom())) -> boolean() | [dynamic(atom())]) - │ - -Because in the expression's type: - fun((atom()) -> - Here the type is: string() - Context expects type: boolean() | [dynamic(atom())] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:909:9 - │ -909 │ fun atom_to_list/1, - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:atom_to_list/1. -Expression has type: fun((atom()) -> string()) -Context expected type: fun((atom()) -> boolean() | [atom()]) - │ - -Because in the expression's type: - fun((atom()) -> - Here the type is: string() - Context expects type: boolean() | [atom()] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:909:9 - │ -909 │ fun atom_to_list/1, - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:atom_to_list/1. -Expression has type: fun((atom()) -> string()) -Context expected type: fun((dynamic(atom())) -> boolean() | [dynamic(atom())]) - │ - -Because in the expression's type: - fun((atom()) -> - Here the type is: string() - Context expects type: boolean() | [dynamic(atom())] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:919:9 - │ -919 │ fun atom_to_list/1, - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:atom_to_list/1. -Expression has type: fun((atom()) -> string()) -Context expected type: fun((atom()) -> boolean() | [atom()]) - │ - -Because in the expression's type: - fun((atom()) -> - Here the type is: string() - Context expects type: boolean() | [atom()] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:919:9 - │ -919 │ fun atom_to_list/1, - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:atom_to_list/1. -Expression has type: fun((atom()) -> string()) -Context expected type: fun((dynamic(atom())) -> boolean() | [dynamic(atom())]) - │ - -Because in the expression's type: - fun((atom()) -> - Here the type is: string() - Context expects type: boolean() | [dynamic(atom())] - ) - -error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) - ┌─ check/src/custom.erl:925:1 - │ -925 │ ╭ queue_filter_13_neg(Q) -> -926 │ │ queue:filter( -927 │ │ fun atom_to_list/1, -928 │ │ Q -929 │ │ ), -930 │ │ ok. - │ ╰──────^ Clause is not covered by spec - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:927:9 - │ -927 │ fun atom_to_list/1, - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:atom_to_list/1. -Expression has type: fun((atom()) -> string()) -Context expected type: fun((atom()) -> boolean() | [atom()]) - │ - -Because in the expression's type: - fun((atom()) -> - Here the type is: string() - Context expects type: boolean() | [atom()] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:927:9 - │ -927 │ fun atom_to_list/1, - │ ^^^^^^^^^^^^^^^^^^ - │ │ - │ erlang:atom_to_list/1. -Expression has type: fun((atom()) -> string()) -Context expected type: fun((dynamic(atom())) -> boolean() | [dynamic(atom())]) - │ - -Because in the expression's type: - fun((atom()) -> - Here the type is: string() - Context expects type: boolean() | [dynamic(atom())] - ) - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:955:5 - │ -955 │ ╭ lists:keystore( -956 │ │ a, 1, -957 │ │ [{foo, b}, {c, d}], -958 │ │ non_tuple -959 │ │ ). - │ ╰─────^ lists:keystore('a', 1, [{'foo', 'b'}, {'c', 'd'}], 'non_tuple'). -Expression has type: [{'foo', 'b'} | {'c', 'd'} | dynamic()] -Context expected type: 'nok' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:958:9 - │ -958 │ non_tuple - │ ^^^^^^^^^ 'non_tuple'. -Expression has type: 'non_tuple' -Context expected type: tuple() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:966:5 - │ -966 │ ╭ lists:keystore( -967 │ │ a, 1, -968 │ │ [non_tuple], -969 │ │ {replacement} -970 │ │ ). - │ ╰─────^ lists:keystore('a', 1, ['non_tuple'], {'replacement'}). -Expression has type: [dynamic() | {'replacement'}] -Context expected type: 'nok' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:968:9 - │ -968 │ [non_tuple], - │ ^^^^^^^^^^^ - │ │ - │ ['non_tuple']. -Expression has type: ['non_tuple'] -Context expected type: [tuple()] - │ - -Because in the expression's type: - [ - Here the type is: 'non_tuple' - Context expects type: tuple() - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:975:5 - │ -975 │ ╭ lists:keystore( -976 │ │ a, 1, -977 │ │ non_list, -978 │ │ {replacement} -979 │ │ ). - │ ╰─────^ lists:keystore('a', 1, 'non_list', {'replacement'}). -Expression has type: [dynamic() | {'replacement'}] -Context expected type: 'nok' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:977:9 - │ -977 │ non_list, - │ ^^^^^^^^ 'non_list'. -Expression has type: 'non_list' -Context expected type: [tuple()] - -error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) - ┌─ check/src/custom.erl:983:1 - │ -983 │ ╭ keystore_7(None) -> -984 │ │ lists:keystore( -985 │ │ a, 1, -986 │ │ None, -987 │ │ {replacement} -988 │ │ ). - │ ╰─────^ Clause is not covered by spec - -error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) - ┌─ check/src/custom.erl:991:1 - │ -991 │ ╭ keystore_8(None) -> -992 │ │ lists:keystore( -993 │ │ a, 1, -994 │ │ None, -995 │ │ None -996 │ │ ). - │ ╰─────^ Clause is not covered by spec - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1011:25 - │ -1011 │ lists:keytake(a, 1, non_tup), - │ ^^^^^^^ 'non_tup'. -Expression has type: 'non_tup' -Context expected type: [Tuple] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1011:25 - │ -1011 │ lists:keytake(a, 1, non_tup), - │ ^^^^^^^ 'non_tup'. -Expression has type: 'non_tup' -Context expected type: [dynamic()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1016:25 - │ -1016 │ lists:keytake(a, 1, non_list), - │ ^^^^^^^^ 'non_list'. -Expression has type: 'non_list' -Context expected type: [Tuple] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1016:25 - │ -1016 │ lists:keytake(a, 1, non_list), - │ ^^^^^^^^ 'non_list'. -Expression has type: 'non_list' -Context expected type: [dynamic()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1021:22 - │ -1021 │ lists:keytake(a, non_num, []), - │ ^^^^^^^ 'non_num'. -Expression has type: 'non_num' -Context expected type: number() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1038:15 - │ -1038 │ lists:sum([a, 1]). - │ ^^^^^^ - │ │ - │ ['a', 1]. -Expression has type: ['a' | number()] -Context expected type: [number()] - │ - -Because in the expression's type: - [ - Here the type is a union type with some valid candidates: number() - However the following candidate: 'a' - Differs from the expected type: number() - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1042:15 - │ -1042 │ lists:sum(not_a_list). - │ ^^^^^^^^^^ 'not_a_list'. -Expression has type: 'not_a_list' -Context expected type: [number()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1062:15 - │ -1062 │ lists:max(not_a_list). - │ ^^^^^^^^^^ 'not_a_list'. -Expression has type: 'not_a_list' -Context expected type: [T] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1062:15 - │ -1062 │ lists:max(not_a_list). - │ ^^^^^^^^^^ 'not_a_list'. -Expression has type: 'not_a_list' -Context expected type: [dynamic()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1082:15 - │ -1082 │ lists:min(not_a_list). - │ ^^^^^^^^^^ 'not_a_list'. -Expression has type: 'not_a_list' -Context expected type: [T] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1082:15 - │ -1082 │ lists:min(not_a_list). - │ ^^^^^^^^^^ 'not_a_list'. -Expression has type: 'not_a_list' -Context expected type: [dynamic()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1100:5 - │ -1100 │ proplists:get_value(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L). -Expression has type: term() -Context expected type: pid() | 'undefined' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1106:5 - │ -1106 │ proplists:get_value(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L). -Expression has type: term() -Context expected type: pid() | 'undefined' | 'v' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1111:5 - │ -1111 │ proplists:get_value(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L). -Expression has type: term() -Context expected type: pid() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1117:5 - │ -1117 │ proplists:get_value(k, L, 3). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L, 3). -Expression has type: term() -Context expected type: pid() | number() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1123:5 - │ -1123 │ proplists:get_value(k, L, my_default). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L, 'my_default'). -Expression has type: term() -Context expected type: 'v1' | 'v2' | 'my_default' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1129:5 - │ -1129 │ proplists:get_value(k, L, my_default). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L, 'my_default'). -Expression has type: term() -Context expected type: 'v' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1134:28 - │ -1134 │ proplists:get_value(k, b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1139:5 - │ -1139 │ proplists:get_value(k, []). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', []). -Expression has type: term() -Context expected type: 'default' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1144:1 - │ -1144 │ proplists:get_value(k, [], my_default). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', [], 'my_default'). -Expression has type: term() -Context expected type: 'my_default' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1149:28 - │ -1149 │ proplists:get_value(k, b, my_default). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1154:5 - │ -1154 │ proplists:get_bool(b, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_bool('b', L). -Expression has type: boolean() -Context expected type: 'true' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1160:5 - │ -1160 │ proplists:get_all_values(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_all_values('k', L). -Expression has type: [term()] -Context expected type: [pid() | 'default'] - │ - -Because in the expression's type: - [ - Here the type is: term() - Context expects type: pid() | 'default' - No candidate matches in the expected union. - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1166:5 - │ -1166 │ proplists:get_all_values(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_all_values('k', L). -Expression has type: [term()] -Context expected type: [pid() | 'default' | 'v'] - │ - -Because in the expression's type: - [ - Here the type is: term() - Context expects type: pid() | 'default' | 'v' - No candidate matches in the expected union. - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1171:5 - │ -1171 │ proplists:get_all_values(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_all_values('k', L). -Expression has type: [term()] -Context expected type: [pid()] - │ - -Because in the expression's type: - [ - Here the type is: term() - Context expects type: pid() - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1176:5 - │ -1176 │ proplists:get_all_values(k, []). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_all_values('k', []). -Expression has type: [term()] -Context expected type: [] - │ - -Because in the expression's type: - [ - Here the type is: term() - Context expects type: none() - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1181:5 - │ -1181 │ proplists:get_all_values(k, b). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_all_values('k', 'b'). -Expression has type: [term()] -Context expected type: [] - │ - -Because in the expression's type: - [ - Here the type is: term() - Context expects type: none() - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1181:33 - │ -1181 │ proplists:get_all_values(k, b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1186:27 - │ -1186 │ proplists:get_bool(b, b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1191:5 - │ -1191 │ proplists:get_keys(L). - │ ^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_keys(L). -Expression has type: [term()] -Context expected type: ['c'] - │ - -Because in the expression's type: - [ - Here the type is: term() - Context expects type: 'c' - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1197:5 - │ -1197 │ proplists:get_keys(L). - │ ^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:get_keys(L). -Expression has type: [term()] -Context expected type: ['a' | 'b' | 'c'] - │ - -Because in the expression's type: - [ - Here the type is: term() - Context expects type: 'a' | 'b' | 'c' - No candidate matches in the expected union. - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1202:24 - │ -1202 │ proplists:get_keys(a). - │ ^ 'a'. -Expression has type: 'a' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1207:5 - │ -1207 │ ╭ ╭ proplists:get_keys( -1208 │ │ │ [{a, b, c}] -1209 │ │ │ ). - │ ╰─│─────^ proplists:get_keys([{'a', 'b', 'c'}]). -Expression has type: [term()] -Context expected type: ['a'] - │ ╰─────' - -Because in the expression's type: - [ - Here the type is: term() - Context expects type: 'a' - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1215:5 - │ -1215 │ proplists:get_value(k, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', L). -Expression has type: term() -Context expected type: 'a' | pid() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1220:5 - │ -1220 │ proplists:get_value(k, b). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('k', 'b'). -Expression has type: term() -Context expected type: 'a' | pid() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1220:28 - │ -1220 │ proplists:get_value(k, b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1225:1 - │ -1225 │ proplists:lookup(self(), [a, {b, true}]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:lookup(erlang:self(), ['a', {'b', 'true'}]). -Expression has type: 'none' | tuple() -Context expected type: {'b', 'true'} - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: tuple() - However the following candidate: 'none' - Differs from the expected type: {'b', 'true'} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1229:25 - │ -1229 │ proplists:lookup(a, b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1239:5 - │ -1239 │ proplists:lookup(k, []). - │ ^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:lookup('k', []). -Expression has type: 'none' | tuple() -Context expected type: 'none' - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: 'none' - However the following candidate: tuple() - Differs from the expected type: 'none' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1252:29 - │ -1252 │ proplists:lookup_all(a, b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1265:5 - │ -1265 │ ╭ ╭ proplists:lookup_all( -1266 │ │ │ self(), -1267 │ │ │ [] -1268 │ │ │ ). - │ ╰─│─────^ proplists:lookup_all(erlang:self(), []). -Expression has type: [tuple()] -Context expected type: [] - │ ╰─────' - -Because in the expression's type: - [ - Here the type is: tuple() - Context expects type: none() - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1291:34 - │ -1291 │ proplists:is_defined(self(), b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1295:25 - │ -1295 │ proplists:delete(k, b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [A] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1295:25 - │ -1295 │ proplists:delete(k, b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [dynamic()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1311:5 - │ -1311 │ proplists:split(L, Ks). - │ ^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ proplists:split(L, Ks). -Expression has type: {[[term()]], [term()]} -Context expected type: {[plist('a', 'b')], plist('a', 'b')} - │ - -Because in the expression's type: - { - [ - [ - Here the type is: term() - Context expects type: 'a' | {'a', 'b'} - No candidate matches in the expected union. - ] - ] - , [term()]} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1315:21 - │ -1315 │ proplists:split(b, []). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1319:25 - │ -1319 │ proplists:split([], b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1324:22 - │ -1324 │ proplists:to_map(b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: [atom() | {term(), term()} | term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1358:24 - │ -1358 │ proplists:from_map(b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: #{K => V} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1358:24 - │ -1358 │ proplists:from_map(b). - │ ^ 'b'. -Expression has type: 'b' -Context expected type: #{dynamic() => dynamic()} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1363:5 - │ -1363 │ proplists:get_value(a, [a]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('a', ['a']). -Expression has type: term() -Context expected type: 'true' | 'undefined' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1368:5 - │ -1368 │ proplists:get_value(X, [a]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value(X, ['a']). -Expression has type: term() -Context expected type: 'true' | 'undefined' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1373:5 - │ -1373 │ proplists:get_value(a, [a], b). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ proplists:get_value('a', ['a'], 'b'). -Expression has type: term() -Context expected type: 'true' | 'b' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1416:5 - │ -1416 │ file:consult(some_file). - │ ^^^^^^^^^^^^^^^^^^^^^^^ file:consult('some_file'). -Expression has type: {'ok', [dynamic()]} | {'error', {number(), atom(), term()} | 'terminated' | 'badarg' | file:posix() | 'system_limit'} -Context expected type: 'nok' - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1428:5 - │ -1428 │ lists:keysort(2, [{a, c}, {b, d}]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lists:keysort(2, [{'a', 'c'}, {'b', 'd'}]). -Expression has type: [{'a', 'c'} | {'b', 'd'}] -Context expected type: none() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1432:22 - │ -1432 │ lists:keysort(1, [3]). - │ ^^^ - │ │ - │ [3]. -Expression has type: [number()] -Context expected type: [tuple()] - │ - -Because in the expression's type: - [ - Here the type is: number() - Context expects type: tuple() - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1448:5 - │ -1448 │ ╭ ╭ lists:filtermap( -1449 │ │ │ fun(X) when X div 2 =:= 0 -> -1450 │ │ │ {true, integer_to_list(X)}; -1451 │ │ │ (X) -> - · │ │ -1454 │ │ │ [1, 2, 3, 4] -1455 │ │ │ ). - │ ╰─│─────^ lists:filtermap(fun, [1, 2, 3, 4]). -Expression has type: [string() | number()] -Context expected type: [number()] - │ ╰─────' - -Because in the expression's type: - [ - Here the type is a union type with some valid candidates: number() - However the following candidate: string() - Differs from the expected type: number() - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1482:5 - │ -1482 │ erlang:min(X, Y). - │ ^^^^^^^^^^^^^^^^ erlang:min(X, Y). -Expression has type: number() -Context expected type: none() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1497:5 - │ -1497 │ erlang:max(X, Y). - │ ^^^^^^^^^^^^^^^^ erlang:max(X, Y). -Expression has type: atom() | number() -Context expected type: none() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1523:9 - │ -1523 │ abs(Atom). - │ ^^^^ Atom. -Expression has type: 'a' -Context expected type: number() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1536:31 - │ -1536 │ seq3_4_wip_neg() -> lists:seq(a, 2, 1). - │ ^ 'a'. -Expression has type: 'a' -Context expected type: number() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1539:30 - │ -1539 │ seq3_5_neg() -> lists:seq(1, a, 1). - │ ^ 'a'. -Expression has type: 'a' -Context expected type: number() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1542:33 - │ -1542 │ seq3_6_neg() -> lists:seq(1, 2, a). - │ ^ 'a'. -Expression has type: 'a' -Context expected type: number() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1570:31 - │ -1570 │ seq2_4_wip_neg() -> lists:seq(a, 2). - │ ^ 'a'. -Expression has type: 'a' -Context expected type: number() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1573:30 - │ -1573 │ seq2_5_neg() -> lists:seq(1, a). - │ ^ 'a'. -Expression has type: 'a' -Context expected type: number() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1622:5 - │ -1622 │ ╭ timer:tc( -1623 │ │ fun() -> -1624 │ │ err -1625 │ │ end -1626 │ │ ). - │ ╰─────^ timer:tc(fun). -Expression has type: {number(), 'err'} -Context expected type: pid() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1635:5 - │ -1635 │ ets:lookup(tab, Any). - │ ^^^^^^^^^^^^^^^^^^^^ ets:lookup('tab', Any). -Expression has type: [dynamic()] -Context expected type: pid() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1639:5 - │ -1639 │ ets:lookup("not atom", Any). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ets:lookup(string_lit, Any). -Expression has type: [dynamic()] -Context expected type: pid() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1639:16 - │ -1639 │ ets:lookup("not atom", Any). - │ ^^^^^^^^^^ string_lit. -Expression has type: string() -Context expected type: ets:tab() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1663:18 - │ -1663 │ ets:tab2list("not atom"). - │ ^^^^^^^^^^ string_lit. -Expression has type: string() -Context expected type: ets:tab() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1702:23 - │ -1702 │ lists:flatten([], 1). - │ ^ 1. -Expression has type: number() -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1711:19 - │ -1711 │ lists:flatten(3). - │ ^ 3. -Expression has type: number() -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1724:8 - │ -1724 │ -> lists:flatten(X). - │ ^^^^^^^^^^^^^^^^ - │ │ - │ lists:flatten(X). -Expression has type: [{A, B} | {B, A}] -Context expected type: [{A, B}] - │ - -Because in the expression's type: - [ - { - Here the type is: B - Context expects type: A - , A} - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1757:5 - │ -1757 │ ╭ ╭ maps:without( -1758 │ │ │ [a, c, DOrE], -1759 │ │ │ #{ -1760 │ │ │ a => ka, - · │ │ -1764 │ │ │ } -1765 │ │ │ ). - │ ╰─│─────^ maps:without(['a', 'c', DOrE], #{..}). -Expression has type: #{a => 'ka', b => atom(), c => pid(), d => 'kd'} -Context expected type: #{b => atom()} - │ ╰─────' - -Because in the expression's type: - Here the type is: #{a => ...} - Context expects type: #{...} - The expected map has no corresponding key for: a. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1769:18 - │ -1769 │ maps:without(non_list, #{}). - │ ^^^^^^^^ 'non_list'. -Expression has type: 'non_list' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1773:22 - │ -1773 │ maps:without([], non_map). - │ ^^^^^^^ 'non_map'. -Expression has type: 'non_map' -Context expected type: #{term() => term()} - -error: clause_not_covered (See https://fb.me/eqwalizer_errors#clause_not_covered) - ┌─ check/src/custom.erl:1808:1 - │ -1808 │ ╭ maps_without_12_neg(None) -> -1809 │ │ maps:without( -1810 │ │ [a, b], -1811 │ │ None -1812 │ │ ). - │ ╰─────^ Clause is not covered by spec - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1818:5 - │ -1818 │ ╭ ╭ maps:without( -1819 │ │ │ Keys, -1820 │ │ │ #{a => self(), b => self()} -1821 │ │ │ ). - │ ╰─│─────^ maps:without(Keys, #{..}). -Expression has type: #{a => pid(), b => pid()} -Context expected type: #{b := pid()} - │ ╰─────' - -Because in the expression's type: - Here the type is: #{b => ..., ...} - Context expects type: #{b := ..., ...} - The type of the expression is missing the following required keys: b. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1826:5 - │ -1826 │ ╭ ╭ maps:without( -1827 │ │ │ [a | improper_tail], -1828 │ │ │ #{a => self(), b => self()} -1829 │ │ │ ). - │ ╰─│─────^ maps:without(['a' | 'improper_tail'], #{..}). -Expression has type: #{a => pid(), b => pid()} -Context expected type: #{b := pid()} - │ ╰─────' - -Because in the expression's type: - Here the type is: #{b => ..., ...} - Context expects type: #{b := ..., ...} - The type of the expression is missing the following required keys: b. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1827:12 - │ -1827 │ [a | improper_tail], - │ ^^^^^^^^^^^^^^^^ 'improper_tail'. -Expression has type: 'improper_tail' -Context expected type: [term()] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1837:5 - │ -1837 │ ╭ ╭ maps:without( -1838 │ │ │ Keys, -1839 │ │ │ #{a => ka, b => self()} -1840 │ │ │ ). - │ ╰─│─────^ maps:without(Keys, #{..}). -Expression has type: #{a => 'ka', b => pid()} -Context expected type: #{b := pid()} - │ ╰─────' - -Because in the expression's type: - Here the type is: #{b => ..., ...} - Context expects type: #{b := ..., ...} - The type of the expression is missing the following required keys: b. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1854:5 - │ -1854 │ maps:without([a, b], M). - │ ^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ maps:without(['a', 'b'], M). -Expression has type: #{c := 'cv', d := 'dv'} | #{c := 'cv', e => 'ev'} -Context expected type: #{c := atom()} - │ - -Because in the expression's type: - Here the type is: #{d := ...} - Context expects type: #{...} - The expected map has no corresponding key for: d. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1916:23 - │ -1916 │ custom_overloaded(X). - │ ^ X. -Expression has type: term() -Context expected type: atom() | binary() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:1939:5 - │ -1939 │ {A, N}. - │ ^^^^^^ - │ │ - │ {A, N}. -Expression has type: {atom(), number() | pid()} -Context expected type: {atom(), number()} - │ - -Because in the expression's type: - { atom(), - Here the type is a union type with some valid candidates: number() - However the following candidate: pid() - Differs from the expected type: number() - } - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2041:5 - │ -2041 │ filename:join(["server", "erl"]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ filename:join([string_lit, string_lit]). -Expression has type: file:filename_all() -Context expected type: file:filename() - │ - -Because in the expression's type: - Here the type is: string() | binary() - Context expects type: string() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2046:5 - │ -2046 │ filename:join(["server", <<>>]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ filename:join([string_lit, <<..>>]). -Expression has type: file:filename_all() -Context expected type: file:filename() - │ - -Because in the expression's type: - Here the type is: string() | binary() - Context expects type: string() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2051:5 - │ -2051 │ filename:join([<<>>, ""]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ filename:join([<<..>>, string_lit]). -Expression has type: file:filename_all() -Context expected type: file:filename() - │ - -Because in the expression's type: - Here the type is: string() | binary() - Context expects type: string() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2066:5 - │ -2066 │ filename:join([<<>>, <<>>]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ filename:join([<<..>>, <<..>>]). -Expression has type: file:filename_all() -Context expected type: binary() - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: binary() - However the following candidate: string() - Differs from the expected type: binary() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2081:19 - │ -2081 │ filename:join([<<>>, self()]). - │ ^^^^^^^^^^^^^^ - │ │ - │ [<<..>>, erlang:self()]. -Expression has type: [binary() | pid()] -Context expected type: [file:name_all()] - │ - -Because in the expression's type: - [ - Here the type is a union type with some valid candidates: binary() - However the following candidate: pid() - Differs from the expected type: string() | atom() | file:deep_list() | binary() - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2086:5 - │ -2086 │ filename:join("server", "erl"). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ filename:join(string_lit, string_lit). -Expression has type: file:filename_all() -Context expected type: file:filename() - │ - -Because in the expression's type: - Here the type is: string() | binary() - Context expects type: string() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2091:5 - │ -2091 │ filename:join("server", <<>>). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ filename:join(string_lit, <<..>>). -Expression has type: file:filename_all() -Context expected type: file:filename() - │ - -Because in the expression's type: - Here the type is: string() | binary() - Context expects type: string() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2096:5 - │ -2096 │ filename:join(<<>>, ""). - │ ^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ filename:join(<<..>>, string_lit). -Expression has type: file:filename_all() -Context expected type: file:filename() - │ - -Because in the expression's type: - Here the type is: string() | binary() - Context expects type: string() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2111:5 - │ -2111 │ filename:join(atom, <<>>). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ filename:join('atom', <<..>>). -Expression has type: file:filename_all() -Context expected type: binary() - │ - -Because in the expression's type: - Here the type is a union type with some valid candidates: binary() - However the following candidate: string() - Differs from the expected type: binary() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2121:25 - │ -2121 │ filename:join(<<>>, self()). - │ ^^^^^^ - │ │ - │ erlang:self(). -Expression has type: pid() -Context expected type: file:name_all() - │ - -Because in the expression's type: - Here the type is: pid() - Context expects type: string() | atom() | file:deep_list() | binary() - No candidate matches in the expected union. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2150:5 - │ -2150 │ ╭ ╭ queue:filter( -2151 │ │ │ fun my_filter1/1, -2152 │ │ │ Q -2153 │ │ │ ). - │ ╰─│─────^ queue:filter(my_filter1/1, Q). -Expression has type: queue:queue(atom() | number()) -Context expected type: queue:queue(number()) - │ ╰─────' - -Because in the expression's type: - { - [ - Here the type is a union type with some valid candidates: number() - However the following candidate: atom() - Differs from the expected type: number() - ] - , [atom() | number()]} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2191:5 - │ -2191 │ M3. - │ ^^ - │ │ - │ M3. -Expression has type: #{count := number(), module := 'foo'} | #{module := 'foo'} -Context expected type: state1() - │ - -Because in the expression's type: - The type is a union type with some valid candidates: #{count := number(), module := 'foo'} - However, the following candidate doesn't match: - Here the type is: #{...} - Context expects type: #{count := ..., ...} - The type of the expression is missing the following required keys: count. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2223:13 - │ -2223 │ Atom + Sum - │ ^^^^ Atom. -Expression has type: atom() -Context expected type: number() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2279:5 - │ -2279 │ maps:remove(A, M). - │ ^^^^^^^^^^^^^^^^^ - │ │ - │ maps:remove(A, M). -Expression has type: #{a => number()} -Context expected type: #{a := number()} - │ - -Because in the expression's type: - Here the type is: #{a => ..., ...} - Context expects type: #{a := ..., ...} - The type of the expression is missing the following required keys: a. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2320:5 - │ -2320 │ ╭ ╭ maps:filtermap( -2321 │ │ │ fun -2322 │ │ │ (a, V) -> true; -2323 │ │ │ (b, V) -> {true, atom_to_binary(V)}; - · │ │ -2326 │ │ │ M -2327 │ │ │ ). - │ ╰─│─────^ maps:filtermap(fun, M). -Expression has type: #{a => atom() | binary(), b => atom() | binary(), c => atom() | binary()} -Context expected type: #{a := atom(), b := binary()} - │ ╰─────' - -Because in the expression's type: - Here the type is: #{a => ..., b => ..., ...} - Context expects type: #{a := ..., b := ..., ...} - The type of the expression is missing the following required keys: a, b. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2342:5 - │ -2342 │ ╭ ╭ maps:filtermap( -2343 │ │ │ fun (_, V) -> -2344 │ │ │ {true, atom_to_binary(V)} -2345 │ │ │ end, -2346 │ │ │ M -2347 │ │ │ ). - │ ╰─│─────^ maps:filtermap(fun, M). -Expression has type: #{atom() => binary()} -Context expected type: #{atom() => atom()} - │ ╰─────' - -Because in the expression's type: - #{ atom() => - Here the type is: binary() - Context expects type: atom() - , ... } - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2354:23 - │ -2354 │ fun (_, _) -> err end, - │ ^^^ - │ │ - │ 'err'. -Expression has type: 'err' -Context expected type: boolean() | {'true', term()} - │ - -Because in the expression's type: - Here the type is: 'err' - Context expects type: 'false' | 'true' | {'true', term()} - No candidate matches in the expected union. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2362:5 - │ -2362 │ ╭ ╭ maps:filtermap( -2363 │ │ │ fun (_, V) -> {true, atom_to_binary(V)} end, -2364 │ │ │ M -2365 │ │ │ ). - │ ╰─│─────^ maps:filtermap(fun, M). -Expression has type: #{atom() => binary()} -Context expected type: #{atom() => atom()} - │ ╰─────' - -Because in the expression's type: - #{ atom() => - Here the type is: binary() - Context expects type: atom() - , ... } - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2363:45 - │ -2363 │ fun (_, V) -> {true, atom_to_binary(V)} end, - │ ^ V. -Expression has type: binary() -Context expected type: atom() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2377:5 - │ -2377 │ re:replace(Subj, "+", "-", [{return, binary}]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ re:replace(Subj, string_lit, string_lit, [{'return', 'binary'}]). -Expression has type: binary() -Context expected type: string() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2381:5 - │ -2381 │ re:replace(Subj, "+", "-", [{return, list}]). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ re:replace(Subj, string_lit, string_lit, [{'return', 'list'}]). -Expression has type: string() -Context expected type: binary() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2385:22 - │ -2385 │ Res = re:replace(Subj, "+", "-", [{return, list}]), - │ ^^^^ - │ │ - │ Subj. -Expression has type: atom() -Context expected type: iodata() | unicode:charlist() - │ - -Because in the expression's type: - Here the type is: atom() - Context expects type: iolist() | binary() - No candidate matches in the expected union. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2386:5 - │ -2386 │ Res. - │ ^^^ Res. -Expression has type: string() -Context expected type: binary() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2390:38 - │ -2390 │ Res = re:replace(Subj, "+", "-", [{return, something}]), - │ ^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ [{'return', 'something'}]. -Expression has type: [{'return', 'something'}] -Context expected type: ['notempty_atstart' | 'noteol' | 'notbol' | 'global' | {'match_limit_recursion', number()} | re:compile_option() | {'match_limit', number()} | {'offset', number()} | 'notempty' | {'return', 'iodata' | 'list' | 'binary'} | 'anchored'] - │ - -Because in the expression's type: - [ - { 'return', - Here the type is: 'something' - Context expects type: 'iodata' | 'list' | 'binary' - No candidate matches in the expected union. - } - ] - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2506:5 - │ -2506 │ lists:partition(fun is_number/1, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ lists:partition(fun, L). -Expression has type: {[number()], [atom()]} -Context expected type: {[atom()], [number()]} - │ - -Because in the expression's type: - { - [ - Here the type is: number() - Context expects type: atom() - ] - , [atom()]} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2518:5 - │ -2518 │ lists:partition(fun({_Term, V}) -> is_number(V) end, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ lists:partition(fun, L). -Expression has type: {[{term(), number()}], [{term(), atom()}]} -Context expected type: {[{term(), atom()}], [{term(), number()}]} - │ - -Because in the expression's type: - { - [ - { term(), - Here the type is: number() - Context expects type: atom() - } - ] - , [{term(), atom()}]} - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2536:5 - │ -2536 │ lists:partition(fun({ok, _}) -> true; (_) -> false end, L). - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ lists:partition(fun, L). -Expression has type: {[{'ok', atom()}], [{'ok', atom()} | {'error', term()}]} -Context expected type: {[{'ok', atom()}], [{'error', term()}]} - │ - -Because in the expression's type: - { [{'ok', atom()}], - [ - { - Here the type is: 'ok' - Context expects type: 'error' - , atom()} - ] - } - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2576:33 - │ -2576 │ maps_intersect_2_neg(M1, M2) -> maps:intersect(M1, M2). - │ ^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ maps:intersect(M1, M2). -Expression has type: #{a => number()} -Context expected type: #{a := number()} - │ - -Because in the expression's type: - Here the type is: #{a => ..., ...} - Context expects type: #{a := ..., ...} - The type of the expression is missing the following required keys: a. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2586:33 - │ -2586 │ maps_intersect_4_neg(M1, M2) -> maps:intersect(M1, M2). - │ ^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ maps:intersect(M1, M2). -Expression has type: #{a => number()} -Context expected type: #{a => 'true'} - │ - -Because in the expression's type: - #{ a => - Here the type is: number() - Context expects type: 'true' - , ... } - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2596:33 - │ -2596 │ maps_intersect_6_neg(M1, M2) -> maps:intersect(M1, M2). - │ ^^^^^^^^^^^^^^^^^^^^^^ - │ │ - │ maps:intersect(M1, M2). -Expression has type: #{a => number()} -Context expected type: #{a := number()} - │ - -Because in the expression's type: - Here the type is: #{a => ..., ...} - Context expects type: #{a := ..., ...} - The type of the expression is missing the following required keys: a. - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2709:40 - │ -2709 │ (foo, A) -> binary_to_atom(A); - │ ^ A. -Expression has type: atom() -Context expected type: binary() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2710:40 - │ -2710 │ (bar, B) -> atom_to_binary(B); - │ ^ B. -Expression has type: binary() -Context expected type: atom() - -error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types) - ┌─ check/src/custom.erl:2711:50 - │ -2711 │ ({foo, bar}, I) -> binary_to_integer(I); - │ ^ I. -Expression has type: number() -Context expected type: binary() - -202 ERRORS diff --git a/crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-26.pretty b/crates/elp/src/resources/test/eqwalizer_tests/check/custom.pretty similarity index 100% rename from crates/elp/src/resources/test/eqwalizer_tests/check/custom-OTP-26.pretty rename to crates/elp/src/resources/test/eqwalizer_tests/check/custom.pretty diff --git a/test/test_projects/eqwalizer_tests/check/src/callbacks3_neg.erl b/test/test_projects/eqwalizer_tests/check/src/callbacks3_neg.erl index f904f0e718..854feec923 100644 --- a/test/test_projects/eqwalizer_tests/check/src/callbacks3_neg.erl +++ b/test/test_projects/eqwalizer_tests/check/src/callbacks3_neg.erl @@ -3,7 +3,7 @@ %%% This source code is licensed under the Apache 2.0 license found in %%% the LICENSE file in the root directory of this source tree. --module(callbacks3_neg). % @OTPVersionDependent +-module(callbacks3_neg). % @OTP27Only -export([ init/1, handle_call/3, diff --git a/test/test_projects/eqwalizer_tests/check/src/custom.erl b/test/test_projects/eqwalizer_tests/check/src/custom.erl index 911bfa3a0a..b6910b0fc7 100644 --- a/test/test_projects/eqwalizer_tests/check/src/custom.erl +++ b/test/test_projects/eqwalizer_tests/check/src/custom.erl @@ -3,7 +3,7 @@ %%% This source code is licensed under the Apache 2.0 license found in %%% the LICENSE file in the root directory of this source tree. --module(custom). % @OTPVersionDependent +-module(custom). % @OTP27Only -import(maps, [get/2, get/3]). -compile([export_all, nowarn_export_all]). From fa0504304371ad27f99045b6d7e022a5dccc3511 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Wed, 17 Dec 2025 09:53:03 -0800 Subject: [PATCH 127/142] Allow project discovery for OTP (#141) 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 --- crates/elp/src/project_loader.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/elp/src/project_loader.rs b/crates/elp/src/project_loader.rs index 103353e5db..0f13139201 100644 --- a/crates/elp/src/project_loader.rs +++ b/crates/elp/src/project_loader.rs @@ -100,8 +100,11 @@ impl ProjectLoader { ) -> Option<(ElpConfig, Result, ProjectManifest)> { let mut path_it = path; loop { - if self.project_roots.contains_key(path_it) { - return None; + if let Some(value) = self.project_roots.get(path_it) { + return match value { + None => Some(self.load_manifest(path_it)), + Some(_) => None, + }; } match path_it.parent() { Some(parent) => path_it = parent, From 54dd1089ed841440dd9709c2fc76b971e85719f4 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Thu, 18 Dec 2025 03:37:42 -0800 Subject: [PATCH 128/142] 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 --- crates/ide/src/diagnostics/edoc.rs | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/crates/ide/src/diagnostics/edoc.rs b/crates/ide/src/diagnostics/edoc.rs index 052c85d57f..622a20fe13 100644 --- a/crates/ide/src/diagnostics/edoc.rs +++ b/crates/ide/src/diagnostics/edoc.rs @@ -31,7 +31,6 @@ use super::DiagnosticCode; use super::GenericLinter; use super::GenericLinterMatchContext; use super::Linter; -use super::Severity; pub(crate) struct EdocLinter; @@ -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 } } @@ -298,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( From 439ebade1bd580b67402572c20a81524746e3bd0 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Thu, 18 Dec 2025 03:37:42 -0800 Subject: [PATCH 129/142] 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 --- crates/ide/src/diagnostics.rs | 4 ++-- .../ide/src/diagnostics/{edoc.rs => old_edoc_syntax.rs} | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) rename crates/ide/src/diagnostics/{edoc.rs => old_edoc_syntax.rs} (99%) diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 733347bfcb..5341d77da7 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -102,7 +102,6 @@ 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; @@ -131,6 +130,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; @@ -1682,7 +1682,7 @@ const GENERIC_LINTERS: &[&dyn GenericDiagnostics] = &[ &duplicate_module::LINTER, &no_nowarn_suppressions::LINTER, ¯o_precedence_suprise::LINTER, - &edoc::LINTER, + &old_edoc_syntax::LINTER, &missing_module::LINTER, &unused_include::LINTER, &misspelled_attribute::LINTER, diff --git a/crates/ide/src/diagnostics/edoc.rs b/crates/ide/src/diagnostics/old_edoc_syntax.rs similarity index 99% rename from crates/ide/src/diagnostics/edoc.rs rename to crates/ide/src/diagnostics/old_edoc_syntax.rs index 622a20fe13..e8524b6e6e 100644 --- a/crates/ide/src/diagnostics/edoc.rs +++ b/crates/ide/src/diagnostics/old_edoc_syntax.rs @@ -32,9 +32,9 @@ use super::GenericLinter; use super::GenericLinterMatchContext; use super::Linter; -pub(crate) struct EdocLinter; +pub(crate) struct OldEdocSyntaxLinter; -impl Linter for EdocLinter { +impl Linter for OldEdocSyntaxLinter { fn id(&self) -> DiagnosticCode { DiagnosticCode::OldEdocSyntax } @@ -54,7 +54,7 @@ pub struct Context { doc_start: TextSize, } -impl GenericLinter for EdocLinter { +impl GenericLinter for OldEdocSyntaxLinter { type Context = Context; fn matches( @@ -131,7 +131,7 @@ impl GenericLinter for EdocLinter { } } -pub static LINTER: EdocLinter = EdocLinter; +pub static LINTER: OldEdocSyntaxLinter = OldEdocSyntaxLinter; fn old_edoc_syntax_fix( sema: &Semantic, From 3804bd17062700a0fb575796083fbc0d2a140fc5 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Thu, 18 Dec 2025 03:37:42 -0800 Subject: [PATCH 130/142] 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 --- crates/ide/src/diagnostics/undefined_function.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/ide/src/diagnostics/undefined_function.rs b/crates/ide/src/diagnostics/undefined_function.rs index 189edf06f5..52fa2cfa3c 100644 --- a/crates/ide/src/diagnostics/undefined_function.rs +++ b/crates/ide/src/diagnostics/undefined_function.rs @@ -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 { From d7b1c561ad05e3ddc8b00b2b9c6969ee7e6d2b69 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Thu, 18 Dec 2025 05:36:31 -0800 Subject: [PATCH 131/142] Fix GH CI for OTP 26 Summary: As title. Reviewed By: robertoaloi Differential Revision: D89461312 fbshipit-source-id: 3c0c75da6ccd00735d3f444b123b0bab222d0980 --- crates/elp/src/bin/main.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index e7ed9051a9..3391fe2c63 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -1595,13 +1595,15 @@ mod tests { #[test_case(false ; "rebar")] #[test_case(true ; "buck")] fn lint_no_stream_produces_output(buck: bool) { - simple_snapshot_expect_error( - args_vec!["lint", "--no-stream"], - "diagnostics", - expect_file!("../resources/test/diagnostics/lint_no_stream.stdout"), - buck, - None, - ); + 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")] From ce0125dec900b669378cda2670af5c3b7d7761e8 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Thu, 18 Dec 2025 07:05:33 -0800 Subject: [PATCH 132/142] 1/n: bound var in match: detect problem 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 --- .../test/diagnostics/lint_no_stream.stdout | 3 + .../diagnostics/parse_all_diagnostics1.stdout | 3 +- .../parse_all_diagnostics_json.stdout | 1 + .../diagnostics/parse_otp27_docstrings.jsonl | 4 +- ...e_elp_no_lint_specified_json_output.stdout | 2 + .../parse_elp_no_lint_specified_output.stdout | 2 + .../test/linter/warnings_as_errors.stdout | 2 + crates/hir/src/lib.rs | 4 + crates/ide/src/diagnostics.rs | 2 + crates/ide/src/diagnostics/bound_variable.rs | 178 ++++++++++++++++++ crates/ide/src/tests.rs | 1 + crates/ide_db/src/diagnostic_code.rs | 4 + website/docs/erlang-error-index/w/W0060.md | 48 +++++ 13 files changed, 252 insertions(+), 2 deletions(-) create mode 100644 crates/ide/src/diagnostics/bound_variable.rs create mode 100644 website/docs/erlang-error-index/w/W0060.md diff --git a/crates/elp/src/resources/test/diagnostics/lint_no_stream.stdout b/crates/elp/src/resources/test/diagnostics/lint_no_stream.stdout index b1c2f07150..eef1c92c6d 100644 --- a/crates/elp/src/resources/test/diagnostics/lint_no_stream.stdout +++ b/crates/elp/src/resources/test/diagnostics/lint_no_stream.stdout @@ -45,6 +45,7 @@ app_a/src/cascading.erl:9:5-9:6::[Error] [W0004] Missing ')' 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 @@ -60,6 +61,8 @@ app_a/src/lints.erl:5:1-5:14::[Error] [P1700] head mismatch 'head_mismatcX' vs ' 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 diff --git a/crates/elp/src/resources/test/diagnostics/parse_all_diagnostics1.stdout b/crates/elp/src/resources/test/diagnostics/parse_all_diagnostics1.stdout index 7fe610f781..4b0a155055 100644 --- a/crates/elp/src/resources/test/diagnostics/parse_all_diagnostics1.stdout +++ b/crates/elp/src/resources/test/diagnostics/parse_all_diagnostics1.stdout @@ -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 diff --git a/crates/elp/src/resources/test/diagnostics/parse_all_diagnostics_json.stdout b/crates/elp/src/resources/test/diagnostics/parse_all_diagnostics_json.stdout index 0c3e5ae9a3..ede82c7c5f 100644 --- a/crates/elp/src/resources/test/diagnostics/parse_all_diagnostics_json.stdout +++ b/crates/elp/src/resources/test/diagnostics/parse_all_diagnostics_json.stdout @@ -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"} diff --git a/crates/elp/src/resources/test/diagnostics/parse_otp27_docstrings.jsonl b/crates/elp/src/resources/test/diagnostics/parse_otp27_docstrings.jsonl index 5e286c673f..c34c9037db 100644 --- a/crates/elp/src/resources/test/diagnostics/parse_otp27_docstrings.jsonl +++ b/crates/elp/src/resources/test/diagnostics/parse_otp27_docstrings.jsonl @@ -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) diff --git a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_json_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_json_output.stdout index 6e07ea2bba..7b6a1e705c 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_json_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_json_output.stdout @@ -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"} diff --git a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout index 9f7a5405b4..5bcd503e10 100644 --- a/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout +++ b/crates/elp/src/resources/test/linter/parse_elp_no_lint_specified_output.stdout @@ -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. diff --git a/crates/elp/src/resources/test/linter/warnings_as_errors.stdout b/crates/elp/src/resources/test/linter/warnings_as_errors.stdout index 00d8506230..01007e94c4 100644 --- a/crates/elp/src/resources/test/linter/warnings_as_errors.stdout +++ b/crates/elp/src/resources/test/linter/warnings_as_errors.stdout @@ -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. diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 35585ef702..751a2f4907 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -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. diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 5341d77da7..7b23da4461 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -96,6 +96,7 @@ 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; @@ -1687,6 +1688,7 @@ const GENERIC_LINTERS: &[&dyn GenericDiagnostics] = &[ &unused_include::LINTER, &misspelled_attribute::LINTER, &boolean_precedence::LINTER, + &bound_variable::LINTER, ]; /// Unified registry for all types of linters diff --git a/crates/ide/src/diagnostics/bound_variable.rs b/crates/ide/src/diagnostics/bound_variable.rs new file mode 100644 index 0000000000..92f0edadfa --- /dev/null +++ b/crates/ide/src/diagnostics/bound_variable.rs @@ -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>> { + 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(). + "#]], + ) + } +} diff --git a/crates/ide/src/tests.rs b/crates/ide/src/tests.rs index f1a9065e01..55e9494e48 100644 --- a/crates/ide/src/tests.rs +++ b/crates/ide/src/tests.rs @@ -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) } diff --git a/crates/ide_db/src/diagnostic_code.rs b/crates/ide_db/src/diagnostic_code.rs index e9d21cadca..ec1e1d76cf 100644 --- a/crates/ide_db/src/diagnostic_code.rs +++ b/crates/ide_db/src/diagnostic_code.rs @@ -93,6 +93,7 @@ pub enum DiagnosticCode { ListsReverseAppend, HirUnresolvedMacro, HirUnresolvedInclude, + BoundVarInLhs, // Wrapper for erlang service diagnostic codes ErlangService(String), @@ -256,6 +257,7 @@ impl DiagnosticCode { DiagnosticCode::HirUnresolvedMacro => "W0057".to_string(), DiagnosticCode::HirUnresolvedInclude => "W0058".to_string(), DiagnosticCode::UnavailableType => "W0059".to_string(), + DiagnosticCode::BoundVarInLhs => "W0060".to_string(), DiagnosticCode::ErlangService(c) => c.to_string(), DiagnosticCode::Eqwalizer(c) => format!("eqwalizer: {c}"), DiagnosticCode::AdHoc(c) => format!("ad-hoc: {c}"), @@ -271,6 +273,7 @@ impl DiagnosticCode { DiagnosticCode::HeadMismatch => "head_mismatch".to_string(), DiagnosticCode::SyntaxError => "syntax_error".to_string(), DiagnosticCode::BoundVarInPattern => "bound_var_in_pattern".to_string(), + DiagnosticCode::BoundVarInLhs => "bound_var_in_lhs".to_string(), DiagnosticCode::ModuleMismatch => "module_mismatch".to_string(), DiagnosticCode::UnusedMacro => "unused_macro".to_string(), DiagnosticCode::UnusedRecordField => "unused_record_field".to_string(), @@ -486,6 +489,7 @@ impl DiagnosticCode { DiagnosticCode::ModuleMismatch => false, DiagnosticCode::UnusedInclude => false, DiagnosticCode::BoundVarInPattern => false, + DiagnosticCode::BoundVarInLhs => false, DiagnosticCode::UnusedMacro => false, DiagnosticCode::UnusedRecordField => false, DiagnosticCode::MutableVarBug => false, diff --git a/website/docs/erlang-error-index/w/W0060.md b/website/docs/erlang-error-index/w/W0060.md new file mode 100644 index 0000000000..91b972dacf --- /dev/null +++ b/website/docs/erlang-error-index/w/W0060.md @@ -0,0 +1,48 @@ +--- +sidebar_position: 60 +--- + +# W0060 - Bound Variable in LHS + +## Error + +```erlang +handle_request(Message) -> + Message = next_action(). +%% ^^^^^^^ 💡 warning: W0060: Match on a bound variable +``` + +## Explanation + +This diagnostic flags cases where a variable that is already bound appears on the left-hand side (LHS) of a match expression. This can be problematic if the binding is not intentional and can lead to subtle bugs. + +Consider the following code snippet: + +```erlang showLineNumbers +foo() -> + AA = foo(), + AA = bar(). +``` + +The pattern on line `3` will only match if and only if the result of the call to `bar/0` is the same as the call to `foo/0`. This behaviour could be intentional or not. If not, it can easily lead to bugs. + +An alternative, more explicit, way to express that behaviour - when intentional - could be: + +```erlang showLineNumbers +foo() -> + AA = foo(), + BB = bar(), + AA = BB. +``` + +Or using an assertion: + +```erlang showLineNumbers +foo() -> + AA = foo(), + ?assertEqual(AA, bar()). +``` + +## Semantic highlighting + +Note that we also have semantic highlighting of the more general case, where a bound variable appears in any pattern. From b12cf72c7e2e4e04e92ad8aba957ee5ca86fe10d Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Thu, 18 Dec 2025 14:47:07 -0800 Subject: [PATCH 133/142] Standalone Buck2 setup for test_projects 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 --- crates/elp/src/bin/main.rs | 119 +++++++++--------- .../test/xref/unavailable_type.stdout | 4 +- crates/project_model/src/buck.rs | 81 +++++++----- test/test_projects/.buckconfig | 24 ++++ test/test_projects/.buckroot | 0 test/test_projects/buck_bad_config/.elp.toml | 4 +- test/test_projects/buck_tests/.elp.toml | 6 +- .../buck_tests/test_elp/TARGETS.v2_ | 10 +- .../test_elp_direct_dep/TARGETS.v2_ | 4 +- test/test_projects/buck_tests_2/.elp.toml | 8 +- test/test_projects/diagnostics/.elp.toml | 4 +- test/test_projects/end_to_end/.elp.toml | 2 +- .../test_projects/eqwalizer_callers/.elp.toml | 4 +- test/test_projects/eqwalizer_tests/.elp.toml | 4 +- .../hierarchical_config/.elp.toml | 4 +- test/test_projects/in_place_tests/.elp.toml | 4 +- .../include_lib_dependency_test/.elp.toml | 4 +- test/test_projects/linter/.elp.toml | 4 +- .../test_projects/linter_bad_config/.elp.toml | 4 +- .../linter_bad_config/linter/.elp.toml | 4 +- test/test_projects/parse_error/.elp.toml | 4 +- test/test_projects/standard/.elp.toml | 4 +- test/test_projects/xref/.elp.toml | 4 +- 23 files changed, 180 insertions(+), 130 deletions(-) create mode 100644 test/test_projects/.buckconfig create mode 100644 test/test_projects/.buckroot diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index 3391fe2c63..df99eff987 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -605,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/test_projects/standard:app_a", - ], + args_vec!["eqwalize-target", "//standard:app_a",], "standard", expect_file!("../resources/test/standard/eqwalize_target_diagnostics.pretty"), true, @@ -985,38 +982,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": [ "" ], @@ -1024,61 +996,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": [] @@ -1093,6 +1092,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() { diff --git a/crates/elp/src/resources/test/xref/unavailable_type.stdout b/crates/elp/src/resources/test/xref/unavailable_type.stdout index b6338343c0..688be17de3 100644 --- a/crates/elp/src/resources/test/xref/unavailable_type.stdout +++ b/crates/elp/src/resources/test/xref/unavailable_type.stdout @@ -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/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/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'). diff --git a/crates/project_model/src/buck.rs b/crates/project_model/src/buck.rs index bb3ec38b5f..96fc1eac89 100644 --- a/crates/project_model/src/buck.rs +++ b/crates/project_model/src/buck.rs @@ -58,6 +58,7 @@ lazy_static! { } const ERL_EXT: &str = "erl"; +const BUCK_ISOLATION_DIR: &str = "lsp"; #[derive( Debug, @@ -108,7 +109,7 @@ impl BuckConfig { cmd.env_remove("RUST_BACKTRACE") .env_remove("RUST_LIB_BACKTRACE"); cmd.arg("--isolation-dir"); - cmd.arg("lsp"); + cmd.arg(BUCK_ISOLATION_DIR); cmd.current_dir(self.buck_root()); CommandProxy::new(guard, cmd) } @@ -1362,36 +1363,56 @@ fn include_path_from_file(path: &AbsPath) -> AbsPathBuf { } } +fn check_buck_output_success(mut command: CommandProxy<'_>) -> Result { + let output = command.output()?; + if output.status.success() { + return String::from_utf8(output.stdout) + .map_err(|e| anyhow::anyhow!("Invalid UTF-8 in stdout for `{command}`: {e}")); + } + let reason = match output.status.code() { + Some(code) => format!("Exited with status code: {code}"), + None => "Process terminated by signal".to_string(), + }; + let details = String::from_utf8(output.stderr).unwrap_or_default(); + bail!("Command `{command}` failed. Reason: {reason}. Details: {details}"); +} + /// This is used in tests pub fn get_prelude_cell(buck_config: &BuckConfig) -> Result { - let output = buck_config - .buck_command() + let mut command = buck_config.buck_command(); + command .arg("audit") .arg("cell") .arg("prelude") - .output()?; - if !output.status.success() { - let reason = match output.status.code() { - Some(code) => format!("Exited with status code: {code}"), - None => "Process terminated by signal".to_string(), - }; - let details = match String::from_utf8(output.stderr) { - Ok(err) => err, - Err(_) => "".to_string(), - }; - bail!("Error evaluating Buck2 query Reason: {reason}. Details: {details}",); - } - let raw_output = String::from_utf8(output.stdout)?; + .arg("--json"); + let raw_output = check_buck_output_success(command)?; - lazy_static! { - static ref RE: Regex = Regex::new(r"^prelude: ([^\s]+)").unwrap(); + let json: serde_json::Value = serde_json::from_str(&raw_output)?; + let prelude_path = json + .get("prelude") + .and_then(|v| v.as_str()) + .ok_or_else(|| anyhow::anyhow!("Could not find prelude path in Buck2 output"))? + .to_string(); + + if Path::new(&prelude_path).exists() { + Ok(prelude_path) + } else { + get_prelude_cell_bundled(buck_config) } - let string = RE - .captures_iter(&raw_output) - .next() - .map(|c| c[1].to_string()) - .unwrap(); - Ok(string) +} + +fn get_prelude_cell_bundled(buck_config: &BuckConfig) -> Result { + let mut command = buck_config.buck_command(); + command.arg("root"); + let root = check_buck_output_success(command)?; + let root = root.trim(); + let bundled_prelude_path = Path::new(&root) + .join("buck-out") + .join(BUCK_ISOLATION_DIR) + .join("external_cells") + .join("bundled") + .join("prelude"); + Ok(bundled_prelude_path.to_string_lossy().to_string()) } #[cfg(test)] @@ -1655,7 +1676,7 @@ mod tests { .arg("--") .args(generated_args) .arg("--included_targets") - .arg("fbcode//whatsapp/elp/test/test_projects/buck_tests_2/auto_gen/...") + .arg("root//buck_tests_2/auto_gen/...") .output() .unwrap(); if !output.status.success() { @@ -1679,7 +1700,7 @@ mod tests { false, expect![[r#" { - "fbcode//whatsapp/elp/test/test_projects/buck_tests_2/auto_gen/auto_gen_a:auto_gen_a": { + "root//buck_tests_2/auto_gen/auto_gen_a:auto_gen_a": { "name": "auto_gen_a", "app_name": null, "suite": null, @@ -1697,7 +1718,7 @@ mod tests { "included_apps": [], "origin": "app" }, - "fbcode//whatsapp/elp/test/test_projects/buck_tests_2/auto_gen/auto_gen_a:generated_srcs": { + "root//buck_tests_2/auto_gen/auto_gen_a:generated_srcs": { "name": "generated_srcs", "app_name": null, "suite": null, @@ -1842,13 +1863,13 @@ mod tests { fn build_info_buck_bxl_generated_query() { if BUCK_TESTS_ENABLED { // Note that there is now a value for `srcs` in the - // "fbcode//whatsapp/elp/test/test_projects/buck_tests_2/auto_gen/auto_gen_a:generated_srcs" + // "root//buck_tests_2/auto_gen/auto_gen_a:generated_srcs" // target check_buck_bxl_query( true, expect![[r#" { - "fbcode//whatsapp/elp/test/test_projects/buck_tests_2/auto_gen/auto_gen_a:auto_gen_a": { + "root//buck_tests_2/auto_gen/auto_gen_a:auto_gen_a": { "name": "auto_gen_a", "app_name": null, "suite": null, @@ -1866,7 +1887,7 @@ mod tests { "included_apps": [], "origin": "app" }, - "fbcode//whatsapp/elp/test/test_projects/buck_tests_2/auto_gen/auto_gen_a:generated_srcs": { + "root//buck_tests_2/auto_gen/auto_gen_a:generated_srcs": { "name": "generated_srcs", "app_name": null, "suite": null, diff --git a/test/test_projects/.buckconfig b/test/test_projects/.buckconfig new file mode 100644 index 0000000000..f14e564a7d --- /dev/null +++ b/test/test_projects/.buckconfig @@ -0,0 +1,24 @@ +[cells] + root = . + prelude = prelude + toolchains = toolchains + none = none + +[cell_aliases] + config = prelude + ovr_config = prelude + fbcode = none + fbsource = none + fbcode_macros = none + buck = none + +[external_cells] + prelude = bundled + +[parser] + target_platform_detector_spec = target:root//...->prelude//platforms:default \ + target:prelude//...->prelude//platforms:default \ + target:toolchains//...->prelude//platforms:default + +[build] + execution_platforms = prelude//platforms:default diff --git a/test/test_projects/.buckroot b/test/test_projects/.buckroot new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/test_projects/buck_bad_config/.elp.toml b/test/test_projects/buck_bad_config/.elp.toml index a4d80e289f..963072284c 100644 --- a/test/test_projects/buck_bad_config/.elp.toml +++ b/test/test_projects/buck_bad_config/.elp.toml @@ -1,8 +1,8 @@ [buck] enabled = true build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test/test_projects/buck_bad_config/..." ] -source_root = "whatsapp/elp/test/test_projects/buck_bad_config" +included_targets = [ "root//buck_bad_config/..." ] +source_root = "buck_bad_config" [eqwalizer] enable_all = false diff --git a/test/test_projects/buck_tests/.elp.toml b/test/test_projects/buck_tests/.elp.toml index 0fbba8f0d6..b38d6f043f 100644 --- a/test/test_projects/buck_tests/.elp.toml +++ b/test/test_projects/buck_tests/.elp.toml @@ -1,9 +1,9 @@ [buck] enabled = true build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test/test_projects/buck_tests/..." ] -excluded_targets = [ "fbcode//whatsapp/elp/test/test_projects/buck_tests:test_elp_ignored" ] -source_root = "whatsapp/elp/test/test_projects/buck_tests" +included_targets = [ "root//buck_tests/..." ] +excluded_targets = [ "buck_tests:test_elp_ignored" ] +source_root = "buck_tests" [eqwalizer] enable_all = false diff --git a/test/test_projects/buck_tests/test_elp/TARGETS.v2_ b/test/test_projects/buck_tests/test_elp/TARGETS.v2_ index 528f2dbc2a..7e5bd95db5 100644 --- a/test/test_projects/buck_tests/test_elp/TARGETS.v2_ +++ b/test/test_projects/buck_tests/test_elp/TARGETS.v2_ @@ -7,11 +7,11 @@ erlang_application( ]), app_src = "src/test_elp.app.src", applications = [ - "//whatsapp/elp/test/test_projects/buck_tests/test_elp_direct_dep:test_elp_direct_dep", - "//whatsapp/elp/test/test_projects/buck_tests:test_elp_no_private_headers", - "//whatsapp/elp/test/test_projects/buck_tests:test_elp_no_public_headers", - "//whatsapp/elp/test/test_projects/buck_tests:test_elp_flat_outside_target", - "//whatsapp/elp/test/test_projects/buck_tests/test_elp_flat_inside_target:test_elp_flat_inside_target", + "//buck_tests/test_elp_direct_dep:test_elp_direct_dep", + "//buck_tests:test_elp_no_private_headers", + "//buck_tests:test_elp_no_public_headers", + "//buck_tests:test_elp_flat_outside_target", + "//buck_tests/test_elp_flat_inside_target:test_elp_flat_inside_target", ], includes = glob(["include/*.hrl"]), version = "1.0.0", diff --git a/test/test_projects/buck_tests/test_elp_direct_dep/TARGETS.v2_ b/test/test_projects/buck_tests/test_elp_direct_dep/TARGETS.v2_ index dd46a58842..a752b99087 100644 --- a/test/test_projects/buck_tests/test_elp_direct_dep/TARGETS.v2_ +++ b/test/test_projects/buck_tests/test_elp_direct_dep/TARGETS.v2_ @@ -5,10 +5,10 @@ erlang_application( "src/*.hrl", ]), applications = [ - "//whatsapp/elp/test/test_projects/buck_tests/test_elp_transitive_dep:test_elp_transitive_dep", + "//buck_tests/test_elp_transitive_dep:test_elp_transitive_dep", ], extra_includes = [ - "//whatsapp/elp/test/test_projects/buck_tests/test_elp:test_elp", + "//buck_tests/test_elp:test_elp", ], includes = glob(["include/*.hrl"]), version = "1.0.0", diff --git a/test/test_projects/buck_tests_2/.elp.toml b/test/test_projects/buck_tests_2/.elp.toml index 102f44c3f5..e71eb6a819 100644 --- a/test/test_projects/buck_tests_2/.elp.toml +++ b/test/test_projects/buck_tests_2/.elp.toml @@ -2,11 +2,11 @@ enabled = true build_deps = false included_targets = [ - "fbcode//whatsapp/elp/test/test_projects/buck_tests_2/util/app_a/...", - "fbcode//whatsapp/elp/test/test_projects/buck_tests_2:check_include" + "root//buck_tests_2/util/app_a/...", + "root//buck_tests_2:check_include" ] -excluded_targets = [ "fbcode//whatsapp/elp/test/test_projects/buck_tests_2:test_elp_ignored" ] -source_root = "whatsapp/elp/test/test_projects/buck_tests_2" +excluded_targets = [ "root//buck_tests_2:test_elp_ignored" ] +source_root = "buck_tests_2" [eqwalizer] enable_all = false diff --git a/test/test_projects/diagnostics/.elp.toml b/test/test_projects/diagnostics/.elp.toml index 44c9516105..504ef547af 100644 --- a/test/test_projects/diagnostics/.elp.toml +++ b/test/test_projects/diagnostics/.elp.toml @@ -1,8 +1,8 @@ [buck] enabled = true build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test/test_projects/diagnostics/..." ] -source_root = "whatsapp/elp/test/test_projects/diagnostics" +included_targets = [ "root//diagnostics/..." ] +source_root = "diagnostics" [eqwalizer] enable_all = false diff --git a/test/test_projects/end_to_end/.elp.toml b/test/test_projects/end_to_end/.elp.toml index 4d685a40c7..acbcd6146f 100644 --- a/test/test_projects/end_to_end/.elp.toml +++ b/test/test_projects/end_to_end/.elp.toml @@ -1,7 +1,7 @@ [buck] enabled = true build_deps = false -included_targets = ["fbcode//whatsapp/elp/test/test_projects/end_to_end/..."] +included_targets = ["root//end_to_end/..."] [eqwalizer] enable_all = false diff --git a/test/test_projects/eqwalizer_callers/.elp.toml b/test/test_projects/eqwalizer_callers/.elp.toml index b0c2b55a5b..68476bcf2e 100644 --- a/test/test_projects/eqwalizer_callers/.elp.toml +++ b/test/test_projects/eqwalizer_callers/.elp.toml @@ -1,5 +1,5 @@ [buck] enabled = true build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test/test_projects/eqwalizer_callers/..." ] -source_root = "whatsapp/elp/test/test_projects/eqwalizer_callers" +included_targets = [ "root//eqwalizer_callers/..." ] +source_root = "eqwalizer_callers" diff --git a/test/test_projects/eqwalizer_tests/.elp.toml b/test/test_projects/eqwalizer_tests/.elp.toml index 9586666619..5dac3aa6a6 100644 --- a/test/test_projects/eqwalizer_tests/.elp.toml +++ b/test/test_projects/eqwalizer_tests/.elp.toml @@ -1,8 +1,8 @@ [buck] enabled = true build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test/test_projects/eqwalizer_tests/..." ] -source_root = "whatsapp/elp/test/test_projects/eqwalizer_tests" +included_targets = [ "root//eqwalizer_tests/..." ] +source_root = "eqwalizer_tests" [eqwalizer] enable_all = true diff --git a/test/test_projects/hierarchical_config/.elp.toml b/test/test_projects/hierarchical_config/.elp.toml index a650134c9f..e4d04eb6e0 100644 --- a/test/test_projects/hierarchical_config/.elp.toml +++ b/test/test_projects/hierarchical_config/.elp.toml @@ -1,5 +1,5 @@ [buck] enabled = true build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test/test_projects/hierarchical_config/..." ] -source_root = "whatsapp/elp/test/test_projects/hierarchical_config" +included_targets = [ "root//hierarchical_config/..." ] +source_root = "hierarchical_config" diff --git a/test/test_projects/in_place_tests/.elp.toml b/test/test_projects/in_place_tests/.elp.toml index 64140d2852..70edcc3f03 100644 --- a/test/test_projects/in_place_tests/.elp.toml +++ b/test/test_projects/in_place_tests/.elp.toml @@ -1,5 +1,5 @@ [buck] enabled = true build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test/test_projects/in_place_tests/..." ] -source_root = "whatsapp/elp/test/test_projects/in_place_tests" +included_targets = [ "root//in_place_tests/..." ] +source_root = "in_place_tests" diff --git a/test/test_projects/include_lib_dependency_test/.elp.toml b/test/test_projects/include_lib_dependency_test/.elp.toml index 02b5b7d244..9d1c859339 100644 --- a/test/test_projects/include_lib_dependency_test/.elp.toml +++ b/test/test_projects/include_lib_dependency_test/.elp.toml @@ -1,5 +1,5 @@ [buck] enabled = true build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test/test_projects/include_lib_dependency_test/..." ] -source_root = "whatsapp/elp/test/test_projects/include_lib_dependency_test" +included_targets = [ "root//include_lib_dependency_test/..." ] +source_root = "include_lib_dependency_test" diff --git a/test/test_projects/linter/.elp.toml b/test/test_projects/linter/.elp.toml index bc8b86088c..4820625b2d 100644 --- a/test/test_projects/linter/.elp.toml +++ b/test/test_projects/linter/.elp.toml @@ -1,5 +1,5 @@ [buck] enabled = true build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test/test_projects/linter/..." ] -source_root = "whatsapp/elp/test/test_projects/linter" +included_targets = [ "root//linter/..." ] +source_root = "linter" diff --git a/test/test_projects/linter_bad_config/.elp.toml b/test/test_projects/linter_bad_config/.elp.toml index bc8b86088c..4820625b2d 100644 --- a/test/test_projects/linter_bad_config/.elp.toml +++ b/test/test_projects/linter_bad_config/.elp.toml @@ -1,5 +1,5 @@ [buck] enabled = true build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test/test_projects/linter/..." ] -source_root = "whatsapp/elp/test/test_projects/linter" +included_targets = [ "root//linter/..." ] +source_root = "linter" diff --git a/test/test_projects/linter_bad_config/linter/.elp.toml b/test/test_projects/linter_bad_config/linter/.elp.toml index bc8b86088c..4820625b2d 100644 --- a/test/test_projects/linter_bad_config/linter/.elp.toml +++ b/test/test_projects/linter_bad_config/linter/.elp.toml @@ -1,5 +1,5 @@ [buck] enabled = true build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test/test_projects/linter/..." ] -source_root = "whatsapp/elp/test/test_projects/linter" +included_targets = [ "root//linter/..." ] +source_root = "linter" diff --git a/test/test_projects/parse_error/.elp.toml b/test/test_projects/parse_error/.elp.toml index a38e24f657..1a06a42f0d 100644 --- a/test/test_projects/parse_error/.elp.toml +++ b/test/test_projects/parse_error/.elp.toml @@ -1,8 +1,8 @@ [buck] enabled = true build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test/test_projects/parse_error/..." ] -source_root = "whatsapp/elp/test/test_projects/parse_error" +included_targets = [ "root//parse_error/..." ] +source_root = "parse_error" [eqwalizer] enable_all = false diff --git a/test/test_projects/standard/.elp.toml b/test/test_projects/standard/.elp.toml index 29b2e051a4..f5e2ac1943 100644 --- a/test/test_projects/standard/.elp.toml +++ b/test/test_projects/standard/.elp.toml @@ -1,5 +1,5 @@ [buck] enabled = true build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test/test_projects/standard/..." ] -source_root = "whatsapp/elp/test/test_projects/standard" +included_targets = [ "root//standard/..." ] +source_root = "standard" diff --git a/test/test_projects/xref/.elp.toml b/test/test_projects/xref/.elp.toml index 0b684fba14..87c2f17334 100644 --- a/test/test_projects/xref/.elp.toml +++ b/test/test_projects/xref/.elp.toml @@ -1,5 +1,5 @@ [buck] enabled = true build_deps = false -included_targets = [ "fbcode//whatsapp/elp/test/test_projects/xref/..." ] -source_root = "whatsapp/elp/test/test_projects/xref" +included_targets = [ "root//xref/..." ] +source_root = "xref" From 8b4c5335271fab6ecbe9642d4db7c392092783bb Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Thu, 18 Dec 2025 23:41:12 -0800 Subject: [PATCH 134/142] Convert EDoc syntax to plain comments in codegen_test_SUITE 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 --- .../codegen_test/app_a/test/codegen_test_SUITE.erl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/test_projects/codegen_test/app_a/test/codegen_test_SUITE.erl b/test/test_projects/codegen_test/app_a/test/codegen_test_SUITE.erl index 797c3c58f3..3c0135dcee 100644 --- a/test/test_projects/codegen_test/app_a/test/codegen_test_SUITE.erl +++ b/test/test_projects/codegen_test/app_a/test/codegen_test_SUITE.erl @@ -1,8 +1,4 @@ -%%%------------------------------------------------------------------- -%%% @doc %%% Test suite for code generation functionality -%%% @end -%%%------------------------------------------------------------------- -module(codegen_test_SUITE). -include_lib("stdlib/include/assert.hrl"). From 8cb6ac76207aa6af0def85c2cb17a4cb1226ec63 Mon Sep 17 00:00:00 2001 From: Facebook Community Bot Date: Fri, 19 Dec 2025 02:51:11 -0800 Subject: [PATCH 135/142] 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 --- test/test_projects/buck_bad_config/BUCK | 14 ++++ test/test_projects/buck_tests_2/BUCK | 41 ++++++++++ .../buck_tests_2/auto_gen/auto_gen_a/BUCK | 25 ++++++ .../test_projects/buck_tests_2/generated/BUCK | 12 +++ .../buck_tests_2/util/app_a/BUCK | 16 ++++ test/test_projects/codegen_test/BUCK | 78 +++++++++++++++++++ test/test_projects/diagnostics/BUCK | 20 +++++ .../end_to_end/assist_examples/BUCK | 14 ++++ .../test_projects/end_to_end/definitions/BUCK | 14 ++++ test/test_projects/end_to_end/docs/BUCK | 16 ++++ test/test_projects/end_to_end/hover/BUCK | 14 ++++ .../end_to_end/single_errors/BUCK | 14 ++++ test/test_projects/eqwalizer_callers/BUCK | 19 +++++ test/test_projects/eqwalizer_tests/BUCK | 77 ++++++++++++++++++ test/test_projects/hierarchical_config/BUCK | 23 ++++++ test/test_projects/in_place_tests/BUCK | 19 +++++ .../include_lib_dependency_test/BUCK | 43 ++++++++++ test/test_projects/linter/BUCK | 41 ++++++++++ test/test_projects/linter_bad_config/BUCK | 12 +++ .../linter_bad_config/linter/BUCK | 41 ++++++++++ test/test_projects/parse_error/BUCK | 9 +++ test/test_projects/standard/BUCK | 49 ++++++++++++ test/test_projects/toolchains/BUCK | 5 ++ test/test_projects/xref/BUCK | 25 ++++++ 24 files changed, 641 insertions(+) create mode 100644 test/test_projects/buck_bad_config/BUCK create mode 100644 test/test_projects/buck_tests_2/BUCK create mode 100644 test/test_projects/buck_tests_2/auto_gen/auto_gen_a/BUCK create mode 100644 test/test_projects/buck_tests_2/generated/BUCK create mode 100644 test/test_projects/buck_tests_2/util/app_a/BUCK create mode 100644 test/test_projects/codegen_test/BUCK create mode 100644 test/test_projects/diagnostics/BUCK create mode 100644 test/test_projects/end_to_end/assist_examples/BUCK create mode 100644 test/test_projects/end_to_end/definitions/BUCK create mode 100644 test/test_projects/end_to_end/docs/BUCK create mode 100644 test/test_projects/end_to_end/hover/BUCK create mode 100644 test/test_projects/end_to_end/single_errors/BUCK create mode 100644 test/test_projects/eqwalizer_callers/BUCK create mode 100644 test/test_projects/eqwalizer_tests/BUCK create mode 100644 test/test_projects/hierarchical_config/BUCK create mode 100644 test/test_projects/in_place_tests/BUCK create mode 100644 test/test_projects/include_lib_dependency_test/BUCK create mode 100644 test/test_projects/linter/BUCK create mode 100644 test/test_projects/linter_bad_config/BUCK create mode 100644 test/test_projects/linter_bad_config/linter/BUCK create mode 100644 test/test_projects/parse_error/BUCK create mode 100644 test/test_projects/standard/BUCK create mode 100644 test/test_projects/toolchains/BUCK create mode 100644 test/test_projects/xref/BUCK diff --git a/test/test_projects/buck_bad_config/BUCK b/test/test_projects/buck_bad_config/BUCK new file mode 100644 index 0000000000..498c769dac --- /dev/null +++ b/test/test_projects/buck_bad_config/BUCK @@ -0,0 +1,14 @@ +oncall("vscode_erlang") + +erlang_application( + name = "bad_app", + srcs = glob(["src/*.erl"]), + applications = [ + "root//buck_bad_config/non_existent:missing", + ], + includes = glob( + ["include/*.hrl"], + exclude = ["include/junk.hrl"], + ), + version = "1.0.0", +) diff --git a/test/test_projects/buck_tests_2/BUCK b/test/test_projects/buck_tests_2/BUCK new file mode 100644 index 0000000000..b01fdb868c --- /dev/null +++ b/test/test_projects/buck_tests_2/BUCK @@ -0,0 +1,41 @@ +oncall("vscode_erlang") + +erlang_application( + name = "check_include", + srcs = [ + "check_include/src/top_includer.erl", + ], + applications = [ + "common_test", + "stdlib", + "root//buck_tests_2:check_include_separate_1", + "root//buck_tests_2:check_include_separate_2", + ], + includes = [], + labels = [], + resources = [], +) + +erlang_application( + name = "check_include_separate_1", + srcs = glob([ + "src/*.erl", + ]), + applications = [ + ], + includes = [ + "check_include_separate_1/include/top_includer.hrl", + ], + resources = [], +) + +erlang_application( + name = "check_include_separate_2", + srcs = glob([ + "src/*.erl", + ]), + applications = [ + ], + includes = glob(["check_include_separate_2/include/*.hrl"]), + resources = [], +) diff --git a/test/test_projects/buck_tests_2/auto_gen/auto_gen_a/BUCK b/test/test_projects/buck_tests_2/auto_gen/auto_gen_a/BUCK new file mode 100644 index 0000000000..f021317020 --- /dev/null +++ b/test/test_projects/buck_tests_2/auto_gen/auto_gen_a/BUCK @@ -0,0 +1,25 @@ +oncall("vscode_erlang") + +erlang_application( + name = "auto_gen_a", + srcs = glob([ + "src/*.erl", + "src/*.hrl", + ]), + includes = glob(["include/*.hrl"]), + visibility = ["//buck_tests_2/..."], +) + +erlang_application( + name = "generated_srcs", + srcs = [ + ":generated.erl", + ], + labels = ["generated"], + visibility = ["//buck_tests_2/..."], +) + +export_file( + name = "generated.erl", + src = "out/pretend_generated.erl", +) diff --git a/test/test_projects/buck_tests_2/generated/BUCK b/test/test_projects/buck_tests_2/generated/BUCK new file mode 100644 index 0000000000..8eec40830b --- /dev/null +++ b/test/test_projects/buck_tests_2/generated/BUCK @@ -0,0 +1,12 @@ +oncall("vscode_erlang") + +erlang_application( + name = "generated_headers", + includes = [ + "out/generated_header.hrl", + ], + labels = ["generated"], + visibility = [ + "PUBLIC", + ], +) diff --git a/test/test_projects/buck_tests_2/util/app_a/BUCK b/test/test_projects/buck_tests_2/util/app_a/BUCK new file mode 100644 index 0000000000..4231c1c6e8 --- /dev/null +++ b/test/test_projects/buck_tests_2/util/app_a/BUCK @@ -0,0 +1,16 @@ +oncall("vscode_erlang") + +erlang_application( + name = "app_a_target", + srcs = glob(["src/*.erl"]), + app_name = "app_a", + applications = [ + "root//buck_tests_2/auto_gen/auto_gen_a:auto_gen_a", + "root//buck_tests_2/generated:generated_headers", + ], + includes = glob( + ["include/*.hrl"], + exclude = ["include/junk.hrl"], + ), + version = "1.0.0", +) diff --git a/test/test_projects/codegen_test/BUCK b/test/test_projects/codegen_test/BUCK new file mode 100644 index 0000000000..3530fca2ff --- /dev/null +++ b/test/test_projects/codegen_test/BUCK @@ -0,0 +1,78 @@ +load("@fbcode_macros//build_defs:native_rules.bzl", "buck_genrule") + +oncall("vscode_erlang") + +# Code generation rule - generates Erlang modules from template files +buck_genrule( + name = "example_service_types_erl", + srcs = [ + "templates/example_service_types.erl", + ], + outs = { + "example_service_types.erl": ["example_service_types.erl"], + }, + bash = "cp $SRCDIR/templates/example_service_types.erl $OUT/example_service_types.erl", + cmd_exe = "copy %SRCDIR%\\templates\\example_service_types.erl %OUT%\\example_service_types.erl", +) + +buck_genrule( + name = "example_service_client_erl", + srcs = [ + "templates/example_service_client.erl", + ], + outs = { + "example_service_client.erl": ["example_service_client.erl"], + }, + bash = "cp $SRCDIR/templates/example_service_client.erl $OUT/example_service_client.erl", + cmd_exe = "copy %SRCDIR%\\templates\\example_service_client.erl %OUT%\\example_service_client.erl", +) + +buck_genrule( + name = "example_service_types_hrl", + srcs = [ + "templates/example_service_types.hrl", + ], + outs = { + "example_service_types.hrl": ["example_service_types.hrl"], + }, + bash = "cp $SRCDIR/templates/example_service_types.hrl $OUT/example_service_types.hrl", + cmd_exe = "copy %SRCDIR%\\templates\\example_service_types.hrl %OUT%\\example_service_types.hrl", +) + +# Erlang library containing only the generated code +erlang_app( + name = "example_service_generated", + srcs = [ + # Include generated Erlang modules from genrule output + ":example_service_types_erl[example_service_types.erl]", + ":example_service_client_erl[example_service_client.erl]", + ], + includes = [ + # Include generated header files from genrule output + ":example_service_types_hrl[example_service_types.hrl]", + ], +) + +# Erlang application that uses the generated code (non-generated files only) +erlang_application( + name = "codegen_test_app", + srcs = glob(["app_a/src/*.erl"]), + app_name = "codegen_test", + app_src = "app_a/src/codegen_test.app.src", + applications = [ + "kernel", + "stdlib", + ":example_service_generated", + ], + includes = glob(["app_a/include/*.hrl"]), + labels = ["user_application"], + version = "1.0.0", +) + +# Test to verify the generated code works +erlang_tests( + suites = [ + "app_a/test/codegen_test_SUITE.erl", + ], + deps = [":codegen_test_app"], +) diff --git a/test/test_projects/diagnostics/BUCK b/test/test_projects/diagnostics/BUCK new file mode 100644 index 0000000000..19c15e5acf --- /dev/null +++ b/test/test_projects/diagnostics/BUCK @@ -0,0 +1,20 @@ +oncall("vscode_erlang") + +erlang_application( + name = "diagnostics_app_a_target", + srcs = glob(["app_a/src/*.erl"]), + app_name = "diagnostics_app_a", + app_src = "app_a/src/app_a.app.src", + applications = [ + ], + includes = glob(["app_a/include/*.hrl"]), + labels = ["user_application"], + version = "1.0.0", +) + +erlang_tests( + suites = [ + "app_a/test/app_a_SUITE.erl", + ], + deps = [":diagnostics_app_a_target"], +) diff --git a/test/test_projects/end_to_end/assist_examples/BUCK b/test/test_projects/end_to_end/assist_examples/BUCK new file mode 100644 index 0000000000..60d39de125 --- /dev/null +++ b/test/test_projects/end_to_end/assist_examples/BUCK @@ -0,0 +1,14 @@ +oncall("vscode_erlang") + +erlang_application( + name = "assist_examples", + srcs = glob([ + "src/*.erl", + ]), + app_src = "src/assist_examples.app.src", + applications = [], + includes = [], + labels = ["user_application"], + version = "1.0.0", + visibility = ["PUBLIC"], +) diff --git a/test/test_projects/end_to_end/definitions/BUCK b/test/test_projects/end_to_end/definitions/BUCK new file mode 100644 index 0000000000..c9e6b204f4 --- /dev/null +++ b/test/test_projects/end_to_end/definitions/BUCK @@ -0,0 +1,14 @@ +oncall("vscode_erlang") + +erlang_application( + name = "definitions", + srcs = glob([ + "src/*.erl", + ]), + app_src = "src/definitions.app.src", + applications = [], + includes = [], + labels = ["user_application"], + version = "1.0.0", + visibility = ["PUBLIC"], +) diff --git a/test/test_projects/end_to_end/docs/BUCK b/test/test_projects/end_to_end/docs/BUCK new file mode 100644 index 0000000000..7429863b6e --- /dev/null +++ b/test/test_projects/end_to_end/docs/BUCK @@ -0,0 +1,16 @@ +load("@waserver//buck2/whatsapp:erlang.bzl", "erlang_application") + +oncall("vscode_erlang") + +erlang_application( + name = "docs", + srcs = glob([ + "src/*.erl", + ]), + app_src = "src/docs.app.src", + applications = [], + includes = [], + labels = ["user_application"], + version = "1.0.0", + visibility = ["PUBLIC"], +) diff --git a/test/test_projects/end_to_end/hover/BUCK b/test/test_projects/end_to_end/hover/BUCK new file mode 100644 index 0000000000..7732dc473e --- /dev/null +++ b/test/test_projects/end_to_end/hover/BUCK @@ -0,0 +1,14 @@ +oncall("vscode_erlang") + +erlang_application( + name = "hover", + srcs = glob([ + "src/*.erl", + ]), + app_src = "src/hover.app.src", + applications = [], + includes = [], + labels = ["user_application"], + version = "1.0.0", + visibility = ["PUBLIC"], +) diff --git a/test/test_projects/end_to_end/single_errors/BUCK b/test/test_projects/end_to_end/single_errors/BUCK new file mode 100644 index 0000000000..a8f6e478b0 --- /dev/null +++ b/test/test_projects/end_to_end/single_errors/BUCK @@ -0,0 +1,14 @@ +oncall("vscode_erlang") + +erlang_application( + name = "single_errors", + srcs = glob([ + "src/*.erl", + ]), + app_src = "src/single_errors.app.src", + applications = [], + includes = [], + labels = ["user_application"], + version = "1.0.0", + visibility = ["PUBLIC"], +) diff --git a/test/test_projects/eqwalizer_callers/BUCK b/test/test_projects/eqwalizer_callers/BUCK new file mode 100644 index 0000000000..32012a0dc0 --- /dev/null +++ b/test/test_projects/eqwalizer_callers/BUCK @@ -0,0 +1,19 @@ +oncall("vscode_erlang") + +erlang_application( + name = "app_a", + srcs = glob(["app_a/src/*.erl"]), + app_src = "app_a/src/app_a.app.src", + applications = [ + ], + includes = glob(["app_a/include/*.hrl"]), + labels = ["user_application"], + version = "1.0.0", +) + +erlang_tests( + suites = [ + "app_a/test/app_a_SUITE.erl", + ], + deps = [":app_a"], +) diff --git a/test/test_projects/eqwalizer_tests/BUCK b/test/test_projects/eqwalizer_tests/BUCK new file mode 100644 index 0000000000..0817873cb0 --- /dev/null +++ b/test/test_projects/eqwalizer_tests/BUCK @@ -0,0 +1,77 @@ +oncall("vscode_erlang") + +erlang_application( + name = "check", + srcs = glob(["check/src/**/*.erl"]), + applications = [":eqwalizer"], + includes = glob([ + "check/include/*.hrl", + "eqwalizer/include/*.hrl", + ]), + labels = ["user_application"], + version = "1.0.0", +) + +erlang_application( + name = "check_gradual", + srcs = glob(["check_gradual/src/*.erl"]), + applications = [":eqwalizer"], + labels = ["user_application"], + version = "1.0.0", +) + +erlang_tests( + suites = [ + "check/test/check_SUITE.erl", + ], + deps = [":check"], +) + +erlang_application( + name = "debug", + srcs = glob(["debug/src/*.erl"]), + applications = [":eqwalizer"], + includes = glob(["debug/include/*.hrl"]), + labels = ["user_application"], + version = "1.0.0", +) + +erlang_application( + name = "elm_core", + srcs = glob(["elm_core/src/*.erl"]), + applications = [":eqwalizer"], + labels = ["user_application"], + version = "1.0.0", +) + +erlang_application( + name = "eqwater", + srcs = glob(["eqwater/src/*.erl"]), + applications = [":eqwalizer"], + labels = ["user_application"], + version = "1.0.0", +) + +erlang_application( + name = "fault_tolerance", + srcs = glob(["fault_tolerance/src/*.erl"]), + applications = [":eqwalizer"], + labels = ["user_application"], + version = "1.0.0", +) + +erlang_application( + name = "options", + srcs = glob(["options/src/*.erl"]), + applications = [":eqwalizer"], + labels = ["user_application"], + version = "1.0.0", +) + +erlang_application( + name = "eqwalizer", + srcs = glob(["eqwalizer/src/*.erl"]), + includes = glob(["eqwalizer/include/*.hrl"]), + labels = ["user_application"], + version = "1.0.0", +) diff --git a/test/test_projects/hierarchical_config/BUCK b/test/test_projects/hierarchical_config/BUCK new file mode 100644 index 0000000000..bc78a42d57 --- /dev/null +++ b/test/test_projects/hierarchical_config/BUCK @@ -0,0 +1,23 @@ +oncall("vscode_erlang") + +erlang_application( + name = "app_a", + srcs = glob(["app_a/src/*.erl"]), + app_src = "app_a/src/app_a.app.src", + applications = [ + ], + includes = glob(["app_a/include/*.hrl"]), + labels = ["user_application"], + version = "1.0.0", +) + +erlang_application( + name = "app_b", + srcs = glob(["app_b/src/*.erl"]), + app_src = "app_b/src/app_b.app.src", + applications = [ + ], + includes = glob(["app_b/include/*.hrl"]), + labels = ["user_application"], + version = "1.0.0", +) diff --git a/test/test_projects/in_place_tests/BUCK b/test/test_projects/in_place_tests/BUCK new file mode 100644 index 0000000000..f4f354e7ee --- /dev/null +++ b/test/test_projects/in_place_tests/BUCK @@ -0,0 +1,19 @@ +oncall("vscode_erlang") + +erlang_application( + name = "in_place_tests_app_a", + srcs = glob(["app_a/src/*.erl"]), + app_src = "app_a/src/app_a.app.src", + applications = [ + ], + includes = glob(["app_a/include/*.hrl"]), + labels = ["user_application"], + version = "1.0.0", +) + +erlang_tests( + suites = [ + "app_a/test/app_a_SUITE.erl", + ], + deps = [":in_place_tests_app_a"], +) diff --git a/test/test_projects/include_lib_dependency_test/BUCK b/test/test_projects/include_lib_dependency_test/BUCK new file mode 100644 index 0000000000..54b81c47b5 --- /dev/null +++ b/test/test_projects/include_lib_dependency_test/BUCK @@ -0,0 +1,43 @@ +oncall("vscode_erlang") + +# Main application that will try to include_lib from an app it doesn't depend on +erlang_application( + name = "main_app", + srcs = glob(["main_app/src/*.erl"]), + app_src = "main_app/src/main_app.app.src", + applications = [ + # Note: deliberately NOT including :external_app as a dependency + "root//include_lib_dependency_test:normal_dep", + ], + extra_includes = ["root//include_lib_dependency_test:extra_app"], + labels = ["user_application"], + version = "1.0.0", +) + +# External application that main_app will try to include_lib from +erlang_application( + name = "external_app", + srcs = glob(["external_app/src/*.erl"]), + app_src = "external_app/src/external_app.app.src", + includes = glob(["external_app/include/*.hrl"]), + labels = ["user_application"], + version = "1.0.0", +) + +erlang_application( + name = "extra_app", + srcs = glob(["extra_app/src/*.erl"]), + app_src = "extra_app/src/extra_app.app.src", + includes = glob(["extra_app/include/*.hrl"]), + labels = ["user_application"], + version = "1.0.0", +) + +erlang_application( + name = "normal_dep", + srcs = glob(["normal_dep/src/*.erl"]), + app_src = "normal_dep/src/normal_dep.app.src", + includes = glob(["normal_dep/include/*.hrl"]), + labels = ["user_application"], + version = "1.0.0", +) diff --git a/test/test_projects/linter/BUCK b/test/test_projects/linter/BUCK new file mode 100644 index 0000000000..2db8750582 --- /dev/null +++ b/test/test_projects/linter/BUCK @@ -0,0 +1,41 @@ +oncall("vscode_erlang") + +erlang_application( + name = "app_a", + srcs = glob(["app_a/src/*.erl"]), + app_src = "app_a/src/app_a.app.src", + applications = [ + ], + includes = glob(["app_a/include/*.hrl"]), + labels = ["user_application"], + version = "1.0.0", +) + +erlang_application( + name = "app_b", + srcs = glob(["app_b/src/*.erl"]), + app_src = "app_b/src/app_b.app.src", + labels = ["user_application"], + version = "1.0.0", +) + +erlang_tests( + suites = [ + "app_a/test/app_a_SUITE.erl", + ], + deps = [":app_a"], +) + +erlang_application( + name = "app_a_test_utils", + srcs = [ + "app_a/test/app_a_test_helpers.erl", + "app_a/test/app_a_test_helpers_not_opted_in.erl", + "app_a/test/app_test_helpers_no_errors.erl", + ], + applications = [":app_a"], + labels = [ + "test_application", + "test_utils", + ], +) diff --git a/test/test_projects/linter_bad_config/BUCK b/test/test_projects/linter_bad_config/BUCK new file mode 100644 index 0000000000..a0817a6ad6 --- /dev/null +++ b/test/test_projects/linter_bad_config/BUCK @@ -0,0 +1,12 @@ +oncall("vscode_erlang") + +erlang_application( + name = "app_a", + srcs = glob(["app_a/src/*.erl"]), + app_src = "app_a/src/app_a.app.src", + applications = [ + ], + includes = glob(["app_a/include/*.hrl"]), + labels = ["user_application"], + version = "1.0.0", +) diff --git a/test/test_projects/linter_bad_config/linter/BUCK b/test/test_projects/linter_bad_config/linter/BUCK new file mode 100644 index 0000000000..2db8750582 --- /dev/null +++ b/test/test_projects/linter_bad_config/linter/BUCK @@ -0,0 +1,41 @@ +oncall("vscode_erlang") + +erlang_application( + name = "app_a", + srcs = glob(["app_a/src/*.erl"]), + app_src = "app_a/src/app_a.app.src", + applications = [ + ], + includes = glob(["app_a/include/*.hrl"]), + labels = ["user_application"], + version = "1.0.0", +) + +erlang_application( + name = "app_b", + srcs = glob(["app_b/src/*.erl"]), + app_src = "app_b/src/app_b.app.src", + labels = ["user_application"], + version = "1.0.0", +) + +erlang_tests( + suites = [ + "app_a/test/app_a_SUITE.erl", + ], + deps = [":app_a"], +) + +erlang_application( + name = "app_a_test_utils", + srcs = [ + "app_a/test/app_a_test_helpers.erl", + "app_a/test/app_a_test_helpers_not_opted_in.erl", + "app_a/test/app_test_helpers_no_errors.erl", + ], + applications = [":app_a"], + labels = [ + "test_application", + "test_utils", + ], +) diff --git a/test/test_projects/parse_error/BUCK b/test/test_projects/parse_error/BUCK new file mode 100644 index 0000000000..ed65c267d0 --- /dev/null +++ b/test/test_projects/parse_error/BUCK @@ -0,0 +1,9 @@ +oncall("vscode_erlang") + +erlang_application( + name = "elp_test_parse_a", + srcs = glob(["parse_error_a/src/*.erl"]), + app_src = "parse_error_a/src/parse_error_a.app.src", + labels = ["user_application"], + version = "1.0.0", +) diff --git a/test/test_projects/standard/BUCK b/test/test_projects/standard/BUCK new file mode 100644 index 0000000000..3f1e9ee947 --- /dev/null +++ b/test/test_projects/standard/BUCK @@ -0,0 +1,49 @@ +oncall("vscode_erlang") + +erlang_application( + name = "app_a", + srcs = glob(["app_a/src/*.erl"]), + app_src = "app_a/src/app_a.app.src", + applications = [ + ], + includes = glob(["app_a/include/*.hrl"]), + labels = ["user_application"], + version = "1.0.0", +) + +erlang_application( + name = "elp_test_standard_app_b", + srcs = glob(["app_b/src/*.erl"]), + app_src = "app_b/src/app_b.app.src", + labels = ["user_application"], + version = "1.0.0", +) + +erlang_application( + name = "elp_test_eqwalizer", + srcs = glob(["eqwalizer/src/*.erl"]), + app_src = "eqwalizer/src/eqwalizer.app.src", + labels = ["user_application"], + version = "1.0.0", +) + +erlang_tests( + suites = [ + "app_a/test/app_a_SUITE.erl", + ], + deps = [":app_a"], +) + +erlang_application( + name = "app_a_test_utils", + srcs = [ + "app_a/test/app_a_test_helpers.erl", + "app_a/test/app_a_test_helpers_not_opted_in.erl", + "app_a/test/app_test_helpers_no_errors.erl", + ], + applications = [":app_a"], + labels = [ + "test_application", + "test_utils", + ], +) diff --git a/test/test_projects/toolchains/BUCK b/test/test_projects/toolchains/BUCK new file mode 100644 index 0000000000..770ac61f67 --- /dev/null +++ b/test/test_projects/toolchains/BUCK @@ -0,0 +1,5 @@ +load("@prelude//toolchains:demo.bzl", "system_demo_toolchains") + +oncall("vscode_erlang") + +system_demo_toolchains() diff --git a/test/test_projects/xref/BUCK b/test/test_projects/xref/BUCK new file mode 100644 index 0000000000..bf44968f02 --- /dev/null +++ b/test/test_projects/xref/BUCK @@ -0,0 +1,25 @@ +oncall("vscode_erlang") + +erlang_application( + name = "app_a", + srcs = glob(["app_a/src/*.erl"]), + applications = [ + ":app_b", + ], + labels = ["user_application"], + version = "1.0.0", +) + +erlang_application( + name = "app_b", + srcs = glob(["app_b/src/*.erl"]), + labels = ["user_application"], + version = "1.0.0", +) + +erlang_application( + name = "app_c", + srcs = glob(["app_c/src/*.erl"]), + labels = ["user_application"], + version = "1.0.0", +) From 8e82f1cee4f54dbd3f7fc60a6a4185eb914775a0 Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Fri, 19 Dec 2025 04:09:00 -0800 Subject: [PATCH 136/142] Fix codegen_test test project for new buck root Summary: As title Reviewed By: TheGeorge Differential Revision: D89543670 fbshipit-source-id: 8d4a417a58950ad9f9d8877839561f407d0e9cc0 --- test/test_projects/codegen_test/.elp.toml | 7 +++---- test/test_projects/codegen_test/BUCK | 17 ++++++----------- test/test_projects/codegen_test/README.md | 11 +++++------ 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/test/test_projects/codegen_test/.elp.toml b/test/test_projects/codegen_test/.elp.toml index 52ec390ccb..f21fe1bc73 100644 --- a/test/test_projects/codegen_test/.elp.toml +++ b/test/test_projects/codegen_test/.elp.toml @@ -1,8 +1,7 @@ [buck] enabled = true -build_deps = true -included_targets = [ "fbcode//whatsapp/elp/test/test_projects/codegen_test:codegen_test_app" ] -source_root = "whatsapp/elp/test/test_projects/codegen_test" +build_deps = false +included_targets = ["root//codegen_test/..."] [eqwalizer] -enable_all = true +enable_all = false diff --git a/test/test_projects/codegen_test/BUCK b/test/test_projects/codegen_test/BUCK index 3530fca2ff..1df5f86eba 100644 --- a/test/test_projects/codegen_test/BUCK +++ b/test/test_projects/codegen_test/BUCK @@ -1,9 +1,7 @@ -load("@fbcode_macros//build_defs:native_rules.bzl", "buck_genrule") - oncall("vscode_erlang") # Code generation rule - generates Erlang modules from template files -buck_genrule( +genrule( name = "example_service_types_erl", srcs = [ "templates/example_service_types.erl", @@ -11,11 +9,10 @@ buck_genrule( outs = { "example_service_types.erl": ["example_service_types.erl"], }, - bash = "cp $SRCDIR/templates/example_service_types.erl $OUT/example_service_types.erl", - cmd_exe = "copy %SRCDIR%\\templates\\example_service_types.erl %OUT%\\example_service_types.erl", + cmd = "cp $SRCDIR/templates/example_service_types.erl $OUT/example_service_types.erl", ) -buck_genrule( +genrule( name = "example_service_client_erl", srcs = [ "templates/example_service_client.erl", @@ -23,11 +20,10 @@ buck_genrule( outs = { "example_service_client.erl": ["example_service_client.erl"], }, - bash = "cp $SRCDIR/templates/example_service_client.erl $OUT/example_service_client.erl", - cmd_exe = "copy %SRCDIR%\\templates\\example_service_client.erl %OUT%\\example_service_client.erl", + cmd = "cp $SRCDIR/templates/example_service_client.erl $OUT/example_service_client.erl", ) -buck_genrule( +genrule( name = "example_service_types_hrl", srcs = [ "templates/example_service_types.hrl", @@ -35,8 +31,7 @@ buck_genrule( outs = { "example_service_types.hrl": ["example_service_types.hrl"], }, - bash = "cp $SRCDIR/templates/example_service_types.hrl $OUT/example_service_types.hrl", - cmd_exe = "copy %SRCDIR%\\templates\\example_service_types.hrl %OUT%\\example_service_types.hrl", + cmd = "cp $SRCDIR/templates/example_service_types.hrl $OUT/example_service_types.hrl", ) # Erlang library containing only the generated code diff --git a/test/test_projects/codegen_test/README.md b/test/test_projects/codegen_test/README.md index 061fb1914c..1f73475d9d 100644 --- a/test/test_projects/codegen_test/README.md +++ b/test/test_projects/codegen_test/README.md @@ -1,7 +1,7 @@ # Code Generation Test Project This test project demonstrates Buck2-based code generation for Erlang -applications using `buck_genrule`. +applications using `genrule`. ## Project Structure @@ -37,10 +37,10 @@ copied to the output directory during the build. ### 2. Code Generation The code is generated automatically during the build process using a -`buck_genrule`: +`genrule`: ```python -buck_genrule( +genrule( name = "example_service_types_erl", srcs = [ "templates/example_service_types.erl", @@ -48,8 +48,7 @@ buck_genrule( outs = { "example_service_types.erl": ["example_service_types.erl"], }, - bash = "cp $SRCDIR/templates/example_service_types.erl $OUT/example_service_types.erl", - cmd_exe = "copy %SRCDIR%\\templates\\example_service_types.erl %OUT%\\example_service_types.erl", + cmd = "cp $SRCDIR/templates/example_service_types.erl $OUT/example_service_types.erl", ) ``` @@ -58,7 +57,7 @@ The genrule: - Takes the template file as input (`srcs`) - Defines named outputs using `outs` parameter (creates subtargets for each file) -- Uses `cp` (bash) or `copy` (cmd_exe) to copy template files to `$OUT` +- Uses `cp` command to copy template files to `$OUT` - Outputs files to `$OUT` directory in `buck-out/` ### 3. Build Integration From fc09ff94fdda5a6c9fbb3d49764dcd0c7767e76b Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Fri, 19 Dec 2025 07:43:01 -0800 Subject: [PATCH 137/142] 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 --- .github/workflows/ci.yml | 2 +- crates/elp/src/bin/main.rs | 13 +++++++++---- .../buck_bad_config/bxl_error_message_oss.stdout | 1 + crates/project_model/src/buck.rs | 4 +--- 4 files changed, 12 insertions(+), 8 deletions(-) create mode 100644 crates/elp/src/resources/test/buck_bad_config/bxl_error_message_oss.stdout diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2fcdf48405..e9980283f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -137,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\"' diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index df99eff987..721632fc01 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -970,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]/"); @@ -1111,7 +1113,7 @@ mod tests { "--to", tmp_file.clone(), "--project", - path_str + path_str.clone() ]; let (stdout, stderr, code) = elp(args); assert_eq!( @@ -1126,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]/"); @@ -1948,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, diff --git a/crates/elp/src/resources/test/buck_bad_config/bxl_error_message_oss.stdout b/crates/elp/src/resources/test/buck_bad_config/bxl_error_message_oss.stdout new file mode 100644 index 0000000000..f5225605a5 --- /dev/null +++ b/crates/elp/src/resources/test/buck_bad_config/bxl_error_message_oss.stdout @@ -0,0 +1 @@ +Project Initialisation Failed: invalid or missing buck 2 configuration diff --git a/crates/project_model/src/buck.rs b/crates/project_model/src/buck.rs index 96fc1eac89..32c6dcccb8 100644 --- a/crates/project_model/src/buck.rs +++ b/crates/project_model/src/buck.rs @@ -1643,9 +1643,7 @@ mod tests { assert_eq!(expected, actual) } - // TODO: enable when buck is properly set up on github project - // @fb-only: const BUCK_TESTS_ENABLED: bool = true; - const BUCK_TESTS_ENABLED: bool = false; // @oss-only + const BUCK_TESTS_ENABLED: bool = true; #[track_caller] fn check_buck_bxl_query(build_generated: bool, expect: Expect) { From 220acfd4f7cfa318c3207db8e5bdefa01eaffccc Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Fri, 19 Dec 2025 08:49:54 -0800 Subject: [PATCH 138/142] Back out "Allow project discovery for OTP" Summary: It breaks navigation to stdlib Reviewed By: TD5 Differential Revision: D89552844 fbshipit-source-id: 676af5f520d9189f98828b4364b0b1665531b245 --- crates/elp/src/project_loader.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/elp/src/project_loader.rs b/crates/elp/src/project_loader.rs index 0f13139201..103353e5db 100644 --- a/crates/elp/src/project_loader.rs +++ b/crates/elp/src/project_loader.rs @@ -100,11 +100,8 @@ impl ProjectLoader { ) -> Option<(ElpConfig, Result, ProjectManifest)> { let mut path_it = path; loop { - if let Some(value) = self.project_roots.get(path_it) { - return match value { - None => Some(self.load_manifest(path_it)), - Some(_) => None, - }; + if self.project_roots.contains_key(path_it) { + return None; } match path_it.parent() { Some(parent) => path_it = parent, From 5c7cfae09d3a89e430cc69a88371939dc2acad42 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Fri, 19 Dec 2025 08:58:10 -0800 Subject: [PATCH 139/142] 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 --- crates/project_model/src/buck.rs | 84 +++++++++++++++----------------- 1 file changed, 40 insertions(+), 44 deletions(-) diff --git a/crates/project_model/src/buck.rs b/crates/project_model/src/buck.rs index 32c6dcccb8..45c6b7f732 100644 --- a/crates/project_model/src/buck.rs +++ b/crates/project_model/src/buck.rs @@ -1643,57 +1643,53 @@ mod tests { assert_eq!(expected, actual) } - const BUCK_TESTS_ENABLED: bool = true; - #[track_caller] fn check_buck_bxl_query(build_generated: bool, expect: Expect) { - if BUCK_TESTS_ENABLED { - let buck_root = to_abs_path_buf(&std::env::current_dir().unwrap()).unwrap(); - // We only need buck_config to get the buck command, everything but the buck root is ignored. - let buck_config = BuckConfig { - config_path: None, - buck_root: Some(buck_root), - enabled: true, - deps_target: None, - deps_targets: vec![], - build_deps: false, - included_targets: vec![], - excluded_targets: vec![], - source_root: None, - test_application_labels: vec!["test_application".to_string()], - }; - let generated_args = if build_generated { - vec!["--build_generated_code", "true"] - } else { - vec![] - }; - let output = buck_config - .buck_command() - .arg("bxl") - .arg("prelude//erlang/elp.bxl:elp_config") - .arg("--") - .args(generated_args) - .arg("--included_targets") - .arg("root//buck_tests_2/auto_gen/...") - .output() - .unwrap(); - if !output.status.success() { - panic!("{output:#?}"); - } - let string = String::from_utf8(output.stdout).unwrap(); - let prelude_cell = get_prelude_cell(&buck_config).expect("could not get prelude"); - let string = string.replace(&prelude_cell, "/[prelude]/"); - - let to_replace = env!("CARGO_WORKSPACE_DIR"); - let string = string.replace(to_replace, "/[..]/"); - expect.assert_eq(&string); + let buck_root = to_abs_path_buf(&std::env::current_dir().unwrap()).unwrap(); + // We only need buck_config to get the buck command, everything but the buck root is ignored. + let buck_config = BuckConfig { + config_path: None, + buck_root: Some(buck_root), + enabled: true, + deps_target: None, + deps_targets: vec![], + build_deps: false, + included_targets: vec![], + excluded_targets: vec![], + source_root: None, + test_application_labels: vec!["test_application".to_string()], + }; + let generated_args = if build_generated { + vec!["--build_generated_code", "true"] + } else { + vec![] + }; + let output = buck_config + .buck_command() + .arg("bxl") + .arg("prelude//erlang/elp.bxl:elp_config") + .arg("--") + .args(generated_args) + .arg("--included_targets") + .arg("root//buck_tests_2/auto_gen/...") + .output() + .unwrap(); + if !output.status.success() { + panic!("{output:#?}"); } + let string = String::from_utf8(output.stdout).unwrap(); + let prelude_cell = get_prelude_cell(&buck_config).expect("could not get prelude"); + let string = string.replace(&prelude_cell, "/[prelude]/"); + + let to_replace = env!("CARGO_WORKSPACE_DIR"); + let string = string.replace(to_replace, "/[..]/"); + expect.assert_eq(&string); } #[test] #[ignore] fn build_info_buck_bxl_query() { - if BUCK_TESTS_ENABLED { + if cfg!(feature = "buck") { check_buck_bxl_query( false, expect![[r#" @@ -1859,7 +1855,7 @@ mod tests { #[test] #[ignore] fn build_info_buck_bxl_generated_query() { - if BUCK_TESTS_ENABLED { + if cfg!(feature = "buck") { // Note that there is now a value for `srcs` in the // "root//buck_tests_2/auto_gen/auto_gen_a:generated_srcs" // target From 6456f325c3a338169e4eed4d0a156db5baf1491f Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Fri, 19 Dec 2025 08:58:10 -0800 Subject: [PATCH 140/142] Ignore buck-out in test_projects Summary: So that version control systems do not try to publish the directory. Reviewed By: alanz Differential Revision: D89550389 fbshipit-source-id: d60646ddd4e8f0ae443673dac3c49efbac60dba7 --- test/test_projects/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_projects/.gitignore b/test/test_projects/.gitignore index b34578734c..2672c17cc4 100644 --- a/test/test_projects/.gitignore +++ b/test/test_projects/.gitignore @@ -3,3 +3,4 @@ *wa.build_info *_build/ *rebar.lock +buck-out/ From 4bba415bc1d863764c564ea8545aae98dcf02403 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Mon, 22 Dec 2025 09:17:24 -0800 Subject: [PATCH 141/142] Fix diagnostic name Summary: Missed during the original rename. Reviewed By: alanz Differential Revision: D89460380 fbshipit-source-id: 60446aa3f505d635ad5ddc842b2d420c24f0a9d2 --- crates/ide/src/diagnostics/old_edoc_syntax.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ide/src/diagnostics/old_edoc_syntax.rs b/crates/ide/src/diagnostics/old_edoc_syntax.rs index e8524b6e6e..da4c3f2430 100644 --- a/crates/ide/src/diagnostics/old_edoc_syntax.rs +++ b/crates/ide/src/diagnostics/old_edoc_syntax.rs @@ -8,7 +8,7 @@ * above-listed licenses. */ -// Diagnostic: edoc +// Diagnostic: old_edoc_syntax use elp_ide_assists::Assist; use elp_ide_assists::helpers; From 7a4eccf14dbf3df942e9d01e0eb0f74d419fbb32 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Tue, 23 Dec 2025 03:53:02 -0800 Subject: [PATCH 142/142] Add support for linter-specific excluded_apps 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 --- crates/elp/src/bin/main.rs | 12 ++ .../resources/test/linter_config/basic.stdout | 5 + crates/ide/src/diagnostics.rs | 119 ++++++++++++++++-- test/test_projects/linter_config/.elp.toml | 5 + .../linter_config/.elp_lint.toml | 5 + test/test_projects/linter_config/BUCK | 25 ++++ .../linter_config/app_a/src/app_a.app.src | 3 + .../linter_config/app_a/src/app_a.erl | 4 + .../linter_config/app_b/src/app_b.app.src | 3 + .../linter_config/app_b/src/app_b.erl | 4 + .../linter_config/app_c/src/app_c.app.src | 3 + .../linter_config/app_c/src/app_c.erl | 4 + test/test_projects/linter_config/rebar.config | 9 ++ 13 files changed, 188 insertions(+), 13 deletions(-) create mode 100644 crates/elp/src/resources/test/linter_config/basic.stdout create mode 100644 test/test_projects/linter_config/.elp.toml create mode 100644 test/test_projects/linter_config/.elp_lint.toml create mode 100644 test/test_projects/linter_config/BUCK create mode 100644 test/test_projects/linter_config/app_a/src/app_a.app.src create mode 100644 test/test_projects/linter_config/app_a/src/app_a.erl create mode 100644 test/test_projects/linter_config/app_b/src/app_b.app.src create mode 100644 test/test_projects/linter_config/app_b/src/app_b.erl create mode 100644 test/test_projects/linter_config/app_c/src/app_c.app.src create mode 100644 test/test_projects/linter_config/app_c/src/app_c.erl create mode 100644 test/test_projects/linter_config/rebar.config diff --git a/crates/elp/src/bin/main.rs b/crates/elp/src/bin/main.rs index 721632fc01..56535e4492 100644 --- a/crates/elp/src/bin/main.rs +++ b/crates/elp/src/bin/main.rs @@ -2263,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) { diff --git a/crates/elp/src/resources/test/linter_config/basic.stdout b/crates/elp/src/resources/test/linter_config/basic.stdout new file mode 100644 index 0000000000..946fd7dc9a --- /dev/null +++ b/crates/elp/src/resources/test/linter_config/basic.stdout @@ -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) \ No newline at end of file diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 7b23da4461..eb6bae611b 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -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; @@ -550,12 +551,37 @@ pub(crate) trait Linter { } } +fn should_process_app( + app_name: &Option, + 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, 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()) @@ -1218,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> { + self.linters + .get(diagnostic_code) + .and_then(|c| c.exclude_apps.clone()) + } + pub fn get_function_call_linter_config( &self, diagnostic_code: &DiagnosticCode, @@ -1341,6 +1377,7 @@ pub struct LinterConfig { pub include_tests: Option, pub include_generated: Option, pub experimental: Option, + pub exclude_apps: Option>, #[serde(flatten)] pub config: Option, } @@ -1361,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, } } @@ -1555,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 @@ -1563,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 { @@ -1613,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 = 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 = 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); } } }); @@ -1734,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 @@ -2300,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)| { ( @@ -3995,6 +4039,7 @@ main(X) -> include_tests: None, include_generated: None, experimental: None, + exclude_apps: None, config: None, }, ); @@ -4037,6 +4082,7 @@ main(X) -> include_tests: Some(true), include_generated: None, experimental: None, + exclude_apps: None, config: None, }, ); @@ -4078,6 +4124,7 @@ main(X) -> include_tests: None, include_generated: Some(true), experimental: None, + exclude_apps: None, config: None, }, ); @@ -4120,6 +4167,7 @@ main(X) -> include_tests: None, include_generated: None, experimental: Some(true), + exclude_apps: None, config: None, }, ); @@ -4164,6 +4212,7 @@ main(X) -> include_tests: None, include_generated: None, experimental: None, + exclude_apps: None, config: None, }, ); @@ -4194,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() @@ -4241,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")]), @@ -4273,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")]), @@ -4290,6 +4382,7 @@ main(X) -> include_tests: None, include_generated: Some(true), experimental: None, + exclude_apps: None, config: None, }, ); diff --git a/test/test_projects/linter_config/.elp.toml b/test/test_projects/linter_config/.elp.toml new file mode 100644 index 0000000000..c490cc155e --- /dev/null +++ b/test/test_projects/linter_config/.elp.toml @@ -0,0 +1,5 @@ +[buck] +enabled = true +build_deps = false +included_targets = [ "root//linter_config/..." ] +source_root = "linter_config" diff --git a/test/test_projects/linter_config/.elp_lint.toml b/test/test_projects/linter_config/.elp_lint.toml new file mode 100644 index 0000000000..47886dd507 --- /dev/null +++ b/test/test_projects/linter_config/.elp_lint.toml @@ -0,0 +1,5 @@ +[linters.L1260] # Unused record, produced by the Erlang Service +exclude_apps = ["app_b", "app_c"] + +[linters.unused_macro] +exclude_apps = ["app_c"] diff --git a/test/test_projects/linter_config/BUCK b/test/test_projects/linter_config/BUCK new file mode 100644 index 0000000000..c1101daea2 --- /dev/null +++ b/test/test_projects/linter_config/BUCK @@ -0,0 +1,25 @@ +oncall("vscode_erlang") + +erlang_application( + name = "app_a", + srcs = glob(["app_a/src/*.erl"]), + app_src = "app_a/src/app_a.app.src", + labels = ["user_application"], + version = "1.0.0", +) + +erlang_application( + name = "app_b", + srcs = glob(["app_b/src/*.erl"]), + app_src = "app_b/src/app_b.app.src", + labels = ["user_application"], + version = "1.0.0", +) + +erlang_application( + name = "app_c", + srcs = glob(["app_c/src/*.erl"]), + app_src = "app_c/src/app_c.app.src", + labels = ["user_application"], + version = "1.0.0", +) diff --git a/test/test_projects/linter_config/app_a/src/app_a.app.src b/test/test_projects/linter_config/app_a/src/app_a.app.src new file mode 100644 index 0000000000..1381435a46 --- /dev/null +++ b/test/test_projects/linter_config/app_a/src/app_a.app.src @@ -0,0 +1,3 @@ +{application, app_a, + [{description, "example app A"}, {vsn, "inplace"}, {applications, [kernel, stdlib]}] +}. diff --git a/test/test_projects/linter_config/app_a/src/app_a.erl b/test/test_projects/linter_config/app_a/src/app_a.erl new file mode 100644 index 0000000000..c374c0b518 --- /dev/null +++ b/test/test_projects/linter_config/app_a/src/app_a.erl @@ -0,0 +1,4 @@ +-module(app_a). + +-define(MACRO_A, a). +-record(rec_a, {a :: atom()}). diff --git a/test/test_projects/linter_config/app_b/src/app_b.app.src b/test/test_projects/linter_config/app_b/src/app_b.app.src new file mode 100644 index 0000000000..4112b4129f --- /dev/null +++ b/test/test_projects/linter_config/app_b/src/app_b.app.src @@ -0,0 +1,3 @@ +{application, app_b, + [{description, "example app B"}, {vsn, "inplace"}, {applications, [kernel, stdlib]}] +}. diff --git a/test/test_projects/linter_config/app_b/src/app_b.erl b/test/test_projects/linter_config/app_b/src/app_b.erl new file mode 100644 index 0000000000..ca7ecf985b --- /dev/null +++ b/test/test_projects/linter_config/app_b/src/app_b.erl @@ -0,0 +1,4 @@ +-module(app_b). + +-define(MACRO_B, b). +-record(rec_b, {b :: atom()}). diff --git a/test/test_projects/linter_config/app_c/src/app_c.app.src b/test/test_projects/linter_config/app_c/src/app_c.app.src new file mode 100644 index 0000000000..c278bbce23 --- /dev/null +++ b/test/test_projects/linter_config/app_c/src/app_c.app.src @@ -0,0 +1,3 @@ +{application, app_c, + [{description, "example app C"}, {vsn, "inplace"}, {applications, [kernel, stdlib]}] +}. diff --git a/test/test_projects/linter_config/app_c/src/app_c.erl b/test/test_projects/linter_config/app_c/src/app_c.erl new file mode 100644 index 0000000000..57573554b6 --- /dev/null +++ b/test/test_projects/linter_config/app_c/src/app_c.erl @@ -0,0 +1,4 @@ +-module(app_c). + +-define(MACRO_C, c). +-record(rec_c, {c :: atom()}). diff --git a/test/test_projects/linter_config/rebar.config b/test/test_projects/linter_config/rebar.config new file mode 100644 index 0000000000..d508bd24de --- /dev/null +++ b/test/test_projects/linter_config/rebar.config @@ -0,0 +1,9 @@ +{checkouts_dir, ["."]}. +{project_app_dirs, [ + "app_a", + "app_b", + "app_c" +]}. + +{erl_opts, [debug_info]}. +{deps, []}.