Skip to content

Commit c4234ad

Browse files
committed
feat: multiple import maps
1 parent 4a1f63b commit c4234ad

File tree

18 files changed

+510
-433
lines changed

18 files changed

+510
-433
lines changed

cli/args/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1424,7 +1424,7 @@ fn resolve_import_map_specifier(
14241424
) -> Result<Option<Url>, ImportMapSpecifierResolveError> {
14251425
if let Some(import_map_path) = maybe_import_map_path {
14261426
if let Some(config_file) = &maybe_config_file
1427-
&& config_file.json.import_map.is_some()
1427+
&& !config_file.json.import_map.is_empty()
14281428
{
14291429
log::warn!(
14301430
"{} the configuration file \"{}\" contains an entry for \"importMap\" that is being ignored.",

cli/factory.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -225,17 +225,18 @@ impl SpecifiedImportMapProvider for CliSpecifiedImportMapProvider {
225225
}))
226226
}
227227
None => {
228-
if let Some(import_map) =
229-
self.workspace_external_import_map_loader.get_or_load()?
230-
{
231-
let path_url = deno_path_util::url_from_file_path(&import_map.path)?;
232-
Ok(Some(deno_resolver::workspace::SpecifiedImportMap {
233-
base_url: path_url,
234-
value: import_map.value.clone(),
235-
}))
236-
} else {
237-
Ok(None)
238-
}
228+
todo!()
229+
// if let Some(import_map) =
230+
// self.workspace_external_import_map_loader.get_or_load()?
231+
// {
232+
// let path_url = deno_path_util::url_from_file_path(&import_map.path)?;
233+
// Ok(Some(deno_resolver::workspace::SpecifiedImportMap {
234+
// base_url: path_url,
235+
// value: import_map.value.clone(),
236+
// }))
237+
// } else {
238+
// Ok(None)
239+
// }
239240
}
240241
}
241242
}

