Skip to content

Commit

Permalink
feat(lsp): add import completions (#9821)
Browse files Browse the repository at this point in the history
  • Loading branch information
kitsonk authored Mar 25, 2021
1 parent d6d5ced commit 5ebb401
Show file tree
Hide file tree
Showing 10 changed files with 1,021 additions and 185 deletions.
4 changes: 2 additions & 2 deletions cli/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,8 @@ fn strip_config_from_emit_options(
pub struct ParsedModule {
comments: SingleThreadedComments,
leading_comments: Vec<Comment>,
module: Module,
source_map: Rc<SourceMap>,
pub module: Module,
pub source_map: Rc<SourceMap>,
source_file: Rc<SourceFile>,
}

Expand Down
167 changes: 88 additions & 79 deletions cli/lsp/analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,100 +235,105 @@ pub fn resolve_import(
ResolvedDependency::Resolved(specifier)
}

pub fn parse_module(
specifier: &ModuleSpecifier,
source: &str,
media_type: &MediaType,
) -> Result<ast::ParsedModule, AnyError> {
let source_map = Rc::new(swc_common::SourceMap::default());
ast::parse_with_source_map(
&specifier.to_string(),
source,
&media_type,
source_map,
)
}

// TODO(@kitsonk) a lot of this logic is duplicated in module_graph.rs in
// Module::parse() and should be refactored out to a common function.
pub fn analyze_dependencies(
specifier: &ModuleSpecifier,
source: &str,
media_type: &MediaType,
parsed_module: &ast::ParsedModule,
maybe_import_map: &Option<ImportMap>,
) -> Option<(HashMap<String, Dependency>, Option<ResolvedDependency>)> {
let specifier_str = specifier.to_string();
let source_map = Rc::new(swc_common::SourceMap::default());
) -> (HashMap<String, Dependency>, Option<ResolvedDependency>) {
let mut maybe_type = None;
if let Ok(parsed_module) =
ast::parse_with_source_map(&specifier_str, source, &media_type, source_map)
{
let mut dependencies = HashMap::<String, Dependency>::new();

// Parse leading comments for supported triple slash references.
for comment in parsed_module.get_leading_comments().iter() {
if let Some(ts_reference) = parse_ts_reference(&comment.text) {
match ts_reference {
TypeScriptReference::Path(import) => {
let dep = dependencies.entry(import.clone()).or_default();
let resolved_import =
resolve_import(&import, specifier, maybe_import_map);
dep.maybe_code = Some(resolved_import);
}
TypeScriptReference::Types(import) => {
let resolved_import =
resolve_import(&import, specifier, maybe_import_map);
if media_type == &MediaType::JavaScript
|| media_type == &MediaType::JSX
{
maybe_type = Some(resolved_import)
} else {
let dep = dependencies.entry(import).or_default();
dep.maybe_type = Some(resolved_import);
}
let mut dependencies = HashMap::<String, Dependency>::new();

// Parse leading comments for supported triple slash references.
for comment in parsed_module.get_leading_comments().iter() {
if let Some(ts_reference) = parse_ts_reference(&comment.text) {
match ts_reference {
TypeScriptReference::Path(import) => {
let dep = dependencies.entry(import.clone()).or_default();
let resolved_import =
resolve_import(&import, specifier, maybe_import_map);
dep.maybe_code = Some(resolved_import);
}
TypeScriptReference::Types(import) => {
let resolved_import =
resolve_import(&import, specifier, maybe_import_map);
if media_type == &MediaType::JavaScript
|| media_type == &MediaType::JSX
{
maybe_type = Some(resolved_import)
} else {
let dep = dependencies.entry(import).or_default();
dep.maybe_type = Some(resolved_import);
}
}
}
}
}

// Parse ES and type only imports
let descriptors = parsed_module.analyze_dependencies();
for desc in descriptors.into_iter().filter(|desc| {
desc.kind != swc_ecmascript::dep_graph::DependencyKind::Require
}) {
let resolved_import =
resolve_import(&desc.specifier, specifier, maybe_import_map);

let maybe_resolved_type_dependency =
// Check for `@deno-types` pragmas that affect the import
if let Some(comment) = desc.leading_comments.last() {
if let Some(deno_types) = parse_deno_types(&comment.text).as_ref() {
Some(resolve_import(deno_types, specifier, maybe_import_map))
} else {
None
}
// Parse ES and type only imports
let descriptors = parsed_module.analyze_dependencies();
for desc in descriptors.into_iter().filter(|desc| {
desc.kind != swc_ecmascript::dep_graph::DependencyKind::Require
}) {
let resolved_import =
resolve_import(&desc.specifier, specifier, maybe_import_map);

let maybe_resolved_type_dependency =
// Check for `@deno-types` pragmas that affect the import
if let Some(comment) = desc.leading_comments.last() {
if let Some(deno_types) = parse_deno_types(&comment.text).as_ref() {
Some(resolve_import(deno_types, specifier, maybe_import_map))
} else {
None
};

let dep = dependencies.entry(desc.specifier.to_string()).or_default();
dep.is_dynamic = desc.is_dynamic;
match desc.kind {
swc_ecmascript::dep_graph::DependencyKind::ExportType
| swc_ecmascript::dep_graph::DependencyKind::ImportType => {
dep.maybe_type = Some(resolved_import)
}
_ => {
dep.maybe_code_specifier_range = Some(Range {
start: Position {
line: (desc.specifier_line - 1) as u32,
character: desc.specifier_col as u32,
},
end: Position {
line: (desc.specifier_line - 1) as u32,
character: (desc.specifier_col
+ desc.specifier.chars().count()
+ 2) as u32,
},
});
dep.maybe_code = Some(resolved_import);
}
} else {
None
};

let dep = dependencies.entry(desc.specifier.to_string()).or_default();
dep.is_dynamic = desc.is_dynamic;
match desc.kind {
swc_ecmascript::dep_graph::DependencyKind::ExportType
| swc_ecmascript::dep_graph::DependencyKind::ImportType => {
dep.maybe_type = Some(resolved_import)
}
if maybe_resolved_type_dependency.is_some() && dep.maybe_type.is_none() {
dep.maybe_type = maybe_resolved_type_dependency;
_ => {
dep.maybe_code_specifier_range = Some(Range {
start: Position {
line: (desc.specifier_line - 1) as u32,
character: desc.specifier_col as u32,
},
end: Position {
line: (desc.specifier_line - 1) as u32,
character: (desc.specifier_col + desc.specifier.chars().count() + 2)
as u32,
},
});
dep.maybe_code = Some(resolved_import);
}
}

Some((dependencies, maybe_type))
} else {
None
if maybe_resolved_type_dependency.is_some() && dep.maybe_type.is_none() {
dep.maybe_type = maybe_resolved_type_dependency;
}
}

(dependencies, maybe_type)
}

#[derive(Debug, Deserialize, Serialize)]
Expand Down Expand Up @@ -695,10 +700,14 @@ mod tests {
// @deno-types="https://deno.land/x/types/react/index.d.ts";
import * as React from "https://cdn.skypack.dev/react";
"#;
let actual =
analyze_dependencies(&specifier, source, &MediaType::TypeScript, &None);
assert!(actual.is_some());
let (actual, maybe_type) = actual.unwrap();
let parsed_module =
parse_module(&specifier, source, &MediaType::TypeScript).unwrap();
let (actual, maybe_type) = analyze_dependencies(
&specifier,
&MediaType::TypeScript,
&parsed_module,
&None,
);
assert!(maybe_type.is_none());
assert_eq!(actual.len(), 2);
assert_eq!(
Expand Down
Loading

0 comments on commit 5ebb401

Please sign in to comment.