Skip to content

Commit

Permalink
add support to lsp for resolving config file JSX import source
Browse files Browse the repository at this point in the history
  • Loading branch information
kitsonk committed Nov 9, 2021
1 parent 527e2d7 commit ea2dda2
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 30 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ winres = "0.1.11"
deno_ast = { version = "0.5.0", features = ["bundler", "codegen", "dep_graph", "module_specifier", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] }
deno_core = { version = "0.105.0", path = "../core" }
deno_doc = "0.20.0"
deno_graph = "0.11.0"
deno_graph = "0.11.1"
deno_lint = { version = "0.19.0", features = ["docs"] }
deno_runtime = { version = "0.31.0", path = "../runtime" }
deno_tls = { version = "0.10.0", path = "../ext/tls" }
Expand Down
126 changes: 123 additions & 3 deletions cli/lsp/documents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use deno_core::parking_lot::Mutex;
use deno_core::url;
use deno_core::ModuleSpecifier;
use lspower::lsp;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::collections::HashSet;
use std::fs;
Expand Down Expand Up @@ -133,6 +134,59 @@ impl IndexValid {
}
}

// TODO(@kitsonk) expose the synthetic module from deno_graph
#[derive(Debug)]
struct SyntheticModule {
dependencies: BTreeMap<String, deno_graph::Resolved>,
specifier: ModuleSpecifier,
}

impl SyntheticModule {
pub fn new(
specifier: ModuleSpecifier,
dependencies: Vec<(String, Option<lsp::Range>)>,
maybe_resolver: Option<&dyn deno_graph::source::Resolver>,
) -> Self {
let dependencies = dependencies
.iter()
.map(|(dep, maybe_range)| {
let range = to_deno_graph_range(&specifier, maybe_range.as_ref());
let result = if let Some(resolver) = maybe_resolver {
resolver.resolve(dep, &specifier).map_err(|err| {
if let Some(specifier_error) =
err.downcast_ref::<deno_graph::SpecifierError>()
{
deno_graph::ResolutionError::InvalidSpecifier(
specifier_error.clone(),
range.clone(),
)
} else {
deno_graph::ResolutionError::ResolverError(
Arc::new(err),
dep.to_string(),
range.clone(),
)
}
})
} else {
deno_core::resolve_import(dep, specifier.as_str()).map_err(|err| {
deno_graph::ResolutionError::ResolverError(
Arc::new(err.into()),
dep.to_string(),
range.clone(),
)
})
};
(dep.to_string(), Some(result.map(|s| (s, range))))
})
.collect();
Self {
dependencies,
specifier,
}
}
}

#[derive(Debug)]
pub(crate) struct Document {
line_index: Arc<LineIndex>,
Expand Down Expand Up @@ -349,6 +403,32 @@ pub(crate) fn to_lsp_range(range: &deno_graph::Range) -> lsp::Range {
}
}

fn to_deno_graph_range(
specifier: &ModuleSpecifier,
maybe_range: Option<&lsp::Range>,
) -> deno_graph::Range {
let specifier = specifier.clone();
if let Some(range) = maybe_range {
deno_graph::Range {
specifier,
start: deno_graph::Position {
line: range.start.line as usize,
character: range.start.character as usize,
},
end: deno_graph::Position {
line: range.end.line as usize,
character: range.end.character as usize,
},
}
} else {
deno_graph::Range {
specifier,
start: deno_graph::Position::zeroed(),
end: deno_graph::Position::zeroed(),
}
}
}

