Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

Commit

Permalink
feat(rome_service): allow workspace clients to provide a language hint (
Browse files Browse the repository at this point in the history
#3140)

* feat(vscode): add a status bar item for the language server

* feat(rome_service): allow workspace clients to provide a language ID as a hint for paths without a file extension

* Update crates/rome_service/src/workspace/server.rs

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>

* fix bindings codegen

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
  • Loading branch information
leops and ematipico committed Sep 5, 2022
1 parent 5e35939 commit 7506e16
Show file tree
Hide file tree
Showing 14 changed files with 378 additions and 95 deletions.
3 changes: 2 additions & 1 deletion crates/rome_cli/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{CliSession, Termination};
use rome_console::{markup, ConsoleExt};
use rome_fs::RomePath;
use rome_service::workspace::{
FeatureName, FixFileMode, FormatFileParams, OpenFileParams, SupportsFeatureParams,
FeatureName, FixFileMode, FormatFileParams, Language, OpenFileParams, SupportsFeatureParams,
};
use std::path::PathBuf;

Expand Down Expand Up @@ -137,6 +137,7 @@ pub(crate) fn execute_mode(mode: Execution, mut session: CliSession) -> Result<(
path: rome_path.clone(),
version: 0,
content: content.into(),
language_hint: Language::default(),
})?;
let printed = workspace.format_file(FormatFileParams { path: rome_path })?;

Expand Down
5 changes: 4 additions & 1 deletion crates/rome_cli/src/traversal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ use rome_diagnostics::{
use rome_fs::{AtomicInterner, FileSystem, OpenOptions, PathInterner, RomePath};
use rome_fs::{TraversalContext, TraversalScope};
use rome_service::{
workspace::{FeatureName, FileGuard, OpenFileParams, RuleCategories, SupportsFeatureParams},
workspace::{
FeatureName, FileGuard, Language, OpenFileParams, RuleCategories, SupportsFeatureParams,
},
Workspace,
};
use std::{
Expand Down Expand Up @@ -571,6 +573,7 @@ fn process_file(ctx: &TraversalOptions, path: &Path, file_id: FileId) -> FileRes
path: rome_path,
version: 0,
content: input.clone(),
language_hint: Language::default(),
},
)
.with_file_id_and_code(file_id, "IO")?;
Expand Down
20 changes: 15 additions & 5 deletions crates/rome_js_syntax/src/source_type.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::fmt::Display;
use std::path::Path;
use std::path::{Path, PathBuf};

/// Enum of the different ECMAScript standard versions.
/// The versions are ordered in increasing order; The newest version comes last.
Expand Down Expand Up @@ -177,15 +177,15 @@ impl TryFrom<&Path> for SourceType {
fn try_from(path: &Path) -> Result<Self, Self::Error> {
let file_name = path
.file_name()
.expect("Can't read the file name")
.ok_or_else(|| SourceTypeError::MissingFileName(path.into()))?
.to_str()
.expect("Can't read the file name");
.ok_or_else(|| SourceTypeError::MissingFileName(path.into()))?;

let extension = path
.extension()
.expect("Can't read the file extension")
.ok_or_else(|| SourceTypeError::MissingFileExtension(path.into()))?
.to_str()
.expect("Can't read the file extension");
.ok_or_else(|| SourceTypeError::MissingFileExtension(path.into()))?;

compute_source_type_from_path_or_extension(file_name, extension)
}
Expand All @@ -194,6 +194,10 @@ impl TryFrom<&Path> for SourceType {
/// Errors around the construct of the source type
#[derive(Debug)]
pub enum SourceTypeError {
/// The path has no file name
MissingFileName(PathBuf),
/// The path has no file extension
MissingFileExtension(PathBuf),
/// The source type is unknown
UnknownExtension(String),
}
Expand All @@ -203,6 +207,12 @@ impl std::error::Error for SourceTypeError {}
impl Display for SourceTypeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SourceTypeError::MissingFileName(path) => {
write!(f, "The path {path:?} has no file name")
}
SourceTypeError::MissingFileExtension(path) => {
write!(f, "The path {path:?} has no file extension")
}
SourceTypeError::UnknownExtension(extension) => {
write!(f, "The parser can't parse the extension '{extension}' yet")
}
Expand Down
27 changes: 0 additions & 27 deletions crates/rome_lsp/src/documents.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,5 @@
use anyhow::bail;

use crate::line_index::LineIndex;

/// Internal representation of supported [language identifiers]
///
/// [language identifiers]: https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum EditorLanguage {
JavaScript,
JavaScriptReact,
TypeScript,
TypeScriptReact,
}

impl TryFrom<&str> for EditorLanguage {
type Error = anyhow::Error;

fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"javascript" => Ok(EditorLanguage::JavaScript),
"javascriptreact" => Ok(EditorLanguage::JavaScriptReact),
"typescript" => Ok(EditorLanguage::TypeScript),
"typescriptreact" => Ok(EditorLanguage::TypeScriptReact),
_ => bail!("Unsupported language: {}", value),
}
}
}

