Skip to content

Development

How to contribute to Roughly

Client Setup

  • Run cd client && bun install. This installs all necessary npm modules in the client
  • Press Ctrl+Shift+B in VS Code to start compiling the client in watch mode.
  • Switch to the Run and Debug View in the Sidebar (Ctrl+Shift+D).
  • Select Launch Client from the drop down (if it is not already).
  • Press ▷ to run the launch config (F5).
  • In the Extension Development Host instance of VSCode, open a document with a .R extension.

Words of warning

The launch.json contains a setting:

"autoAttachChildProcesses": true,

For me this led to the issue that the language server wasn’t spawned because I had CodeLLDB from nixpkgs installed.

Roadmap

  • static analysis
    • type checking
    • name binding
      • are all names defined
      • unused names (variables & parameters)
  • variable renaming
  • inlay hints

Formatting

Comments in expressions

The main challenge in formatting code is handling comments because they can appear at any location. This is particualar hard to handle these expressions:

  • if expression
  • for expression
  • repeat statment
  • while expression
  • binary operator
  • extract operator

Example of if expressions

For example, in an if expression comments can appear at any location (numerated):

if
# 1
( # open
# 2
a && b # condition
# 3
) # close
# 4
{
y
}
# 5
else
# 6
{
4
}

With a structured AST, we might typically could write our code in a concise functional style:

let condition = fmt(field("condition")?);
let consequence = fmt(field("consequence")?);
let alternative = fmt(field("alternative")?);
format!("if({condition}) {{ {consequence} }} else {{ {alternative} }}");

However, due to the arbitrary placement of comments, we must adopt a more imperative style. This approach preserves comments but somewhat harder to comprehend:

let mut out = String::new();
let mut cursor = node.walk();
if cursor.goto_first_child() {
loop {
match cursor.field_name() {
None => match child.kind() {
"if" => out.push_str("if"),
"else" => ...,
"comment" => ...,
_ => unreachable!(),
},
Some(field_name) => match field_name {
"open" => ...,
"condition" => ...,
"close" => ...,
"consequence" | "alternative" => ...,
_ => unreachable!(),
},
};
if !cursor.goto_next_sibling() {
cursor.goto_parent();
break;
}
}
};
out

Tree-sitter: How to handle required fields

  • For formatting we do an initial check if there are no errors, so we can safely assume that all required fields are present
  • For type-checking we should do the same
  • For checks/diagnostics that run while typing (syntax & fast), we cannot make any assumption
  • Same is true for index. It should still be possible to index a file, while there are parse errors

tree-sitter-r vs R parser

No default for parameter

Accept by tree-sitter-r but rejected by R.

function(parameter =) {}

See https://github.com/r-lib/tree-sitter-r/issues/161

line break after else

This is valid R but cannot be parsed by tree-sitter-r

if (TRUE) {
1
} else
{
2
}

Two line breaks after extract_operator

This is valid R but cannot be parsed by tree-sitter-r

foo$
bar

See https://github.com/r-lib/tree-sitter-r/issues/166

Linting ideas

  • Empty loops: for, while, repeat
  • unused variables

Type checking

References

Formatting

Type Checker

Out of order issue