+4
compiler-core/src/ast.rs
+4
compiler-core/src/ast.rs
···
2276
2276
byte_index >= self.start && byte_index <= self.end
2277
2277
}
2278
2278
2279
+
pub fn contains_span(&self, span: SrcSpan) -> bool {
2280
+
self.contains(span.start) && self.contains(span.end)
2281
+
}
2282
+
2279
2283
/// Merges two spans into a new one that starts at the start of the smaller
2280
2284
/// one and ends at the end of the bigger one. For example:
2281
2285
///
+187
compiler-core/src/language_server/code_action.rs
+187
compiler-core/src/language_server/code_action.rs
···
8411
8411
self.arguments_and_omitted_labels = Some(omitted_labels);
8412
8412
}
8413
8413
}
8414
+
8415
+
/// Code action to extract selected code into a separate function.
8416
+
pub struct ExtractFunction<'a> {
8417
+
module: &'a Module,
8418
+
params: &'a CodeActionParams,
8419
+
edits: TextEdits<'a>,
8420
+
extract: Extract<'a>,
8421
+
function_end_position: Option<u32>,
8422
+
}
8423
+
8424
+
#[derive(Debug)]
8425
+
enum Extract<'a> {
8426
+
None,
8427
+
Expression(&'a TypedExpr),
8428
+
Statements(Vec<&'a TypedStatement>),
8429
+
}
8430
+
8431
+
impl<'a> ExtractFunction<'a> {
8432
+
pub fn new(
8433
+
module: &'a Module,
8434
+
line_numbers: &'a LineNumbers,
8435
+
params: &'a CodeActionParams,
8436
+
) -> Self {
8437
+
Self {
8438
+
module,
8439
+
params,
8440
+
edits: TextEdits::new(line_numbers),
8441
+
extract: Extract::None,
8442
+
function_end_position: None,
8443
+
}
8444
+
}
8445
+
8446
+
pub fn code_actions(mut self) -> Vec<CodeAction> {
8447
+
if self.params.range.start == self.params.range.end {
8448
+
return Vec::new();
8449
+
}
8450
+
8451
+
self.visit_typed_module(&self.module.ast);
8452
+
8453
+
let Some(end) = self.function_end_position else {
8454
+
return Vec::new();
8455
+
};
8456
+
8457
+
match std::mem::replace(&mut self.extract, Extract::None) {
8458
+
Extract::None => return Vec::new(),
8459
+
Extract::Expression(expression) => self.extract_expression(expression, end),
8460
+
Extract::Statements(statements) => self.extract_statements(statements, end),
8461
+
}
8462
+
8463
+
let mut action = Vec::with_capacity(1);
8464
+
CodeActionBuilder::new("Extract function")
8465
+
.kind(CodeActionKind::REFACTOR_EXTRACT)
8466
+
.changes(self.params.text_document.uri.clone(), self.edits.edits)
8467
+
.preferred(false)
8468
+
.push_to(&mut action);
8469
+
action
8470
+
}
8471
+
8472
+
fn extract_expression(&mut self, expression: &TypedExpr, function_end: u32) {
8473
+
let referenced_variables = referenced_variables(expression);
8474
+
8475
+
let expression_code = code_at(self.module, expression.location());
8476
+
8477
+
let arguments = referenced_variables.iter().map(|(name, _)| name).join(", ");
8478
+
let call = format!("function({arguments})");
8479
+
self.edits.replace(expression.location(), call);
8480
+
8481
+
let mut printer = Printer::new(&self.module.ast.names);
8482
+
8483
+
let parameters = referenced_variables
8484
+
.iter()
8485
+
.map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_)))
8486
+
.join(", ");
8487
+
let return_type = printer.print_type(&expression.type_());
8488
+
8489
+
let function = format!(
8490
+
"\n\nfn function({parameters}) -> {return_type} {{
8491
+
{expression_code}
8492
+
}}"
8493
+
);
8494
+
8495
+
self.edits.insert(function_end, function);
8496
+
}
8497
+
8498
+
fn extract_statements(&mut self, statements: Vec<&TypedStatement>, function_end: u32) {
8499
+
todo!("Implement for statements")
8500
+
}
8501
+
}
8502
+
8503
+
impl<'ast> ast::visit::Visit<'ast> for ExtractFunction<'ast> {
8504
+
fn visit_typed_function(&mut self, function: &'ast ast::TypedFunction) {
8505
+
let range = self.edits.src_span_to_lsp_range(function.full_location());
8506
+
8507
+
if within(self.params.range, range) {
8508
+
self.function_end_position = Some(function.end_position);
8509
+
8510
+
ast::visit::visit_typed_function(self, function);
8511
+
}
8512
+
}
8513
+
8514
+
fn visit_typed_expr(&mut self, expression: &'ast TypedExpr) {
8515
+
match &self.extract {
8516
+
Extract::None => {
8517
+
let range = self.edits.src_span_to_lsp_range(expression.location());
8518
+
8519
+
if within(range, self.params.range) {
8520
+
self.extract = Extract::Expression(expression);
8521
+
return;
8522
+
}
8523
+
}
8524
+
Extract::Expression(_) | Extract::Statements(_) => {}
8525
+
}
8526
+
ast::visit::visit_typed_expr(self, expression);
8527
+
}
8528
+
8529
+
fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) {
8530
+
let range = self.edits.src_span_to_lsp_range(statement.location());
8531
+
if within(range, self.params.range) {
8532
+
match &mut self.extract {
8533
+
Extract::None => {
8534
+
self.extract = Extract::Statements(vec![statement]);
8535
+
}
8536
+
Extract::Expression(expression) => {
8537
+
if expression.location().contains_span(statement.location()) {
8538
+
return;
8539
+
}
8540
+
8541
+
self.extract = Extract::Statements(vec![statement]);
8542
+
}
8543
+
Extract::Statements(statements) => {
8544
+
statements.push(statement);
8545
+
}
8546
+
}
8547
+
} else {
8548
+
ast::visit::visit_typed_statement(self, statement);
8549
+
}
8550
+
}
8551
+
}
8552
+
8553
+
fn referenced_variables(expression: &TypedExpr) -> Vec<(EcoString, Arc<Type>)> {
8554
+
let mut references = ReferencedVariables {
8555
+
variables: Vec::new(),
8556
+
defined_variables: HashSet::new(),
8557
+
};
8558
+
references.visit_typed_expr(expression);
8559
+
references.variables
8560
+
}
8561
+
8562
+
struct ReferencedVariables {
8563
+
variables: Vec<(EcoString, Arc<Type>)>,
8564
+
defined_variables: HashSet<EcoString>,
8565
+
}
8566
+
8567
+
impl ReferencedVariables {
8568
+
fn register(&mut self, name: &EcoString, type_: &Arc<Type>) {
8569
+
if self.defined_variables.contains(name) {
8570
+
return;
8571
+
}
8572
+
8573
+
if !self
8574
+
.variables
8575
+
.iter()
8576
+
.any(|(variable_name, _)| variable_name == name)
8577
+
{
8578
+
self.variables.push((name.clone(), type_.clone()))
8579
+
}
8580
+
}
8581
+
}
8582
+
8583
+
impl<'ast> ast::visit::Visit<'ast> for ReferencedVariables {
8584
+
fn visit_typed_expr_var(
8585
+
&mut self,
8586
+
_location: &'ast SrcSpan,
8587
+
constructor: &'ast ValueConstructor,
8588
+
name: &'ast EcoString,
8589
+
) {
8590
+
match &constructor.variant {
8591
+
type_::ValueConstructorVariant::LocalVariable { .. } => {
8592
+
self.register(name, &constructor.type_);
8593
+
}
8594
+
type_::ValueConstructorVariant::ModuleConstant { .. }
8595
+
| type_::ValueConstructorVariant::LocalConstant { .. }
8596
+
| type_::ValueConstructorVariant::ModuleFn { .. }
8597
+
| type_::ValueConstructorVariant::Record { .. } => {}
8598
+
}
8599
+
}
8600
+
}
+3
-2
compiler-core/src/language_server/engine.rs
+3
-2
compiler-core/src/language_server/engine.rs
···
13
13
io::{BeamCompiler, CommandExecutor, FileSystemReader, FileSystemWriter},
14
14
language_server::{
15
15
code_action::{
16
-
AddOmittedLabels, CollapseNestedCase, RemoveBlock, RemovePrivateOpaque,
17
-
RemoveUnreachableBranches,
16
+
AddOmittedLabels, CollapseNestedCase, ExtractFunction, RemoveBlock,
17
+
RemovePrivateOpaque, RemoveUnreachableBranches,
18
18
},
19
19
compiler::LspProjectCompiler,
20
20
files::FileSystemProxy,
···
450
450
actions.extend(WrapInBlock::new(module, &lines, ¶ms).code_actions());
451
451
actions.extend(RemoveBlock::new(module, &lines, ¶ms).code_actions());
452
452
actions.extend(RemovePrivateOpaque::new(module, &lines, ¶ms).code_actions());
453
+
actions.extend(ExtractFunction::new(module, &lines, ¶ms).code_actions());
453
454
GenerateDynamicDecoder::new(module, &lines, ¶ms, &mut actions).code_actions();
454
455
GenerateJsonEncoder::new(
455
456
module,
+21
compiler-core/src/language_server/tests/action.rs
+21
compiler-core/src/language_server/tests/action.rs
···
135
135
const COLLAPSE_NESTED_CASE: &str = "Collapse nested case";
136
136
const REMOVE_UNREACHABLE_BRANCHES: &str = "Remove unreachable branches";
137
137
const ADD_OMITTED_LABELS: &str = "Add omitted labels";
138
+
const EXTRACT_FUNCTION: &str = "Extract function";
138
139
139
140
macro_rules! assert_code_action {
140
141
($title:expr, $code:literal, $range:expr $(,)?) => {
···
10227
10228
find_position_of("labelled").to_selection(),
10228
10229
);
10229
10230
}
10231
+
10232
+
#[test]
10233
+
fn extract_function() {
10234
+
assert_code_action!(
10235
+
EXTRACT_FUNCTION,
10236
+
"
10237
+
pub fn do_things(a, b) {
10238
+
let result = {
10239
+
let a = 10 + a
10240
+
let b = 10 + b
10241
+
a * b
10242
+
}
10243
+
result + 3
10244
+
}
10245
+
",
10246
+
find_position_of("{")
10247
+
.nth_occurrence(2)
10248
+
.select_until(find_position_of("}\n").under_char('\n'))
10249
+
);
10250
+
}
+35
compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__extract_function.snap
+35
compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__extract_function.snap
···
1
+
---
2
+
source: compiler-core/src/language_server/tests/action.rs
3
+
expression: "\npub fn do_things(a, b) {\n let result = {\n let a = 10 + a\n let b = 10 + b\n a * b\n }\n result + 3\n}\n"
4
+
---
5
+
----- BEFORE ACTION
6
+
7
+
pub fn do_things(a, b) {
8
+
let result = {
9
+
▔
10
+
let a = 10 + a
11
+
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
12
+
let b = 10 + b
13
+
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
14
+
a * b
15
+
▔▔▔▔▔▔▔▔▔
16
+
}
17
+
▔▔▔
18
+
result + 3
19
+
}
20
+
21
+
22
+
----- AFTER ACTION
23
+
24
+
pub fn do_things(a, b) {
25
+
let result = function(a, b)
26
+
result + 3
27
+
}
28
+
29
+
fn function(a: Int, b: Int) -> Int {
30
+
{
31
+
let a = 10 + a
32
+
let b = 10 + b
33
+
a * b
34
+
}
35
+
}