Skip to content

Commit

Permalink
feat(workspace): adds GraphQL parsing capabilities via feature (#3238)
Browse files Browse the repository at this point in the history
Co-authored-by: Ze-Zheng Wu <[email protected]>
  • Loading branch information
ematipico and Sec-ant committed Jun 20, 2024
1 parent 54b4c9a commit f146639
Show file tree
Hide file tree
Showing 13 changed files with 245 additions and 10 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion crates/biome_formatter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ mod verbatim;
use crate::formatter::Formatter;
use crate::group_id::UniqueGroupIdBuilder;
use crate::prelude::TagKind;
use std::fmt;
use std::fmt::{Debug, Display};

use crate::builders::syntax_token_cow_slice;
Expand Down Expand Up @@ -673,7 +674,7 @@ impl FormatContext for SimpleFormatContext {
}
}

#[derive(Debug, Default, Eq, PartialEq)]
#[derive(Debug, Default, Eq, PartialEq, Copy, Clone)]
pub struct SimpleFormatOptions {
pub indent_style: IndentStyle,
pub indent_width: IndentWidth,
Expand Down Expand Up @@ -713,6 +714,12 @@ impl FormatOptions for SimpleFormatOptions {
}
}

impl Display for SimpleFormatOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fmt::Debug::fmt(self, f)
}
}

/// Lightweight sourcemap marker between source and output tokens
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(
Expand Down
4 changes: 2 additions & 2 deletions crates/biome_graphql_syntax/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ version = "0.1.0"
[dependencies]
biome_rowan = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"], optional = true }
serde = { workspace = true, features = ["derive"] }

[features]
serde = ["dep:serde", "schemars", "biome_rowan/serde"]
schema = ["schemars", "biome_rowan/serde"]

[lints]
workspace = true
66 changes: 66 additions & 0 deletions crates/biome_graphql_syntax/src/file_source.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use biome_rowan::FileSourceError;
use std::ffi::OsStr;
use std::path::Path;

#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(
Debug, Clone, Default, Copy, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize,
)]
pub struct GraphqlFileSource {}

impl GraphqlFileSource {
/// Try to return the GraphQL file source corresponding to this file name from well-known files
pub fn try_from_well_known(file_name: &str) -> Result<Self, FileSourceError> {
// TODO: to be implemented
Err(FileSourceError::UnknownFileName(file_name.into()))
}

/// Try to return the GraphQL file source corresponding to this file extension
pub fn try_from_extension(extension: &str) -> Result<Self, FileSourceError> {
match extension {
"graphql" | "gql" => Ok(Self::default()),
_ => Err(FileSourceError::UnknownExtension(
Default::default(),
extension.into(),
)),
}
}

/// Try to return the GraphQL file source corresponding to this language ID
///
/// See the [LSP spec] and [VS Code spec] for a list of language identifiers
///
/// [LSP spec]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem
/// [VS Code spec]: https://code.visualstudio.com/docs/languages/identifiers
pub fn try_from_language_id(language_id: &str) -> Result<Self, FileSourceError> {
match language_id {
"graphql" => Ok(Self::default()),
_ => Err(FileSourceError::UnknownLanguageId(language_id.into())),
}
}
}

impl TryFrom<&Path> for GraphqlFileSource {
type Error = FileSourceError;

fn try_from(path: &Path) -> Result<Self, Self::Error> {
let file_name = path
.file_name()
.and_then(OsStr::to_str)
.ok_or_else(|| FileSourceError::MissingFileName(path.into()))?;

if let Ok(file_source) = Self::try_from_well_known(file_name) {
return Ok(file_source);
}

// We assume the file extensions are case-insensitive
// and we use the lowercase form of them for pattern matching
let extension = &path
.extension()
.and_then(OsStr::to_str)
.map(str::to_lowercase)
.ok_or_else(|| FileSourceError::MissingFileExtension(path.into()))?;

Self::try_from_extension(extension)
}
}
2 changes: 2 additions & 0 deletions crates/biome_graphql_syntax/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

#[macro_use]
mod generated;
mod file_source;
mod syntax_node;

use biome_rowan::{AstNode, RawSyntaxKind, SyntaxKind};
pub use biome_rowan::{TextLen, TextRange, TextSize, TokenAtOffset, TriviaPieceKind, WalkEvent};
pub use file_source::GraphqlFileSource;
pub use generated::*;
pub use syntax_node::*;

Expand Down
4 changes: 1 addition & 3 deletions crates/biome_grit_parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ quickcheck_macros = { workspace = true }
tests_macros = { workspace = true }

[features]
schemars = ["dep:schemars"]
serde = ["biome_grit_syntax/serde"]
tests = []
schemars = ["dep:schemars", "biome_grit_syntax/schema"]