cli/lib/standalone/binary.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ pub struct SerializedResolverWorkspaceJsrPackage {
6363

6464
#[derive(Deserialize, Serialize)]
6565
pub struct SerializedWorkspaceResolver {
66-
pub import_map: Option<SerializedWorkspaceResolverImportMap>,
66+
pub import_maps: Vec<SerializedWorkspaceResolverImportMap>,
6767
pub jsr_pkgs: Vec<SerializedResolverWorkspaceJsrPackage>,
6868
pub package_jsons: BTreeMap<String, serde_json::Value>,
6969
pub pkg_json_resolution: PackageJsonDepResolution,

cli/lsp/analysis.rs

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ pub fn import_map_lookup(
272272
pub struct TsResponseImportMapper<'a> {
273273
document_modules: &'a DocumentModules,
274274
scope: Option<Arc<ModuleSpecifier>>,
275-
maybe_import_map: Option<&'a ImportMap>,
275+
maybe_import_maps: Vec<&'a ImportMap>,
276276
resolver: &'a LspResolver,
277277
tsc_specifier_map: &'a tsc::TscSpecifierMap,
278278
}
@@ -284,14 +284,15 @@ impl<'a> TsResponseImportMapper<'a> {
284284
resolver: &'a LspResolver,
285285
tsc_specifier_map: &'a tsc::TscSpecifierMap,
286286
) -> Self {
287-
let maybe_import_map = resolver
287+
let maybe_import_maps = resolver
288288
.get_scoped_resolver(scope.as_deref())
289289
.as_workspace_resolver()
290-
.maybe_import_map();
290+
.maybe_import_maps()
291+
.collect();
291292
Self {
292293
document_modules,
293294
scope,
294-
maybe_import_map,
295+
maybe_import_maps,
295296
resolver,
296297
tsc_specifier_map,
297298
}
@@ -351,20 +352,21 @@ impl<'a> TsResponseImportMapper<'a> {
351352
.map(SmallStackString::from_string);
352353
let mut req = None;
353354
req = req.or_else(|| {
354-
let import_map = self.maybe_import_map?;
355-
for entry in import_map.entries_for_referrer(referrer) {
356-
let Some(value) = entry.raw_value else {
357-
continue;
358-
};
359-
let Ok(req_ref) = JsrPackageReqReference::from_str(value) else {
360-
continue;
361-
};
362-
let req = req_ref.req();
363-
if req.name == nv.name
364-
&& req.version_req.tag().is_none()
365-
&& req.version_req.matches(&nv.version)
366-
{
367-
return Some(req.clone());
355+
for import_map in &self.maybe_import_maps {
356+
for entry in import_map.entries_for_referrer(referrer) {
357+
let Some(value) = entry.raw_value else {
358+
continue;
359+
};
360+
let Ok(req_ref) = JsrPackageReqReference::from_str(value) else {
361+
continue;
362+
};
363+
let req = req_ref.req();
364+
if req.name == nv.name
365+
&& req.version_req.tag().is_none()
366+
&& req.version_req.matches(&nv.version)
367+
{
368+
return Some(req.clone());
369+
}
368370
}
369371
}
370372
None
@@ -378,7 +380,7 @@ impl<'a> TsResponseImportMapper<'a> {
378380
JsrPackageNvReference::new(nv_ref).to_string()
379381
};
380382
let specifier = ModuleSpecifier::parse(&spec_str).ok()?;
381-
if let Some(import_map) = self.maybe_import_map {
383+
for import_map in &self.maybe_import_maps {
382384
if let Some(result) =
383385
import_map_lookup(import_map, &specifier, referrer)
384386
{
@@ -429,7 +431,7 @@ impl<'a> TsResponseImportMapper<'a> {
429431
self.resolve_package_path(specifier, &pkg_folder)
430432
})?;
431433
let sub_path = Some(sub_path).filter(|s| !s.is_empty());
432-
if let Some(import_map) = self.maybe_import_map {
434+
for import_map in &self.maybe_import_maps {
433435
let pkg_reqs = pkg_reqs.iter().collect::<HashSet<_>>();
434436
let mut matches = Vec::new();
435437
for entry in import_map.entries_for_referrer(referrer) {
@@ -477,10 +479,10 @@ impl<'a> TsResponseImportMapper<'a> {
477479
}
478480

479481
// check if the import map has this specifier
480-
if let Some(import_map) = self.maybe_import_map
481-
&& let Some(result) = import_map_lookup(import_map, specifier, referrer)
482-
{
483-
return Some(result);
482+
for import_map in &self.maybe_import_maps {
483+
if let Some(result) = import_map_lookup(import_map, specifier, referrer) {
484+
return Some(result);
485+
}
484486
}
485487

486488
None

cli/lsp/completions.rs

Lines changed: 64 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use deno_semver::jsr::JsrPackageReqReference;
1515
use deno_semver::package::PackageNv;
1616
use import_map::ImportMap;
1717
use indexmap::IndexSet;
18+
use lsp_types::CompletionItem;
1819
use lsp_types::CompletionList;
1920
use node_resolver::NodeResolutionKind;
2021
use node_resolver::ResolutionMode;
@@ -159,10 +160,11 @@ pub async fn get_import_completions(
159160
document_modules: &DocumentModules,
160161
resolver: &LspResolver,
161162
) -> Option<lsp::CompletionResponse> {
162-
let maybe_import_map = resolver
163+
let maybe_import_maps = resolver
163164
.get_scoped_resolver(module.scope.as_deref())
164165
.as_workspace_resolver()
165-
.maybe_import_map();
166+
.maybe_import_maps()
167+
.collect::<Vec<_>>();
166168
let (text, _, graph_range) = module.dependency_at_position(position)?;
167169
let resolution_mode = graph_range
168170
.resolution_mode
@@ -206,11 +208,11 @@ pub async fn get_import_completions(
206208
Some(lsp::CompletionResponse::List(completion_list))
207209
}
208210
_ => {
209-
match get_import_map_completions(
211+
match get_import_maps_completions(
210212
&module.specifier,
211213
text,
212214
&range,
213-
maybe_import_map,
215+
&maybe_import_maps,
214216
) {
215217
Some(completion_list) => {
216218
// completions for import map specifiers
@@ -286,7 +288,7 @@ pub async fn get_import_completions(
286288
})
287289
.collect();
288290
let mut is_incomplete = false;
289-
if let Some(import_map) = maybe_import_map {
291+
for import_map in maybe_import_maps {
290292
items.extend(get_base_import_map_completions(
291293
import_map,
292294
&module.specifier,
@@ -363,49 +365,65 @@ fn get_import_map_completions(
363365
_specifier: &ModuleSpecifier,
364366
text: &str,
365367
range: &lsp::Range,
366-
maybe_import_map: Option<&ImportMap>,
367-
) -> Option<CompletionList> {
368-
if !text.is_empty()
369-
&& let Some(import_map) = maybe_import_map
370-
{
371-
let mut specifiers = IndexSet::new();
372-
for key in import_map.imports().keys() {
373-
// for some reason, the import_map stores keys that begin with `/` as
374-
// `file:///` in its index, so we have to reverse that here
375-
let key = if key.starts_with("file://") {
376-
FILE_PROTO_RE.replace(key, "").to_string()
377-
} else {
378-
key.to_string()
379-
};
380-
if key.starts_with(text) && key != text {
381-
specifiers.insert(key.trim_end_matches('/').to_string());
382-
}
383-
}
384-
if !specifiers.is_empty() {
385-
let items = specifiers
386-
.into_iter()
387-
.map(|specifier| lsp::CompletionItem {
388-
label: specifier.clone(),
389-
kind: Some(lsp::CompletionItemKind::FILE),
390-
detail: Some("(import map)".to_string()),
391-
sort_text: Some("1".to_string()),
392-
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
393-
range: *range,
394-
new_text: specifier,
395-
})),
396-
commit_characters: Some(
397-
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect(),
398-
),
399-
..Default::default()
400-
})
401-
.collect();
402-
return Some(CompletionList {
403-
items,
404-
is_incomplete: false,
405-
});
368+
import_map: &ImportMap,
369+
) -> Vec<CompletionItem> {
370+
if text.is_empty() {
371+
return vec![];
372+
}
373+
let mut specifiers = IndexSet::new();
374+
for key in import_map.imports().keys() {
375+
// for some reason, the import_map stores keys that begin with `/` as
376+
// `file:///` in its index, so we have to reverse that here
377+
let key = if key.starts_with("file://") {
378+
FILE_PROTO_RE.replace(key, "").to_string()
379+
} else {
380+
key.to_string()
381+
};
382+
if key.starts_with(text) && key != text {
383+
specifiers.insert(key.trim_end_matches('/').to_string());
406384
}
407385
}
408-
None
386+
if specifiers.is_empty() {
387+
return vec![];
388+
}
389+
specifiers
390+
.into_iter()
391+
.map(|specifier| lsp::CompletionItem {
392+
label: specifier.clone(),
393+
kind: Some(lsp::CompletionItemKind::FILE),
394+
detail: Some("(import map)".to_string()),
395+
sort_text: Some("1".to_string()),
396+
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
397+
range: *range,
398+
new_text: specifier,
399+
})),
400+
commit_characters: Some(
401+
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect(),
402+
),
403+
..Default::default()
404+
})
405+
.collect()
406+
}
407+
fn get_import_maps_completions<'i>(
408+
_specifier: &ModuleSpecifier,
409+
text: &str,
410+
range: &lsp::Range,
411+
import_maps: &[&ImportMap],
412+
) -> Option<CompletionList> {
413+
if text.is_empty() || import_maps.is_empty() {
414+
return None;
415+
}
416+
let mut out = CompletionList {
417+
is_incomplete: false,
418+
items: vec![],
419+
};
420+
421+
for import_map in import_maps {
422+
out.items.extend(get_import_map_completions(
423+
_specifier, text, range, import_map,
424+
))
425+
}
426+
Some(out)
409427
}
410428

411429
/// Return local completions that are relative to the base specifier.

cli/lsp/config.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1571,12 +1571,13 @@ impl ConfigData {
15711571
.map(|p| p.to_path_buf());
15721572

15731573
// Mark the import map as a watched file
1574-
if let Some(import_map_specifier) = member_dir
1574+
for import_map_specifier in member_dir
15751575
.workspace
1576-
.to_import_map_path()
1576+
.to_import_map_paths()
15771577
.ok()
1578+
.into_iter()
15781579
.flatten()
1579-
.and_then(|path| Url::from_file_path(path).ok())
1580+
.flat_map(|path| Url::from_file_path(path).ok())
15801581
{
15811582
add_watched_file(
15821583
import_map_specifier.clone(),
@@ -1586,12 +1587,12 @@ impl ConfigData {
15861587
let mut import_map_from_settings = {
15871588
let is_config_import_map = member_dir
15881589
.maybe_deno_json()
1589-
.map(|c| c.is_an_import_map() || c.json.import_map.is_some())
1590+
.map(|c| c.is_an_import_map() || !c.json.import_map.is_empty())
15901591
.or_else(|| {
15911592
member_dir
15921593
.workspace
15931594
.root_deno_json()
1594-
.map(|c| c.is_an_import_map() || c.json.import_map.is_some())
1595+
.map(|c| c.is_an_import_map() || !c.json.import_map.is_empty())
15951596
})
15961597
.unwrap_or(false);
15971598
if is_config_import_map {
@@ -1608,12 +1609,12 @@ impl ConfigData {
16081609
let specified_import_map = {
16091610
let is_config_import_map = member_dir
16101611
.maybe_deno_json()
1611-
.map(|c| c.is_an_import_map() || c.json.import_map.is_some())
1612+
.map(|c| c.is_an_import_map() || !c.json.import_map.is_empty())
16121613
.or_else(|| {
16131614
member_dir
16141615
.workspace
16151616
.root_deno_json()
1616-
.map(|c| c.is_an_import_map() || c.json.import_map.is_some())
1617+
.map(|c| c.is_an_import_map() || !c.json.import_map.is_empty())
16171618
})
16181619
.unwrap_or(false);
16191620
if is_config_import_map {

0 commit comments

Comments
 (0)