Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Format source code #1541

Merged
merged 33 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ce83ef6
add goto-references, goto-implementations, goto-type-definitions
chioni16 Aug 16, 2023
83f6d2d
add test cases
chioni16 Aug 24, 2023
e842c0b
add type definitions for enum variants + pr suggestions
chioni16 Sep 4, 2023
1c21bd0
use one mutex for both types and definitions
chioni16 Sep 4, 2023
9fe1c43
Merge branch 'main' of https://github.com/hyperledger/solang into got…
chioni16 Sep 4, 2023
87d35db
rename
chioni16 Sep 5, 2023
a2bcd00
cargo fmt
chioni16 Sep 8, 2023
5754aa7
changes to AST
chioni16 Sep 11, 2023
93f8f4e
add goto-declarations functionality
chioni16 Sep 11, 2023
2d5bd1d
filter declarations to include just the methods from parent contracts
chioni16 Sep 11, 2023
efafe91
Merge branch 'main' into rename
chioni16 Sep 15, 2023
53d2a84
add doc comments
chioni16 Sep 15, 2023
68d6d85
add tests
chioni16 Sep 15, 2023
439e4c9
add document formatting
chioni16 Sep 16, 2023
f9e7616
Merge branch 'rename' into declarations
chioni16 Sep 16, 2023
b40e300
refactoring + comments
chioni16 Sep 17, 2023
dd29b58
add tests
chioni16 Sep 17, 2023
8ffad93
separate config file for forge-fmt from foundry-config
chioni16 Sep 17, 2023
760d70d
error handling + comments
chioni16 Sep 17, 2023
8230873
set MSRV to 1.70
chioni16 Sep 19, 2023
53301ac
add SPDX licence to the newly added files
chioni16 Sep 19, 2023
47bab7d
add comments for the virtual_functions field in Contract struct
chioni16 Sep 19, 2023
430a078
Merge branch 'main' of https://github.com/hyperledger/solang into dec…
chioni16 Sep 22, 2023
6561dd7
add forge-fmt dependency
chioni16 Sep 22, 2023
b7e6d51
change the API exposed by Builder
chioni16 Sep 25, 2023
cebf464
change to doc comments
chioni16 Sep 25, 2023
2638b4c
inline builder build
chioni16 Sep 26, 2023
a7c2ec8
Merge branch 'main' of https://github.com/hyperledger/solang into dec…
chioni16 Sep 29, 2023
12b83fb
add tests
chioni16 Sep 29, 2023
a668046
Merge branch 'declarations' into format
chioni16 Sep 29, 2023
9033c11
update rust version to 1.72.0
chioni16 Oct 1, 2023
54b5679
Merge branch 'update-rust-version' into format
chioni16 Oct 1, 2023
824bad2
Merge branch 'main' into format
seanyoung Oct 2, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ contract-build = { version = "3.0.1", optional = true }
primitive-types = { version = "0.12", features = ["codec"] }
normalize-path = "0.2.1"
bitflags = "2.3.3"
forge-fmt = "0.2.0"

[dev-dependencies]
num-derive = "0.4"
Expand Down
85 changes: 79 additions & 6 deletions src/bin/languageserver/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: Apache-2.0

use forge_fmt::{format, parse, FormatterConfig};
use itertools::Itertools;
use num_traits::ToPrimitive;
use rust_lapper::{Interval, Lapper};
Expand Down Expand Up @@ -34,12 +35,12 @@ use tower_lsp::{
DiagnosticRelatedInformation, DiagnosticSeverity, DidChangeConfigurationParams,
DidChangeTextDocumentParams, DidChangeWatchedFilesParams, DidChangeWorkspaceFoldersParams,
DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams,
ExecuteCommandOptions, ExecuteCommandParams, GotoDefinitionParams, GotoDefinitionResponse,
Hover, HoverContents, HoverParams, HoverProviderCapability,
ImplementationProviderCapability, InitializeParams, InitializeResult, InitializedParams,
Location, MarkedString, MessageType, OneOf, Position, Range, ReferenceParams, RenameParams,
ServerCapabilities, SignatureHelpOptions, TextDocumentContentChangeEvent,
TextDocumentSyncCapability, TextDocumentSyncKind, TextEdit,
DocumentFormattingParams, ExecuteCommandOptions, ExecuteCommandParams,
GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams,
HoverProviderCapability, ImplementationProviderCapability, InitializeParams,
InitializeResult, InitializedParams, Location, MarkedString, MessageType, OneOf, Position,
Range, ReferenceParams, RenameParams, ServerCapabilities, SignatureHelpOptions,
TextDocumentContentChangeEvent, TextDocumentSyncCapability, TextDocumentSyncKind, TextEdit,
TypeDefinitionProviderCapability, Url, WorkspaceEdit, WorkspaceFoldersServerCapabilities,
WorkspaceServerCapabilities,
},
Expand Down Expand Up @@ -1862,6 +1863,7 @@ impl LanguageServer for SolangServer {
declaration_provider: Some(DeclarationCapability::Simple(true)),
references_provider: Some(OneOf::Left(true)),
rename_provider: Some(OneOf::Left(true)),
document_formatting_provider: Some(OneOf::Left(true)),
..ServerCapabilities::default()
},
})
Expand Down Expand Up @@ -2311,6 +2313,77 @@ impl LanguageServer for SolangServer {

Ok(Some(WorkspaceEdit::new(ws)))
}