/// Recurse and collect specifiers that appear in the dependent map.
fn recurse_dependents(
specifier: &ModuleSpecifier,
Expand Down Expand Up @@ -378,6 +458,9 @@ struct Inner {
/// A map of documents that can either be "open" in the language server, or
/// just present on disk.
docs: HashMap<ModuleSpecifier, Document>,
/// Any imports to the context supplied by configuration files. This is like
/// the imports into the a module graph in CLI.
imports: HashMap<ModuleSpecifier, SyntheticModule>,
/// The optional import map that should be used when resolving dependencies.
maybe_import_map: Option<ImportMapResolver>,
/// The optional JSX resolver, which is used when JSX imports are configured.
Expand All @@ -392,6 +475,7 @@ impl Inner {
dirty: true,
dependents_map: HashMap::default(),
docs: HashMap::default(),
imports: HashMap::default(),
maybe_import_map: None,
maybe_jsx_resolver: None,
redirects: HashMap::default(),
Expand Down Expand Up @@ -527,8 +611,7 @@ impl Inner {
specifier: &str,
referrer: &ModuleSpecifier,
) -> bool {
let maybe_resolver =
self.maybe_import_map.as_ref().map(|im| im.as_resolver());
let maybe_resolver = self.get_maybe_resolver();
let maybe_specifier = if let Some(resolver) = maybe_resolver {
resolver.resolve(specifier, referrer).ok()
} else {
Expand Down Expand Up @@ -723,12 +806,13 @@ impl Inner {
language_id: LanguageId,
content: Arc<String>,
) {
let maybe_resolver = self.get_maybe_resolver();
let document_data = Document::open(
specifier.clone(),
version,
language_id,
content,
self.maybe_import_map.as_ref().map(|r| r.as_resolver()),
maybe_resolver,
);
self.docs.insert(specifier, document_data);
self.dirty = true;
Expand Down Expand Up @@ -775,6 +859,12 @@ impl Inner {
} else {
results.push(None);
}
} else if let Some(Some(Ok((specifier, _)))) =
self.resolve_imports_dependency(&specifier)
{
// clone here to avoid double borrow of self
let specifier = specifier.clone();
results.push(self.resolve_dependency(&specifier));
} else {
results.push(None);
}
Expand Down Expand Up @@ -807,6 +897,22 @@ impl Inner {
}
}

/// Iterate through any "imported" modules, checking to see if a dependency
/// is available. This is used to provide "global" imports like the JSX import
/// source.
fn resolve_imports_dependency(
&self,
specifier: &str,
) -> Option<&deno_graph::Resolved> {
for module in self.imports.values() {
let maybe_dep = module.dependencies.get(specifier);
if maybe_dep.is_some() {
return maybe_dep;
}
}
None
}

fn resolve_remote_specifier(
&self,
specifier: &ModuleSpecifier,
Expand Down Expand Up @@ -907,6 +1013,20 @@ impl Inner {
.map(|im| JsxResolver::new(im, self.maybe_import_map.clone()))
})
.flatten();
if let Some(Ok(Some(imports))) =
maybe_config_file.map(|cf| cf.to_maybe_imports())
{
for (referrer, dependencies) in imports {
let dependencies =
dependencies.into_iter().map(|s| (s, None)).collect();
let module = SyntheticModule::new(
referrer.clone(),
dependencies,
self.get_maybe_resolver(),
);
self.imports.insert(referrer, module);
}
}
self.dirty = true;
}

Expand Down
70 changes: 46 additions & 24 deletions cli/tsc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,27 @@ pub struct ResolveArgs {
pub specifiers: Vec<String>,
}

fn resolve_specifier(
state: &mut State,
specifier: &ModuleSpecifier,
) -> (String, String) {
let media_type = state
.graph
.get(specifier)
.map_or(&MediaType::Unknown, |m| &m.media_type);
let specifier_str = match specifier.scheme() {
"data" | "blob" => {
let specifier_str = hash_url(specifier, media_type);
state
.data_url_map
.insert(specifier_str.clone(), specifier.clone());
specifier_str
}
_ => specifier.to_string(),
};
(specifier_str, media_type.as_ts_extension().into())
}

fn op_resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
let v: ResolveArgs = serde_json::from_value(args)
.context("Invalid request from JavaScript for \"op_resolve\".")?;
Expand All @@ -434,30 +455,31 @@ fn op_resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
MediaType::from(specifier).as_ts_extension().to_string(),
));
} else {
let resolved_dependency =
match state.graph.resolve_dependency(specifier, &referrer, true) {
Some(resolved_specifier) => {
let media_type = state
.graph
.get(resolved_specifier)
.map_or(&MediaType::Unknown, |m| &m.media_type);
let resolved_specifier_str = match resolved_specifier.scheme() {
"data" | "blob" => {
let specifier_str = hash_url(resolved_specifier, media_type);
state
.data_url_map
.insert(specifier_str.clone(), resolved_specifier.clone());
specifier_str
}
_ => resolved_specifier.to_string(),
};
(resolved_specifier_str, media_type.as_ts_extension().into())
}
None => (
"deno:///missing_dependency.d.ts".to_string(),
".d.ts".to_string(),
),
};
// here, we try to resolve the specifier via the referrer, but if we can't
// we will try to resolve the specifier via the configuration file, if
// present, finally defaulting to a "placeholder" specifier. This handles
// situations like the jsxImportSource, which tsc tries to resolve the
// import source from a JSX module, but the module graph only contains the
// import as a dependency of the configuration file.
let resolved_dependency = if let Some(resolved_specifier) = state
.graph
.resolve_dependency(specifier, &referrer, true)
.cloned()
{
resolve_specifier(state, &resolved_specifier)
} else if let Some(resolved_specifier) = state
.maybe_config_specifier
.as_ref()
.map(|cf| state.graph.resolve_dependency(specifier, cf, true).cloned())
.flatten()
{
resolve_specifier(state, &resolved_specifier)
} else {
(
"deno:///missing_dependency.d.ts".to_string(),
".d.ts".to_string(),
)
};
resolved.push(resolved_dependency);
}
}
Expand Down

0 comments on commit ea2dda2

Please sign in to comment.