/// Represents an open [`textDocument`]. Can be cheaply cloned.
///
/// [`textDocument`]: https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#textDocumentItem
Expand Down
4 changes: 3 additions & 1 deletion crates/rome_lsp/src/handlers/text_document.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use anyhow::{bail, Result};
use rome_service::workspace::{ChangeFileParams, CloseFileParams, OpenFileParams};
use rome_service::workspace::{ChangeFileParams, CloseFileParams, Language, OpenFileParams};
use tower_lsp::lsp_types;
use tracing::error;

Expand All @@ -14,6 +14,7 @@ pub(crate) async fn did_open(
let url = params.text_document.uri;
let version = params.text_document.version;
let content = params.text_document.text;
let language_hint = Language::from_language_id(&params.text_document.language_id);

let rome_path = session.file_path(&url);
let doc = Document::new(version, &content);
Expand All @@ -22,6 +23,7 @@ pub(crate) async fn did_open(
path: rome_path,
version,
content,
language_hint,
})?;

session.insert_document(url.clone(), doc);
Expand Down
70 changes: 70 additions & 0 deletions crates/rome_lsp/tests/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,76 @@ async fn document_lifecycle() -> Result<()> {
Ok(())
}

#[tokio::test]
async fn document_no_extension() -> Result<()> {
let factory = ServerFactory::default();
let (service, client) = factory.create().into_inner();
let (stream, sink) = client.split();
let mut server = Server::new(service);

let reader = tokio::spawn(client_handler(stream, sink));

server.initialize().await?;
server.initialized().await?;

server
.notify(
"textDocument/didOpen",
DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: Url::parse("test://workspace/document")?,
language_id: String::from("javascript"),
version: 0,
text: String::from("statement()"),
},
},
)
.await?;

let res: Option<Vec<TextEdit>> = server
.request(
"textDocument/formatting",
"formatting",
DocumentFormattingParams {
text_document: TextDocumentIdentifier {
uri: Url::parse("test://workspace/document")?,
},
options: FormattingOptions {
tab_size: 4,
insert_spaces: false,
properties: HashMap::default(),
trim_trailing_whitespace: None,
insert_final_newline: None,
trim_final_newlines: None,
},
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
},
)
.await?
.context("formatting returned None")?;

let edits = res.context("formatting did not return an edit list")?;
assert!(!edits.is_empty(), "formatting returned an empty edit list");

server
.notify(
"textDocument/didClose",
DidCloseTextDocumentParams {
text_document: TextDocumentIdentifier {
uri: Url::parse("test://workspace/document")?,
},
},
)
.await?;

server.shutdown().await?;
reader.abort();

Ok(())
}

#[tokio::test]
async fn pull_quick_fixes() -> Result<()> {
let factory = ServerFactory::default();
Expand Down
11 changes: 8 additions & 3 deletions crates/rome_service/src/file_handlers/javascript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use super::{
AnalyzerCapabilities, DebugCapabilities, ExtensionHandler, FormatterCapabilities, Mime,
ParserCapabilities,
};
use crate::file_handlers::FixAllParams;
use crate::file_handlers::{FixAllParams, Language as LanguageId};
use indexmap::IndexSet;
use rome_console::codespan::Severity;
use std::borrow::Cow;
Expand Down Expand Up @@ -106,11 +106,16 @@ impl ExtensionHandler for JsFileHandler {
}
}