/// Called when "Format Document" is called by the user on the client side.
///
/// Expected to return the formatted version of source code present in the file on which this method was triggered.
///
/// ### Arguments
/// * `DocumentFormattingParams`
/// * provides the name of the file whose code is to be formatted.
/// * provides options that help configure how the file is formatted.
///
/// ### Edge cases
/// * Returns `Err` when
/// * an invalid file path is received.
/// * reading the file fails.
/// * parsing the file fails.
/// * formatting the file fails.
async fn formatting(&self, params: DocumentFormattingParams) -> Result<Option<Vec<TextEdit>>> {
// get parse tree for the input file
let uri = params.text_document.uri;
let source_path = uri.to_file_path().map_err(|_| Error {
code: ErrorCode::InvalidRequest,
message: format!("Received invalid URI: {uri}").into(),
data: None,
})?;
let source = std::fs::read_to_string(source_path).map_err(|err| Error {
code: ErrorCode::InternalError,
message: format!("Failed to read file: {uri}").into(),
data: Some(Value::String(format!("{:?}", err))),
})?;
let source_parsed = parse(&source).map_err(|err| {
let err = err
.into_iter()
.map(|e| Value::String(e.message))
.collect::<Vec<_>>();
Error {
code: ErrorCode::InternalError,
message: format!("Failed to parse file: {uri}").into(),
data: Some(Value::Array(err)),
}
})?;

// get the formatted text
let config = FormatterConfig {
line_length: 80,
tab_width: params.options.tab_size as _,
..Default::default()
};
let mut source_formatted = String::new();
format(&mut source_formatted, source_parsed, config).map_err(|err| Error {
code: ErrorCode::InternalError,
message: format!("Failed to format file: {uri}").into(),
data: Some(Value::String(format!("{:?}", err))),
})?;

// create a `TextEdit` instance that replaces the contents of the file with the formatted text
let text_edit = TextEdit {
range: Range {
start: Position {
line: 0,
character: 0,
},
end: Position {
line: u32::max_value(),
character: u32::max_value(),
},
},
new_text: source_formatted,
};

Ok(Some(vec![text_edit]))
}
}

/// Calculate the line and column from the Loc offset received from the parser
Expand Down
5 changes: 4 additions & 1 deletion vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@
}
},
"capabilities": {
"hoverProvider": "true"
"hoverProvider": "true",
"formatting": {
"dynamicRegistration": true
}
},
"languages": [
{
Expand Down
48 changes: 47 additions & 1 deletion vscode/src/test/suite/extension.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as assert from 'assert';

import * as vscode from 'vscode';
import { getDocUri, activate } from './helper';
import { getDocUri, activate, doc } from './helper';

// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
Expand Down Expand Up @@ -110,6 +110,13 @@ suite('Extension Test Suite', function () {
test('Testing for Rename', async () => {
await testrename(renamedoc1);
});

// Tests for formatting
this.timeout(20000);
const formatdoc1 = getDocUri('format.sol');
test('Testing for Formatting', async () => {
await testformat(formatdoc1);
});
});

function toRange(lineno1: number, charno1: number, lineno2: number, charno2: number) {
Expand Down Expand Up @@ -464,6 +471,45 @@ async function testrename(docUri: vscode.Uri) {
assert.strictEqual(loc03.newText, newname0);
}

async function testformat(docUri: vscode.Uri) {
await activate(docUri);

const options = {
tabSize: 4,
insertSpaces: false,
};
const textedits = (await vscode.commands.executeCommand(
'vscode.executeFormatDocumentProvider',
docUri,
options,
)) as vscode.TextEdit[];
// make sure that the input file is not already formatted
assert(textedits.length > 0);

// undo the changes done during the test
const undochanges = async () => {
for (let i = 0; i < textedits.length; i++) {
await vscode.commands.executeCommand('undo');
}
};

try {
const workedits = new vscode.WorkspaceEdit();
workedits.set(docUri, textedits);
const done = await vscode.workspace.applyEdit(workedits);
assert(done);

const actualtext = doc.getText();
const expectedtext = "contract deck {\n enum suit {\n club,\n diamonds,\n hearts,\n spades\n }\n enum value {\n two,\n three,\n four,\n five,\n six,\n seven,\n eight,\n nine,\n ten,\n jack,\n queen,\n king,\n ace\n }\n\n struct card {\n value v;\n suit s;\n }\n\n function score(card c) public returns (uint32 score) {\n if (c.s == suit.hearts) {\n if (c.v == value.ace) {\n score = 14;\n }\n if (c.v == value.king) {\n score = 13;\n }\n if (c.v == value.queen) {\n score = 12;\n }\n if (c.v == value.jack) {\n score = 11;\n }\n }\n // all others score 0\n }\n}\n";
assert.strictEqual(actualtext, expectedtext);
} catch (error) {
await undochanges();
throw error;
}

await undochanges();
}

async function testhover(docUri: vscode.Uri) {
await activate(docUri);

Expand Down
48 changes: 48 additions & 0 deletions vscode/src/testFixture/format.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
contract deck {
enum
suit {
club,
diamonds,
hearts,
spades
}
enum value {
two,


three,
four,
five,
six,
seven,
eight,
nine,
ten,jack,
queen,
king,
ace
}
struct card {
value v;
suit s;
}

function score
(card c) public returns (
uint32 score) {
if (c.s == suit.hearts) {
if (c.v == value.ace) {
score = 14;
}
if ( c.v == value.king) {
score = 13;
}
if ( c.v == value.queen) {
score = 12;
}
if ( c.v == value.jack) {
score = 11;}
}
// all others score 0
}
}