Skip to content

Commit

Permalink
eqwalizer assist for non exported type
Browse files Browse the repository at this point in the history
Summary: When eqwalizer reports that a spec contains a non exported type, add a quick fix to the diagnostic to export it

Reviewed By: robertoaloi

Differential Revision: D56008209

fbshipit-source-id: c779dafbea629b8f0668449dbf50581956af4b5b
  • Loading branch information
alanz authored and facebook-github-bot committed Apr 11, 2024
1 parent 873deed commit 0980351
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 9 deletions.
2 changes: 2 additions & 0 deletions crates/ide/src/diagnostics/eqwalizer_assists.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use hir::Semantic;
use super::Diagnostic;

mod expected_type;
mod unexported_type;

pub fn add_eqwalizer_assists(
sema: &Semantic,
Expand All @@ -22,4 +23,5 @@ pub fn add_eqwalizer_assists(
diagnostic: &mut Diagnostic,
) {
expected_type::expected_type(sema, file_id, d, diagnostic);
unexported_type::unexported_type(sema, file_id, d, diagnostic);
}
124 changes: 124 additions & 0 deletions crates/ide/src/diagnostics/eqwalizer_assists/unexported_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* 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
* License, Version 2.0 found in the LICENSE-APACHE file in the root directory
* of this source tree.
*/

use elp_eqwalizer::ast::RemoteId;
use elp_ide_assists::helpers;
use elp_ide_assists::helpers::ExportForm;
use elp_ide_db::elp_base_db::FileId;
use elp_ide_db::source_change::SourceChangeBuilder;
use elp_ide_db::EqwalizerDiagnostic;
use elp_types_db::eqwalizer::invalid_diagnostics::Invalid;
use elp_types_db::eqwalizer::invalid_diagnostics::NonExportedId;
use elp_types_db::eqwalizer::StructuredDiagnostic;
use hir::sema::to_def::resolve_module_name;
use hir::Name;
use hir::NameArity;
use hir::Semantic;

use crate::diagnostics::Diagnostic;
use crate::fix;

pub fn unexported_type(
sema: &Semantic,
file_id: FileId,
d: &EqwalizerDiagnostic,
diagnostic: &mut Diagnostic,
) {
if let Some(StructuredDiagnostic::InvalidForm(Invalid::NonExportedId(NonExportedId {
location: _,
id: RemoteId {
module,
name,
arity,
},
}))) = &d.diagnostic
{
if let Some(module) = resolve_module_name(sema, file_id, module) {
let name = NameArity::new(Name::from_erlang_service(name), *arity);
if let Some(type_alias) = sema
.db
.def_map(module.file.file_id)
.get_types()
.get(&name)
.cloned()
{
if !type_alias.exported {
let mut builder = SourceChangeBuilder::new(module.file.file_id);
helpers::ExportBuilder::new(
sema,
module.file.file_id,
ExportForm::Types,
&[name.clone()],
&mut builder,
)
.finish();
let edit = builder.finish();
diagnostic.add_fix(fix(
"export_type",
format!("Export the type `{name}`").as_str(),
edit,
diagnostic.range,
))
}
}
}
}
}

#[cfg(test)]
mod tests {
use crate::tests::check_diagnostics;
use crate::tests::check_fix;

#[test]
fn unexported_type() {
check_diagnostics(
r#"
//- eqwalizer
//- /play/src/bar.erl app:play
-module(bar).
-spec baz() -> other:a_type().
%% ^^^^^^^^^^^^^^ 💡 error: eqwalizer: non_exported_id
baz() -> ok.
//- /play/src/other.erl app:play
-module(other).
-type a_type() :: ok.
"#,
)
}

#[test]
fn fix_unexported_type() {
check_fix(
r#"
//- eqwalizer
//- /play/src/other.erl app:play
-module(other).
-type a_type() :: ok.
//- /play/src/bar.erl app:play
-module(bar).
-spec baz() -> other:a_~type().
%% ^^^^^^^^^^^^^^ 💡 error: eqwalizer: unknown_id
baz() -> ok.
"#,
r#"
-module(other).
-export_type([a_type/0]).
-type a_type() :: ok.
"#,
)
}
}
18 changes: 9 additions & 9 deletions crates/ide_assists/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -544,19 +544,19 @@ fn add_to_compile_attribute(
// ---------------------------------------------------------------------

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub(crate) enum ExportListPosition {
pub enum ExportListPosition {
First,
Last,
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub(crate) enum ExportForm {
pub enum ExportForm {
Functions,
#[allow(unused)] // Used in next diff
Types,
}

pub(crate) struct ExportBuilder<'a> {
pub struct ExportBuilder<'a> {
sema: &'a Semantic<'a>,
file_id: FileId,
export_form: ExportForm,
Expand All @@ -571,7 +571,7 @@ pub(crate) struct ExportBuilder<'a> {
}

impl<'a> ExportBuilder<'a> {
pub(crate) fn new(
pub fn new(
sema: &'a Semantic<'a>,
file_id: FileId,
export_form: ExportForm,
Expand All @@ -591,27 +591,27 @@ impl<'a> ExportBuilder<'a> {
}
}

pub(crate) fn group_with(mut self, name: NameArity) -> ExportBuilder<'a> {
pub fn group_with(mut self, name: NameArity) -> ExportBuilder<'a> {
self.group_with = Some(name);
self
}

pub(crate) fn export_list_pos(mut self, pos: ExportListPosition) -> ExportBuilder<'a> {
pub fn export_list_pos(mut self, pos: ExportListPosition) -> ExportBuilder<'a> {
self.export_list_pos = pos;
self
}

pub(crate) fn insert_at(mut self, location: TextSize) -> ExportBuilder<'a> {
pub fn insert_at(mut self, location: TextSize) -> ExportBuilder<'a> {
self.insert_at = Some(location);
self
}

pub(crate) fn with_comment(mut self, comment: String) -> ExportBuilder<'a> {
pub fn with_comment(mut self, comment: String) -> ExportBuilder<'a> {
self.with_comment = Some(comment);
self
}

pub(crate) fn finish(&mut self) {
pub fn finish(&mut self) {
let source = self.sema.parse(self.file_id).value;
let form_list = self.sema.form_list(self.file_id);
let export_text = self
Expand Down

0 comments on commit 0980351

Please sign in to comment.