Skip to content

Commit

Permalink
Sync schema text with LSP
Browse files Browse the repository at this point in the history
  • Loading branch information
tobias-tengler committed Apr 5, 2024
1 parent d1cf472 commit 4cefb54
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 60 deletions.
3 changes: 2 additions & 1 deletion compiler/crates/relay-lsp/src/goto_definition/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,13 @@ pub fn on_goto_definition(
let program = state.get_program(&project_name)?;

let definition_description = match feature {
crate::Feature::GraphQLDocument(document) => {
crate::Feature::ExecutableDocument(document) => {
get_graphql_definition_description(document, position_span, &schema)?
}
crate::Feature::DocblockIr(docblock_ir) => {
get_docblock_definition_description(&docblock_ir, position_span)?
}
crate::Feature::SchemaDocument(_) => Err(LSPRuntimeError::ExpectedError)?,
};

let extra_data_provider = state.get_extra_data_provider();
Expand Down
4 changes: 3 additions & 1 deletion compiler/crates/relay-lsp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ use common::PerfLogger;
use docblock_resolution_info::DocblockResolutionInfo;
pub use extract_graphql::JavaScriptSourceFeature;
use graphql_syntax::ExecutableDocument;
use graphql_syntax::SchemaDocument;
pub use hover::ContentConsumerType;
pub use js_language_server::JSLanguageServer;
use log::debug;
Expand All @@ -62,8 +63,9 @@ pub use server::Schemas;
pub use utils::position_to_offset;

pub enum Feature {
GraphQLDocument(ExecutableDocument),
ExecutableDocument(ExecutableDocument),
DocblockIr(DocblockIr),
SchemaDocument(SchemaDocument),
}

#[allow(clippy::large_enum_variant)]
Expand Down
132 changes: 78 additions & 54 deletions compiler/crates/relay-lsp/src/server/lsp_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use graphql_ir::RelayMode;
use graphql_syntax::parse_executable_with_error_recovery_and_parser_features;
use graphql_syntax::ExecutableDefinition;
use graphql_syntax::ExecutableDocument;
use graphql_syntax::GraphQLSource;
use intern::string_key::Intern;
use intern::string_key::StringKey;
use log::debug;
Expand Down Expand Up @@ -131,7 +132,7 @@ pub trait GlobalState {

fn document_opened(&self, url: &Url, text: &str) -> LSPRuntimeResult<()>;

fn document_changed(&self, url: &Url, full_text: &str) -> LSPRuntimeResult<()>;
fn document_changed(&self, url: &Url, text: &str) -> LSPRuntimeResult<()>;

fn document_closed(&self, url: &Url) -> LSPRuntimeResult<()>;

Expand All @@ -156,7 +157,8 @@ pub struct LSPState<
pub(crate) schemas: Schemas,
schema_documentation_loader: Option<Box<dyn SchemaDocumentationLoader<TSchemaDocumentation>>>,
pub(crate) source_programs: SourcePrograms,
synced_javascript_features: DashMap<Url, Vec<JavaScriptSourceFeature>>,
synced_javascript_sources: DashMap<Url, Vec<JavaScriptSourceFeature>>,
synced_schema_sources: DashMap<Url, GraphQLSource>,
pub(crate) perf_logger: Arc<TPerfLogger>,
pub(crate) diagnostic_reporter: Arc<DiagnosticReporter>,
pub(crate) notify_lsp_state_resources: Arc<Notify>,
Expand Down Expand Up @@ -201,7 +203,8 @@ impl<TPerfLogger: PerfLogger + 'static, TSchemaDocumentation: SchemaDocumentatio
schemas: Arc::new(DashMap::with_hasher(FnvBuildHasher::default())),
schema_documentation_loader,
source_programs: Arc::new(DashMap::with_hasher(FnvBuildHasher::default())),
synced_javascript_features: Default::default(),
synced_javascript_sources: Default::default(),
synced_schema_sources: Default::default(),
js_resource,
};

Expand All @@ -213,13 +216,13 @@ impl<TPerfLogger: PerfLogger + 'static, TSchemaDocumentation: SchemaDocumentatio
lsp_state
}

fn insert_synced_sources(&self, url: &Url, sources: Vec<JavaScriptSourceFeature>) {
self.synced_javascript_features.insert(url.clone(), sources);
fn insert_synced_js_sources(&self, url: &Url, sources: Vec<JavaScriptSourceFeature>) {
self.synced_javascript_sources.insert(url.clone(), sources);
}

fn validate_synced_sources(&self, url: &Url) -> LSPRuntimeResult<()> {
fn validate_synced_js_sources(&self, url: &Url) -> LSPRuntimeResult<()> {
let mut diagnostics = vec![];
let javascript_features = self.synced_javascript_features.get(url).ok_or_else(|| {
let javascript_features = self.synced_javascript_sources.get(url).ok_or_else(|| {
LSPRuntimeError::UnexpectedError(format!("Expected GraphQL sources for URL {}", url))
})?;
let project_name = self.extract_project_name_from_url(url)?;
Expand Down Expand Up @@ -346,23 +349,26 @@ impl<TPerfLogger: PerfLogger + 'static, TSchemaDocumentation: SchemaDocumentatio
self.task_scheduler.schedule(super::Task::LSPState(task));
}

fn process_synced_sources(
&self,
uri: &Url,
sources: Vec<JavaScriptSourceFeature>,
) -> LSPRuntimeResult<()> {
self.insert_synced_sources(uri, sources);
fn process_synced_js_sources(&self, uri: &Url, sources: Vec<JavaScriptSourceFeature>) {
self.insert_synced_js_sources(uri, sources);
self.schedule_task(Task::ValidateSyncedSource(uri.clone()));

Ok(())
}

fn remove_synced_sources(&self, url: &Url) {
self.synced_javascript_features.remove(url);
fn remove_synced_js_sources(&self, url: &Url) {
self.synced_javascript_sources.remove(url);
self.diagnostic_reporter
.clear_quick_diagnostics_for_url(url);
}

fn insert_synced_schema_source(&self, url: &Url, graphql_source: GraphQLSource) {
self.synced_schema_sources
.insert(url.clone(), graphql_source);
}

fn remove_synced_schema_source(&self, url: &Url) {
self.synced_schema_sources.remove(url);
}

fn initialize_lsp_state_resources(&self, project_name: StringKey) {
if let Entry::Vacant(e) = self.project_status.entry(project_name) {
e.insert(ProjectStatus::Activated);
Expand Down Expand Up @@ -422,17 +428,21 @@ impl<TPerfLogger: PerfLogger + 'static, TSchemaDocumentation: SchemaDocumentatio
1,
)?;

let info = match feature {
Feature::GraphQLDocument(executable_document) => FeatureResolutionInfo::GraphqlNode(
create_node_resolution_info(executable_document, position_span)?,
),
Feature::DocblockIr(docblock_ir) => FeatureResolutionInfo::DocblockNode(DocblockNode {
resolution_info: create_docblock_resolution_info(&docblock_ir, position_span)
.ok_or(LSPRuntimeError::ExpectedError)?,
ir: docblock_ir,
}),
};
Ok(info)
match feature {
Feature::ExecutableDocument(executable_document) => {
Ok(FeatureResolutionInfo::GraphqlNode(
create_node_resolution_info(executable_document, position_span)?,
))
}
Feature::DocblockIr(docblock_ir) => {
Ok(FeatureResolutionInfo::DocblockNode(DocblockNode {
resolution_info: create_docblock_resolution_info(&docblock_ir, position_span)
.ok_or(LSPRuntimeError::ExpectedError)?,
ir: docblock_ir,
}))
}
Feature::SchemaDocument(_) => Err(LSPRuntimeError::ExpectedError),
}
}

/// Return a parsed executable document for this LSP request, only if the request occurs
Expand All @@ -444,8 +454,9 @@ impl<TPerfLogger: PerfLogger + 'static, TSchemaDocumentation: SchemaDocumentatio
) -> LSPRuntimeResult<(ExecutableDocument, Span)> {
let (feature, span) = self.extract_feature_from_text(position, index_offset)?;
match feature {
Feature::GraphQLDocument(document) => Ok((document, span)),
Feature::ExecutableDocument(document) => Ok((document, span)),
Feature::DocblockIr(_) => Err(LSPRuntimeError::ExpectedError),
Feature::SchemaDocument(_) => Err(LSPRuntimeError::ExpectedError),
}
}

Expand All @@ -463,7 +474,8 @@ impl<TPerfLogger: PerfLogger + 'static, TSchemaDocumentation: SchemaDocumentatio

extract_feature_from_text(
project_config,
&self.synced_javascript_features,
&self.synced_javascript_sources,
&self.synced_schema_sources,
position,
index_offset,
)
Expand Down Expand Up @@ -508,7 +520,7 @@ impl<TPerfLogger: PerfLogger + 'static, TSchemaDocumentation: SchemaDocumentatio

extract_executable_definitions_from_text_document(
text_document_uri,
&self.synced_javascript_features,
&self.synced_javascript_sources,
get_parser_features(project_config),
)
}
Expand Down Expand Up @@ -540,12 +552,10 @@ impl<TPerfLogger: PerfLogger + 'static, TSchemaDocumentation: SchemaDocumentatio
})?;

match file_group {
FileGroup::Schema { project_set: _ } => {
self.initialize_lsp_state_resources(project_name);
Ok(())
}
FileGroup::Extension { project_set: _ } => {
FileGroup::Schema { project_set: _ } | FileGroup::Extension { project_set: _ } => {
self.initialize_lsp_state_resources(project_name);
self.insert_synced_schema_source(uri, GraphQLSource::new(text, 0, 0));

Ok(())
}
FileGroup::Source { project_set: _ } => {
Expand All @@ -555,37 +565,51 @@ impl<TPerfLogger: PerfLogger + 'static, TSchemaDocumentation: SchemaDocumentatio

let embedded_sources = extract_graphql::extract(text);

if embedded_sources.is_empty() {
Ok(())
} else {
if !embedded_sources.is_empty() {
self.initialize_lsp_state_resources(project_name);
self.process_synced_sources(uri, embedded_sources)
self.process_synced_js_sources(uri, embedded_sources);
}

Ok(())
}
_ => Err(LSPRuntimeError::ExpectedError),
}
}

fn document_changed(&self, uri: &Url, full_text: &str) -> LSPRuntimeResult<()> {
if let Some(js_server) = self.get_js_language_sever() {
js_server.process_js_source(uri, full_text);
}
fn document_changed(&self, uri: &Url, text: &str) -> LSPRuntimeResult<()> {
let file_group = get_file_group_from_uri(&self.file_categorizer, uri, &self.root_dir)?;

// First we check to see if this document has any GraphQL documents.
let embedded_sources = extract_graphql::extract(full_text);
if embedded_sources.is_empty() {
self.remove_synced_sources(uri);
Ok(())
} else {
self.process_synced_sources(uri, embedded_sources)
match file_group {
FileGroup::Schema { project_set: _ } | FileGroup::Extension { project_set: _ } => {
self.insert_synced_schema_source(uri, GraphQLSource::new(text, 0, 0));

Ok(())
}
FileGroup::Source { project_set: _ } => {
if let Some(js_server) = self.get_js_language_sever() {
js_server.process_js_source(uri, text);
}

let embedded_sources = extract_graphql::extract(text);
if embedded_sources.is_empty() {
self.remove_synced_js_sources(uri);
} else {
self.process_synced_js_sources(uri, embedded_sources);
}

Ok(())
}
_ => Err(LSPRuntimeError::ExpectedError),
}
}

fn document_closed(&self, uri: &Url) -> LSPRuntimeResult<()> {
if let Some(js_server) = self.get_js_language_sever() {
js_server.remove_js_source(uri);
}
self.remove_synced_sources(uri);

self.remove_synced_schema_source(uri);
self.remove_synced_js_sources(uri);
Ok(())
}

Expand All @@ -609,10 +633,10 @@ pub(crate) fn handle_lsp_state_tasks<
) {
match task {
Task::ValidateSyncedSource(url) => {
state.validate_synced_sources(&url).ok();
state.validate_synced_js_sources(&url).ok();
}
Task::ValidateSyncedSources => {
for item in &state.synced_javascript_features {
for item in &state.synced_javascript_sources {
state.schedule_task(Task::ValidateSyncedSource(item.key().clone()));
}
}
Expand Down
26 changes: 22 additions & 4 deletions compiler/crates/relay-lsp/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use docblock_syntax::parse_docblock;
use extract_graphql::JavaScriptSourceFeature;
use graphql_syntax::parse_executable_with_error_recovery_and_parser_features;
use graphql_syntax::ExecutableDefinition;
use graphql_syntax::GraphQLSource;
use graphql_syntax::ParserFeatures;
use intern::string_key::StringKey;
use log::debug;
Expand Down Expand Up @@ -116,14 +117,31 @@ pub fn get_project_name_from_file_group(file_group: &FileGroup) -> Result<String
/// request, only if the request occurs within a GraphQL document or Docblock.
pub fn extract_feature_from_text(
project_config: &ProjectConfig,
source_feature_cache: &DashMap<Url, Vec<JavaScriptSourceFeature>>,
js_source_feature_cache: &DashMap<Url, Vec<JavaScriptSourceFeature>>,
schema_source_cache: &DashMap<Url, GraphQLSource>,
text_document_position: &TextDocumentPositionParams,
index_offset: usize,
) -> LSPRuntimeResult<(Feature, Span)> {
let uri = &text_document_position.text_document.uri;
let position = text_document_position.position;

let source_features = source_feature_cache
if let Some(schema_source) = schema_source_cache.get(uri) {
let source_location_key = SourceLocationKey::standalone(uri.as_ref());
let schema_document = graphql_syntax::parse_schema_document(
&schema_source.text_source().text,
source_location_key,
)
.map_err(|_| LSPRuntimeError::ExpectedError)?;

let position_span = position_to_span(&position, schema_source.text_source(), index_offset)
.ok_or_else(|| {
LSPRuntimeError::UnexpectedError("Failed to map positions to spans".to_string())
})?;

return Ok((Feature::SchemaDocument(schema_document), position_span));
}

let source_features = js_source_feature_cache
.get(uri)
.ok_or(LSPRuntimeError::ExpectedError)?;

Expand Down Expand Up @@ -167,12 +185,12 @@ pub fn extract_feature_from_text(
// since the change event fires before completion.
debug!("position_span: {:?}", position_span);

Ok((Feature::GraphQLDocument(document), position_span))
Ok((Feature::ExecutableDocument(document), position_span))
}
JavaScriptSourceFeature::Docblock(docblock_source) => {
let executable_definitions_in_file = extract_executable_definitions_from_text_document(
uri,
source_feature_cache,
js_source_feature_cache,
parser_features,
)?;

Expand Down

0 comments on commit 4cefb54

Please sign in to comment.