fn parse(rome_path: &RomePath, text: &str) -> AnyParse {
fn parse(rome_path: &RomePath, language_hint: LanguageId, text: &str) -> AnyParse {
let file_id = rome_path.file_id();

let source_type =
SourceType::try_from(rome_path.as_path()).unwrap_or_else(|_| SourceType::js_module());
SourceType::try_from(rome_path.as_path()).unwrap_or_else(|_| match language_hint {
LanguageId::JavaScriptReact => SourceType::jsx(),
LanguageId::TypeScript => SourceType::ts(),
LanguageId::TypeScriptReact => SourceType::tsx(),
_ => SourceType::js_module(),
});

let parse = rome_js_parser::parse(text, file_id, source_type);
AnyParse::from(parse)
Expand Down
95 changes: 75 additions & 20 deletions crates/rome_service/src/file_handlers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,34 +23,81 @@ use crate::workspace::FixFileMode;
pub use javascript::JsFormatSettings;

/// Supported languages by Rome
#[derive(Debug, PartialEq)]
pub(crate) enum Language {
/// JavaScript, TypeScript, JSX, TSX
#[derive(Clone, Copy, Debug, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum Language {
/// JavaScript
JavaScript,
/// JSX
JavaScriptReact,
/// TypeScript
TypeScript,
/// TSX
TypeScriptReact,
/// JSON
Json,
/// Any language that is not supported
#[default]
Unknown,
}

impl From<&str> for Language {
fn from(s: &str) -> Self {
impl Language {
/// Returns the language corresponding to this file extension
pub fn from_extension(s: &str) -> Self {
match s.to_lowercase().as_str() {
"js" | "ts" | "jsx" | "tsx" | "mjs" | "cjs" | "cts" | "mts" => Language::JavaScript,
"js" | "mjs" | "cjs" => Language::JavaScript,
"jsx" => Language::JavaScriptReact,
"ts" | "mts" | "cts" => Language::TypeScript,
"tsx" => Language::TypeScriptReact,
"json" => Language::Json,
_ => Language::Unknown,
}
}
}

impl From<&OsStr> for Language {
fn from(s: &OsStr) -> Self {
match s.to_str().unwrap() {
"js" | "ts" | "jsx" | "tsx" | "mjs" | "cjs" | "cts" | "mts" => Language::JavaScript,
/// Returns the language corresponding to this language ID
///
/// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem
/// for a list of language identifiers
pub fn from_language_id(s: &str) -> Self {
match s.to_lowercase().as_str() {
"javascript" => Language::JavaScript,
"typescript" => Language::TypeScript,
"javascriptreact" => Language::JavaScriptReact,
"typescriptreact" => Language::TypeScriptReact,
"json" => Language::Json,
_ => Language::Unknown,
}
}

/// Returns the language if it's not unknown, otherwise returns `other`.
///
/// # Examples
///
/// ```
/// # use rome_service::workspace::Language;
/// let x = Language::JavaScript;
/// let y = Language::Unknown;
/// assert_eq!(x.or(y), Language::JavaScript);
///
/// let x = Language::Unknown;
/// let y = Language::JavaScript;
/// assert_eq!(x.or(y), Language::JavaScript);
///
/// let x = Language::JavaScript;
/// let y = Language::Json;
/// assert_eq!(x.or(y), Language::JavaScript);
///
/// let x = Language::Unknown;
/// let y = Language::Unknown;
/// assert_eq!(x.or(y), Language::Unknown);
/// ```
pub fn or(self, other: Language) -> Language {
if self != Language::Unknown {
self
} else {
other
}
}
}

// TODO: The Css variant is unused at the moment
Expand Down Expand Up @@ -89,7 +136,7 @@ pub(crate) struct Capabilities {
pub(crate) formatter: FormatterCapabilities,
}

type Parse = fn(&RomePath, &str) -> AnyParse;
type Parse = fn(&RomePath, Language, &str) -> AnyParse;

#[derive(Default)]
pub(crate) struct ParserCapabilities {
Expand Down Expand Up @@ -186,17 +233,25 @@ impl Features {
}

/// Return a [Language] from a string
fn get_language(rome_path: &RomePath) -> Language {
match rome_path.extension() {
Some(file_extension) => file_extension.into(),
None => Language::Unknown,
}
pub(crate) fn get_language(rome_path: &RomePath) -> Language {
rome_path
.extension()
.and_then(OsStr::to_str)
.map(Language::from_extension)
.unwrap_or_default()
}

/// Returns the [Capabilities] associated with a [RomePath]
pub(crate) fn get_capabilities(&self, rome_path: &RomePath) -> Capabilities {
match Self::get_language(rome_path) {
Language::JavaScript => self.js.capabilities(),
pub(crate) fn get_capabilities(
&self,
rome_path: &RomePath,
language_hint: Language,
) -> Capabilities {
match Self::get_language(rome_path).or(language_hint) {
Language::JavaScript
| Language::JavaScriptReact
| Language::TypeScript
| Language::TypeScriptReact => self.js.capabilities(),
Language::Json => self.json.capabilities(),
Language::Unknown => self.unknown.capabilities(),
}
Expand Down

0 comments on commit 7506e16

Please sign in to comment.