# cargo-workspaces metadata
[package.metadata.workspaces]
Expand Down
4 changes: 2 additions & 2 deletions crates/biome_grit_syntax/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ version = "0.5.7"
[dependencies]
biome_rowan = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"], optional = true }
serde = { workspace = true, features = ["derive"] }

[features]
serde = ["dep:serde", "schemars", "biome_rowan/serde"]
schema = ["schemars", "biome_rowan/serde"]

[lints]
workspace = true
5 changes: 5 additions & 0 deletions crates/biome_service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ biome_diagnostics = { workspace = true }
biome_flags = { workspace = true }
biome_formatter = { workspace = true, features = ["serde"] }
biome_fs = { workspace = true, features = ["serde"] }
biome_graphql_parser = { workspace = true }
biome_graphql_syntax = { workspace = true }
biome_grit_patterns = { workspace = true }
biome_js_analyze = { workspace = true }
biome_js_factory = { workspace = true, optional = true }
Expand Down Expand Up @@ -65,8 +67,11 @@ schema = [
"biome_text_edit/schemars",
"biome_json_syntax/schema",
"biome_css_syntax/schema",
"biome_graphql_syntax/schema",
]

graphql = []

[dev-dependencies]
insta = { workspace = true }
tests_macros = { workspace = true }
Expand Down
112 changes: 112 additions & 0 deletions crates/biome_service/src/file_handlers/graphql.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
use super::{DocumentFileSource, ExtensionHandler, ParseResult};
use crate::file_handlers::DebugCapabilities;
use crate::file_handlers::{
AnalyzerCapabilities, Capabilities, FormatterCapabilities, ParserCapabilities,
};
use crate::settings::{
FormatSettings, LanguageListSettings, LanguageSettings, LinterSettings, OverrideSettings,
ServiceLanguage, Settings,
};
use crate::workspace::GetSyntaxTreeResult;
use biome_analyze::{AnalyzerConfiguration, AnalyzerOptions};
use biome_formatter::SimpleFormatOptions;
use biome_fs::BiomePath;
use biome_graphql_parser::parse_graphql_with_cache;
use biome_graphql_syntax::{GraphqlLanguage, GraphqlRoot, GraphqlSyntaxNode};
use biome_parser::AnyParse;
use biome_rowan::NodeCache;

impl ServiceLanguage for GraphqlLanguage {
type FormatterSettings = ();
type LinterSettings = ();
type OrganizeImportsSettings = ();
type FormatOptions = SimpleFormatOptions;
type ParserSettings = ();
type EnvironmentSettings = ();

fn lookup_settings(language: &LanguageListSettings) -> &LanguageSettings<Self> {
&language.graphql
}

fn resolve_format_options(
_global: Option<&FormatSettings>,
_overrides: Option<&OverrideSettings>,
_language: Option<&()>,
_path: &BiomePath,
_document_file_source: &DocumentFileSource,
) -> Self::FormatOptions {
SimpleFormatOptions::default()
}

fn resolve_analyzer_options(
_global: Option<&Settings>,
_linter: Option<&LinterSettings>,
_overrides: Option<&OverrideSettings>,
_language: Option<&Self::LinterSettings>,
path: &BiomePath,
_file_source: &DocumentFileSource,
) -> AnalyzerOptions {
AnalyzerOptions {
configuration: AnalyzerConfiguration::default(),
file_path: path.to_path_buf(),
}
}
}

#[derive(Debug, Default, PartialEq, Eq)]
pub(crate) struct GraphqlFileHandler;

impl ExtensionHandler for GraphqlFileHandler {
fn capabilities(&self) -> Capabilities {
Capabilities {
parser: ParserCapabilities { parse: Some(parse) },
debug: DebugCapabilities {
debug_syntax_tree: Some(debug_syntax_tree),
debug_control_flow: None,
debug_formatter_ir: None,
},
analyzer: AnalyzerCapabilities {
lint: None,
code_actions: None,
rename: None,
fix_all: None,
organize_imports: None,
},
formatter: FormatterCapabilities {
format: None,
format_range: None,
format_on_type: None,
},
}
}
}

fn parse(
_biome_path: &BiomePath,
file_source: DocumentFileSource,
text: &str,
_settings: Option<&Settings>,
cache: &mut NodeCache,
) -> ParseResult {
let parse = parse_graphql_with_cache(text, cache);
let root = parse.syntax();
let diagnostics = parse.into_diagnostics();

ParseResult {
any_parse: AnyParse::new(
// SAFETY: the parser should always return a root node
root.as_send().unwrap(),
diagnostics,
),
language: Some(file_source),
}
}

fn debug_syntax_tree(_rome_path: &BiomePath, parse: AnyParse) -> GetSyntaxTreeResult {
let syntax: GraphqlSyntaxNode = parse.syntax();
let tree: GraphqlRoot = parse.tree();
GetSyntaxTreeResult {
cst: format!("{syntax:#?}"),
ast: format!("{tree:#?}"),
}
}
Loading

0 comments on commit f146639

Please sign in to comment.