summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
Diffstat (limited to 'cli')
-rw-r--r--cli/build.rs4
-rw-r--r--cli/config_file.rs9
-rw-r--r--cli/dts/lib.deno.unstable.d.ts20
-rw-r--r--cli/lsp/config.rs5
-rw-r--r--cli/lsp/language_server.rs33
-rw-r--r--cli/lsp/sources.rs3
-rw-r--r--cli/lsp/tsc.rs181
-rw-r--r--cli/main.rs12
-rw-r--r--cli/module_graph.rs117
-rw-r--r--cli/ops/runtime_compiler.rs3
-rw-r--r--cli/program_state.rs2
-rw-r--r--cli/tests/compiler_api_test.ts50
-rw-r--r--cli/tests/config_types.ts1
-rw-r--r--cli/tests/config_types.ts.out1
-rw-r--r--cli/tests/config_types.tsconfig.json7
-rw-r--r--cli/tests/config_types_remote.tsconfig.json7
-rw-r--r--cli/tests/integration_tests.rs25
-rw-r--r--cli/tests/integration_tests_lsp.rs85
-rw-r--r--cli/tests/lsp/a.d.ts1
-rw-r--r--cli/tests/lsp/b.d.ts1
-rw-r--r--cli/tests/lsp/types.tsconfig.json7
-rw-r--r--cli/tests/reference_types.ts3
-rw-r--r--cli/tests/reference_types.ts.out1
-rw-r--r--cli/tests/reference_types_remote.ts3
-rw-r--r--cli/tests/reference_types_remote.ts.out1
-rw-r--r--cli/tests/subdir/types.d.ts1
-rw-r--r--cli/tools/doc.rs3
-rw-r--r--cli/tsc.rs55
-rw-r--r--cli/tsc/99_main_compiler.js19
29 files changed, 568 insertions, 92 deletions
diff --git a/cli/build.rs b/cli/build.rs
index c88c00376..8e15ef443 100644
--- a/cli/build.rs
+++ b/cli/build.rs
@@ -163,6 +163,10 @@ fn create_compiler_snapshot(
}))
}),
);
+ js_runtime.register_op(
+ "op_cwd",
+ op_sync(move |_state, _args: Value, _: ()| Ok(json!("cache:///"))),
+ );
// using the same op that is used in `tsc.rs` for loading modules and reading
// files, but a slightly different implementation at build time.
js_runtime.register_op(
diff --git a/cli/config_file.rs b/cli/config_file.rs
index a8bd40e69..5ea92447b 100644
--- a/cli/config_file.rs
+++ b/cli/config_file.rs
@@ -31,6 +31,14 @@ pub struct EmitConfigOptions {
pub jsx_fragment_factory: String,
}
+/// There are certain compiler options that can impact what modules are part of
+/// a module graph, which need to be deserialized into a structure for analysis.
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CompilerOptions {
+ pub types: Option<Vec<String>>,
+}
+
/// A structure that represents a set of options that were ignored and the
/// path those options came from.
#[derive(Debug, Clone, PartialEq)]
@@ -90,7 +98,6 @@ pub const IGNORED_COMPILER_OPTIONS: &[&str] = &[
"sourceMap",
"sourceRoot",
"target",
- "types",
"useDefineForClassFields",
];
diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts
index 42f3c0202..92858ba31 100644
--- a/cli/dts/lib.deno.unstable.d.ts
+++ b/cli/dts/lib.deno.unstable.d.ts
@@ -422,11 +422,25 @@ declare namespace Deno {
| "es2019"
| "es2020"
| "esnext";
- /** List of names of type definitions to include. Defaults to `undefined`.
+ /** List of names of type definitions to include when type checking.
+ * Defaults to `undefined`.
*
* The type definitions are resolved according to the normal Deno resolution
- * irrespective of if sources are provided on the call. Like other Deno
- * modules, there is no "magical" resolution. For example:
+ * irrespective of if sources are provided on the call. In addition, unlike
+ * passing the `--config` option on startup, there is no base to resolve
+ * relative specifiers, so the specifiers here have to be fully qualified
+ * URLs or paths. For example:
+ *
+ * ```ts
+ * Deno.emit("./a.ts", {
+ * compilerOptions: {
+ * types: [
+ * "https://deno.land/x/pkg/types.d.ts",
+ * "/Users/me/pkg/types.d.ts",
+ * ]
+ * }
+ * });
+ * ```
*/
types?: string[];
/** Emit class fields with ECMAScript-standard semantics. Defaults to
diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs
index ca2a535ef..0d3bf748e 100644
--- a/cli/lsp/config.rs
+++ b/cli/lsp/config.rs
@@ -10,6 +10,7 @@ use deno_core::serde_json::Value;
use deno_core::url::Url;
use deno_core::ModuleSpecifier;
use log::error;
+use lsp::WorkspaceFolder;
use lspower::lsp;
use std::collections::BTreeMap;
use std::collections::HashMap;
@@ -188,6 +189,7 @@ pub struct ConfigSnapshot {
pub client_capabilities: ClientCapabilities,
pub root_uri: Option<Url>,
pub settings: Settings,
+ pub workspace_folders: Option<Vec<lsp::WorkspaceFolder>>,
}
impl ConfigSnapshot {
@@ -218,6 +220,7 @@ pub struct Config {
pub root_uri: Option<Url>,
settings: Arc<RwLock<Settings>>,
tx: mpsc::Sender<ConfigRequest>,
+ pub workspace_folders: Option<Vec<WorkspaceFolder>>,
}
impl Config {
@@ -319,6 +322,7 @@ impl Config {
root_uri: None,
settings,
tx,
+ workspace_folders: None,
}
}
@@ -343,6 +347,7 @@ impl Config {
.try_read()
.map_err(|_| anyhow!("Error reading settings."))?
.clone(),
+ workspace_folders: self.workspace_folders.clone(),
})
}
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index 491e402ad..00f49b05d 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -70,6 +70,7 @@ pub struct StateSnapshot {
pub assets: Assets,
pub config: ConfigSnapshot,
pub documents: DocumentCache,
+ pub maybe_config_uri: Option<ModuleSpecifier>,
pub module_registries: registries::ModuleRegistry,
pub performance: Performance,
pub sources: Sources,
@@ -92,6 +93,9 @@ pub(crate) struct Inner {
module_registries: registries::ModuleRegistry,
/// The path to the module registries cache
module_registries_location: PathBuf,
+ /// An optional configuration file which has been specified in the client
+ /// options.
+ maybe_config_file: Option<ConfigFile>,
/// An optional URL which provides the location of a TypeScript configuration
/// file which will be used by the Deno LSP.
maybe_config_uri: Option<Url>,
@@ -138,6 +142,7 @@ impl Inner {
config,
diagnostics_server,
documents: Default::default(),
+ maybe_config_file: Default::default(),
maybe_config_uri: Default::default(),
maybe_import_map: Default::default(),
maybe_import_map_uri: Default::default(),
@@ -326,6 +331,7 @@ impl Inner {
LspError::internal_error()
})?,
documents: self.documents.clone(),
+ maybe_config_uri: self.maybe_config_uri.clone(),
module_registries: self.module_registries.clone(),
performance: self.performance.clone(),
sources: self.sources.clone(),
@@ -477,6 +483,7 @@ impl Inner {
};
let (value, maybe_ignored_options) = config_file.as_compiler_options()?;
tsconfig.merge(&value);
+ self.maybe_config_file = Some(config_file);
self.maybe_config_uri = Some(config_url);
if let Some(ignored_options) = maybe_ignored_options {
// TODO(@kitsonk) turn these into diagnostics that can be sent to the
@@ -2281,20 +2288,28 @@ impl Inner {
if !params.uris.is_empty() {
for identifier in &params.uris {
let specifier = self.url_map.normalize_url(&identifier.uri);
- sources::cache(&specifier, &self.maybe_import_map)
- .await
- .map_err(|err| {
- error!("{}", err);
- LspError::internal_error()
- })?;
- }
- } else {
- sources::cache(&referrer, &self.maybe_import_map)
+ sources::cache(
+ &specifier,
+ &self.maybe_import_map,
+ &self.maybe_config_file,
+ )
.await
.map_err(|err| {
error!("{}", err);
LspError::internal_error()
})?;
+ }
+ } else {
+ sources::cache(
+ &referrer,
+ &self.maybe_import_map,
+ &self.maybe_config_file,
+ )
+ .await
+ .map_err(|err| {
+ error!("{}", err);
+ LspError::internal_error()
+ })?;
}
// now that we have dependencies loaded, we need to re-analyze them and
// invalidate some diagnostics
diff --git a/cli/lsp/sources.rs b/cli/lsp/sources.rs
index 74e1a6a60..37f8b6bce 100644
--- a/cli/lsp/sources.rs
+++ b/cli/lsp/sources.rs
@@ -4,6 +4,7 @@ use super::analysis;
use super::text::LineIndex;
use super::tsc;
+use crate::config_file::ConfigFile;
use crate::file_fetcher::get_source_from_bytes;
use crate::file_fetcher::map_content_type;
use crate::file_fetcher::SUPPORTED_SCHEMES;
@@ -33,6 +34,7 @@ use tsc::NavigationTree;
pub async fn cache(
specifier: &ModuleSpecifier,
maybe_import_map: &Option<ImportMap>,
+ maybe_config_file: &Option<ConfigFile>,
) -> Result<(), AnyError> {
let program_state = Arc::new(ProgramState::build(Default::default()).await?);
let handler = Arc::new(Mutex::new(FetchHandler::new(
@@ -41,6 +43,7 @@ pub async fn cache(
Permissions::allow_all(),
)?));
let mut builder = GraphBuilder::new(handler, maybe_import_map.clone(), None);
+ builder.analyze_config_file(maybe_config_file).await?;
builder.add(specifier, false).await
}
diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs
index 359f1f24b..83958fde2 100644
--- a/cli/lsp/tsc.rs
+++ b/cli/lsp/tsc.rs
@@ -61,14 +61,18 @@ impl TsServer {
pub fn new() -> Self {
let (tx, mut rx) = mpsc::unbounded_channel::<Request>();
let _join_handle = thread::spawn(move || {
- // TODO(@kitsonk) we need to allow displaying diagnostics here, but the
- // current compiler snapshot sends them to stdio which would totally break
- // the language server...
- let mut ts_runtime = start(false).expect("could not start tsc");
+ let mut ts_runtime = load().expect("could not load tsc");
let runtime = create_basic_runtime();
runtime.block_on(async {
+ let mut started = false;
while let Some((req, state_snapshot, tx)) = rx.recv().await {
+ if !started {
+ // TODO(@kitsonk) need to reflect the debug state of the lsp here
+ start(&mut ts_runtime, false, &state_snapshot)
+ .expect("could not start tsc");
+ started = true;
+ }
let value = request(&mut ts_runtime, state_snapshot, req);
if tx.send(value).is_err() {
warn!("Unable to send result to client.");
@@ -572,7 +576,7 @@ impl DocumentSpan {
line_index: &LineIndex,
language_server: &mut language_server::Inner,
) -> Option<lsp::LocationLink> {
- let target_specifier = resolve_url(&self.file_name).unwrap();
+ let target_specifier = normalize_specifier(&self.file_name).unwrap();
let target_line_index = language_server
.get_line_index(target_specifier.clone())
.await
@@ -773,7 +777,7 @@ impl ImplementationLocation {
line_index: &LineIndex,
language_server: &mut language_server::Inner,
) -> lsp::Location {
- let specifier = resolve_url(&self.document_span.file_name).unwrap();
+ let specifier = normalize_specifier(&self.document_span.file_name).unwrap();
let uri = language_server
.url_map
.normalize_specifier(&specifier)
@@ -819,7 +823,7 @@ impl RenameLocations {
let mut text_document_edit_map: HashMap<Url, lsp::TextDocumentEdit> =
HashMap::new();
for location in self.locations.iter() {
- let specifier = resolve_url(&location.document_span.file_name)?;
+ let specifier = normalize_specifier(&location.document_span.file_name)?;
let uri = language_server.url_map.normalize_specifier(&specifier)?;
// ensure TextDocumentEdit for `location.file_name`.
@@ -982,7 +986,7 @@ impl FileTextChanges {
&self,
language_server: &mut language_server::Inner,
) -> Result<lsp::TextDocumentEdit, AnyError> {
- let specifier = resolve_url(&self.file_name)?;
+ let specifier = normalize_specifier(&self.file_name)?;
let line_index = language_server.get_line_index(specifier.clone()).await?;
let edits = self
.text_changes
@@ -1102,7 +1106,7 @@ impl ReferenceEntry {
line_index: &LineIndex,
language_server: &mut language_server::Inner,
) -> lsp::Location {
- let specifier = resolve_url(&self.document_span.file_name).unwrap();
+ let specifier = normalize_specifier(&self.document_span.file_name).unwrap();
let uri = language_server
.url_map
.normalize_specifier(&specifier)
@@ -1134,7 +1138,7 @@ impl CallHierarchyItem {
language_server: &mut language_server::Inner,
maybe_root_path: Option<&Path>,
) -> Option<lsp::CallHierarchyItem> {
- let target_specifier = resolve_url(&self.file).unwrap();
+ let target_specifier = normalize_specifier(&self.file).unwrap();
let target_line_index = language_server
.get_line_index(target_specifier)
.await
@@ -1153,7 +1157,7 @@ impl CallHierarchyItem {
language_server: &mut language_server::Inner,
maybe_root_path: Option<&Path>,
) -> lsp::CallHierarchyItem {
- let target_specifier = resolve_url(&self.file).unwrap();
+ let target_specifier = normalize_specifier(&self.file).unwrap();
let uri = language_server
.url_map
.normalize_specifier(&target_specifier)
@@ -1234,7 +1238,7 @@ impl CallHierarchyIncomingCall {
language_server: &mut language_server::Inner,
maybe_root_path: Option<&Path>,
) -> Option<lsp::CallHierarchyIncomingCall> {
- let target_specifier = resolve_url(&self.from.file).unwrap();
+ let target_specifier = normalize_specifier(&self.from.file).unwrap();
let target_line_index = language_server
.get_line_index(target_specifier)
.await
@@ -1269,7 +1273,7 @@ impl CallHierarchyOutgoingCall {
language_server: &mut language_server::Inner,
maybe_root_path: Option<&Path>,
) -> Option<lsp::CallHierarchyOutgoingCall> {
- let target_specifier = resolve_url(&self.to.file).unwrap();
+ let target_specifier = normalize_specifier(&self.to.file).unwrap();
let target_line_index = language_server
.get_line_index(target_specifier)
.await
@@ -1803,6 +1807,7 @@ struct State<'a> {
response: Option<Response>,
state_snapshot: StateSnapshot,
snapshots: HashMap<(ModuleSpecifier, Cow<'a, str>), String>,
+ specifiers: HashMap<String, String>,
}
impl<'a> State<'a> {
@@ -1812,8 +1817,36 @@ impl<'a> State<'a> {
response: None,
state_snapshot,
snapshots: HashMap::default(),
+ specifiers: HashMap::default(),
}
}
+
+ /// If a normalized version of the specifier has been stored for tsc, this
+ /// will "restore" it for communicating back to the tsc language server,
+ /// otherwise it will just convert the specifier to a string.
+ fn denormalize_specifier(&self, specifier: &ModuleSpecifier) -> String {
+ let specifier_str = specifier.to_string();
+ self
+ .specifiers
+ .get(&specifier_str)
+ .unwrap_or(&specifier_str)
+ .to_string()
+ }
+
+ /// In certain situations, tsc can request "invalid" specifiers and this will
+ /// normalize and memoize the specifier.
+ fn normalize_specifier<S: AsRef<str>>(
+ &mut self,
+ specifier: S,
+ ) -> Result<ModuleSpecifier, AnyError> {
+ let specifier_str = specifier.as_ref().replace(".d.ts.d.ts", ".d.ts");
+ if specifier_str != specifier.as_ref() {
+ self
+ .specifiers
+ .insert(specifier_str.clone(), specifier.as_ref().to_string());
+ }
+ ModuleSpecifier::parse(&specifier_str).map_err(|err| err.into())
+ }
}
/// If a snapshot is missing from the state cache, add it.
@@ -1846,6 +1879,13 @@ fn cache_snapshot(
Ok(())
}
+fn normalize_specifier<S: AsRef<str>>(
+ specifier: S,
+) -> Result<ModuleSpecifier, AnyError> {
+ resolve_url(specifier.as_ref().replace(".d.ts.d.ts", ".d.ts").as_str())
+ .map_err(|err| err.into())
+}
+
// buffer-less json_sync ops
fn op<F, V, R>(op_fn: F) -> Box<OpFn>
where
@@ -1876,7 +1916,7 @@ fn op_dispose(
.state_snapshot
.performance
.mark("op_dispose", Some(&args));
- let specifier = resolve_url(&args.specifier)?;
+ let specifier = state.normalize_specifier(&args.specifier)?;
state.snapshots.remove(&(specifier, args.version.into()));
state.state_snapshot.performance.measure(mark);
Ok(true)
@@ -1884,6 +1924,23 @@ fn op_dispose(
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
+struct SpecifierArgs {
+ specifier: String,
+}
+
+fn op_exists(state: &mut State, args: SpecifierArgs) -> Result<bool, AnyError> {
+ let mark = state
+ .state_snapshot
+ .performance
+ .mark("op_exists", Some(&args));
+ let specifier = state.normalize_specifier(args.specifier)?;
+ let result = state.state_snapshot.sources.contains_key(&specifier);
+ state.state_snapshot.performance.measure(mark);
+ Ok(result)
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
struct GetChangeRangeArgs {
specifier: String,
old_length: u32,
@@ -1901,7 +1958,7 @@ fn op_get_change_range(
.state_snapshot
.performance
.mark("op_get_change_range", Some(&args));
- let specifier = resolve_url(&args.specifier)?;
+ let specifier = state.normalize_specifier(&args.specifier)?;
cache_snapshot(state, &specifier, args.version.clone())?;
let r = if let Some(current) = state
.snapshots
@@ -1948,7 +2005,7 @@ fn op_get_length(
.state_snapshot
.performance
.mark("op_get_length", Some(&args));
- let specifier = resolve_url(&args.specifier)?;
+ let specifier = state.normalize_specifier(args.specifier)?;
let r = if let Some(Some(asset)) = state.state_snapshot.assets.get(&specifier)
{
Ok(asset.length)
@@ -1981,7 +2038,7 @@ fn op_get_text(
.state_snapshot
.performance
.mark("op_get_text", Some(&args));
- let specifier = resolve_url(&args.specifier)?;
+ let specifier = state.normalize_specifier(args.specifier)?;
let content =
if let Some(Some(content)) = state.state_snapshot.assets.get(&specifier) {
content.text.clone()
@@ -1997,6 +2054,20 @@ fn op_get_text(
Ok(text::slice(&content, args.start..args.end).to_string())
}
+fn op_load(
+ state: &mut State,
+ args: SpecifierArgs,
+) -> Result<Option<String>, AnyError> {
+ let mark = state
+ .state_snapshot
+ .performance
+ .mark("op_load", Some(&args));
+ let specifier = state.normalize_specifier(args.specifier)?;
+ let result = state.state_snapshot.sources.get_source(&specifier);
+ state.state_snapshot.performance.measure(mark);
+ Ok(result)
+}
+
fn op_resolve(
state: &mut State,
args: ResolveArgs,
@@ -2006,7 +2077,7 @@ fn op_resolve(
.performance
.mark("op_resolve", Some(&args));
let mut resolved = Vec::new();
- let referrer = resolve_url(&args.base)?;
+ let referrer = state.normalize_specifier(&args.base)?;
let sources = &mut state.state_snapshot.sources;
if state.state_snapshot.documents.contains_key(&referrer) {
@@ -2124,7 +2195,7 @@ fn op_script_version(
.state_snapshot
.performance
.mark("op_script_version", Some(&args));
- let specifier = resolve_url(&args.specifier)?;
+ let specifier = state.normalize_specifier(args.specifier)?;
let r = if specifier.scheme() == "asset" {
if state.state_snapshot.assets.contains_key(&specifier) {
Ok(Some("1".to_string()))
@@ -2151,7 +2222,7 @@ fn op_script_version(
/// Create and setup a JsRuntime based on a snapshot. It is expected that the
/// supplied snapshot is an isolate that contains the TypeScript language
/// server.
-pub fn start(debug: bool) -> Result<JsRuntime, AnyError> {
+fn load() -> Result<JsRuntime, AnyError> {
let mut runtime = JsRuntime::new(RuntimeOptions {
startup_snapshot: Some(tsc::compiler_snapshot()),
..Default::default()
@@ -2164,20 +2235,36 @@ pub fn start(debug: bool) -> Result<JsRuntime, AnyError> {
}
runtime.register_op("op_dispose", op(op_dispose));
+ runtime.register_op("op_exists", op(op_exists));
runtime.register_op("op_get_change_range", op(op_get_change_range));
runtime.register_op("op_get_length", op(op_get_length));
runtime.register_op("op_get_text", op(op_get_text));
+ runtime.register_op("op_load", op(op_load));
runtime.register_op("op_resolve", op(op_resolve));
runtime.register_op("op_respond", op(op_respond));
runtime.register_op("op_script_names", op(op_script_names));
runtime.register_op("op_script_version", op(op_script_version));
runtime.sync_ops_cache();
- let init_config = json!({ "debug": debug });
+ Ok(runtime)
+}
+
+/// Instruct a language server runtime to start the language server and provide
+/// it with a minimal bootstrap configuration.
+fn start(
+ runtime: &mut JsRuntime,
+ debug: bool,
+ state_snapshot: &StateSnapshot,
+) -> Result<(), AnyError> {
+ let root_uri = state_snapshot
+ .config
+ .root_uri
+ .clone()
+ .unwrap_or_else(|| Url::parse("cache:///").unwrap());
+ let init_config = json!({ "debug": debug, "rootUri": root_uri });
let init_src = format!("globalThis.serverInit({});", init_config);
- runtime.execute("[native code]", &init_src)?;
- Ok(runtime)
+ runtime.execute("[native code]", &init_src)
}
#[derive(Debug, Serialize)]
@@ -2369,7 +2456,7 @@ pub enum RequestMethod {
}
impl RequestMethod {
- pub fn to_value(&self, id: usize) -> Value {
+ fn to_value(&self, state: &State, id: usize) -> Value {
match self {
RequestMethod::Configure(config) => json!({
"id": id,
@@ -2386,7 +2473,7 @@ impl RequestMethod {
json!({
"id": id,
"method": "findRenameLocations",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position,
"findInStrings": find_in_strings,
"findInComments": find_in_comments,
@@ -2406,7 +2493,7 @@ impl RequestMethod {
)) => json!({
"id": id,
"method": "getCodeFixes",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"startPosition": start_pos,
"endPosition": end_pos,
"errorCodes": error_codes,
@@ -2414,7 +2501,7 @@ impl RequestMethod {
RequestMethod::GetCombinedCodeFix((specifier, fix_id)) => json!({
"id": id,
"method": "getCombinedCodeFix",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"fixId": fix_id,
}),
RequestMethod::GetCompletionDetails(args) => json!({
@@ -2426,7 +2513,7 @@ impl RequestMethod {
json!({
"id": id,
"method": "getCompletions",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position,
"preferences": preferences,
})
@@ -2434,13 +2521,13 @@ impl RequestMethod {
RequestMethod::GetDefinition((specifier, position)) => json!({
"id": id,
"method": "getDefinition",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position,
}),
RequestMethod::GetDiagnostics(specifiers) => json!({
"id": id,
"method": "getDiagnostics",
- "specifiers": specifiers,
+ "specifiers": specifiers.iter().map(|s| state.denormalize_specifier(s)).collect::<Vec<String>>(),
}),
RequestMethod::GetDocumentHighlights((
specifier,
@@ -2449,7 +2536,7 @@ impl RequestMethod {
)) => json!({
"id": id,
"method": "getDocumentHighlights",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position,
"filesToSearch": files_to_search,
}),
@@ -2457,43 +2544,43 @@ impl RequestMethod {
json!({
"id": id,
"method": "getEncodedSemanticClassifications",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"span": span,
})
}
RequestMethod::GetImplementation((specifier, position)) => json!({
"id": id,
"method": "getImplementation",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position,
}),
RequestMethod::GetNavigationTree(specifier) => json!({
"id": id,
"method": "getNavigationTree",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
}),
RequestMethod::GetOutliningSpans(specifier) => json!({
"id": id,
"method": "getOutliningSpans",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
}),
RequestMethod::GetQuickInfo((specifier, position)) => json!({
"id": id,
"method": "getQuickInfo",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position,
}),
RequestMethod::GetReferences((specifier, position)) => json!({
"id": id,
"method": "getReferences",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position,
}),
RequestMethod::GetSignatureHelpItems((specifier, position, options)) => {
json!({
"id": id,
"method": "getSignatureHelpItems",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position,
"options": options,
})
@@ -2502,7 +2589,7 @@ impl RequestMethod {
json!({
"id": id,
"method": "getSmartSelectionRange",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position
})
}
@@ -2514,7 +2601,7 @@ impl RequestMethod {
json!({
"id": id,
"method": "prepareCallHierarchy",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position
})
}
@@ -2525,7 +2612,7 @@ impl RequestMethod {
json!({
"id": id,
"method": "provideCallHierarchyIncomingCalls",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position
})
}
@@ -2536,7 +2623,7 @@ impl RequestMethod {
json!({
"id": id,
"method": "provideCallHierarchyOutgoingCalls",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position
})
}
@@ -2551,15 +2638,15 @@ pub fn request(
method: RequestMethod,
) -> Result<Value, AnyError> {
let performance = state_snapshot.performance.clone();
- let id = {
+ let request_params = {
let op_state = runtime.op_state();
let mut op_state = op_state.borrow_mut();
let state = op_state.borrow_mut::<State>();
state.state_snapshot = state_snapshot;
state.last_id += 1;
- state.last_id
+ let id = state.last_id;
+ method.to_value(state, id)
};
- let request_params = method.to_value(id);
let mark = performance.mark("request", Some(request_params.clone()));
let request_src = format!("globalThis.serverRequest({});", request_params);
runtime.execute("[native_code]", &request_src)?;
@@ -2632,7 +2719,9 @@ mod tests {
let temp_dir = TempDir::new().expect("could not create temp dir");
let location = temp_dir.path().join("deps");
let state_snapshot = mock_state_snapshot(sources, &location);
- let mut runtime = start(debug).expect("could not start server");
+ let mut runtime = load().expect("could not start server");
+ start(&mut runtime, debug, &state_snapshot)
+ .expect("could not start server");
let ts_config = TsConfig::new(config);
assert_eq!(
request(
diff --git a/cli/main.rs b/cli/main.rs
index 92abf9778..20f9131bf 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -427,6 +427,9 @@ async fn info_command(
program_state.lockfile.clone(),
);
builder.add(&specifier, false).await?;
+ builder
+ .analyze_config_file(&program_state.maybe_config_file)
+ .await?;
let graph = builder.get_graph();
let info = graph.info()?;
@@ -575,6 +578,9 @@ async fn create_module_graph_and_maybe_check(
program_state.lockfile.clone(),
);
builder.add(&module_specifier, false).await?;
+ builder
+ .analyze_config_file(&program_state.maybe_config_file)
+ .await?;
let module_graph = builder.get_graph();
if !program_state.flags.no_check {
@@ -813,6 +819,9 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<(), AnyError> {
program_state.lockfile.clone(),
);
builder.add(&main_module, false).await?;
+ builder
+ .analyze_config_file(&program_state.maybe_config_file)
+ .await?;
let module_graph = builder.get_graph();
// Find all local files in graph
@@ -1024,6 +1033,9 @@ async fn test_command(
for specifier in test_modules.iter() {
builder.add(specifier, false).await?;
}
+ builder
+ .analyze_config_file(&program_state.maybe_config_file)
+ .await?;
let graph = builder.get_graph();
for specifier in doc_modules {
diff --git a/cli/module_graph.rs b/cli/module_graph.rs
index 770f8b872..8ea8b7f88 100644
--- a/cli/module_graph.rs
+++ b/cli/module_graph.rs
@@ -7,6 +7,7 @@ use crate::ast::Location;
use crate::ast::ParsedModule;
use crate::checksum;
use crate::colors;
+use crate::config_file::CompilerOptions;
use crate::config_file::ConfigFile;
use crate::config_file::IgnoredCompilerOptions;
use crate::config_file::TsConfig;
@@ -31,11 +32,13 @@ use deno_core::error::AnyError;
use deno_core::error::Context;
use deno_core::futures::stream::FuturesUnordered;
use deno_core::futures::stream::StreamExt;
+use deno_core::resolve_import;
use deno_core::resolve_url_or_path;
use deno_core::serde::Deserialize;
use deno_core::serde::Deserializer;
use deno_core::serde::Serialize;
use deno_core::serde::Serializer;
+use deno_core::serde_json;
use deno_core::serde_json::json;
use deno_core::serde_json::Value;
use deno_core::url::Url;
@@ -890,11 +893,20 @@ impl Graph {
vec![config.as_bytes(), version::deno().as_bytes().to_owned()];
let graph = Arc::new(Mutex::new(self));
+ let maybe_config_specifier =
+ if let Some(config_file) = &options.maybe_config_file {
+ ModuleSpecifier::from_file_path(&config_file.path).ok()
+ } else {
+ None
+ };
+ debug!("maybe_config_specifier: {:?}", maybe_config_specifier);
+
let response = tsc::exec(tsc::Request {
config: config.clone(),
debug: options.debug,
graph: graph.clone(),
hash_data,
+ maybe_config_specifier,
maybe_tsbuildinfo,
root_names,
})?;
@@ -958,6 +970,11 @@ impl Graph {
})
}
+ /// Indicates if the module graph contains the supplied specifier or not.
+ pub fn contains(&self, specifier: &ModuleSpecifier) -> bool {
+ matches!(self.get_module(specifier), ModuleSlot::Module(_))
+ }
+
/// Emit the module graph in a specific format. This is specifically designed
/// to be an "all-in-one" API for access by the runtime, allowing both
/// emitting single modules as well as bundles, using Deno module resolution
@@ -1025,6 +1042,7 @@ impl Graph {
debug: options.debug,
graph: graph.clone(),
hash_data,
+ maybe_config_specifier: None,
maybe_tsbuildinfo: None,
root_names,
})?;
@@ -1829,26 +1847,7 @@ impl GraphBuilder {
specifier: &ModuleSpecifier,
is_dynamic: bool,
) -> Result<(), AnyError> {
- self.fetch(specifier, &None, is_dynamic);
-
- loop {
- match self.pending.next().await {
- Some(Err((specifier, err))) => {
- self
- .graph
- .modules
- .insert(specifier, ModuleSlot::Err(Arc::new(err)));
- }
- Some(Ok(cached_module)) => {
- let is_root = &cached_module.specifier == specifier;
- self.visit(cached_module, is_root, is_dynamic)?;
- }
- _ => {}
- }
- if self.pending.is_empty() {
- break;
- }
- }
+ self.insert(specifier, is_dynamic).await?;
if !self.graph.roots.contains(specifier) {
self.graph.roots.push(specifier.clone());
@@ -1862,6 +1861,53 @@ impl GraphBuilder {
Ok(())
}
+ /// Analyze compiler options, identifying any specifiers that need to be
+ /// resolved and added to the graph.
+ pub async fn analyze_compiler_options(
+ &mut self,
+ maybe_compiler_options: &Option<HashMap<String, Value>>,
+ ) -> Result<(), AnyError> {
+ if let Some(user_config) = maybe_compiler_options {
+ if let Some(value) = user_config.get("types") {
+ let types: Vec<String> = serde_json::from_value(value.clone())?;
+ for specifier in types {
+ if let Ok(specifier) = resolve_url_or_path(&specifier) {
+ self.insert(&specifier, false).await?;
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+
+ /// Analyze a config file, identifying any specifiers that need to be resolved
+ /// and added to the graph.
+ pub async fn analyze_config_file(
+ &mut self,
+ maybe_config_file: &Option<ConfigFile>,
+ ) -> Result<(), AnyError> {
+ if let Some(config_file) = maybe_config_file {
+ let referrer = ModuleSpecifier::from_file_path(&config_file.path)
+ .map_err(|_| {
+ anyhow!("Could not convert file path: \"{:?}\"", config_file.path)
+ })?;
+ if let Some(compiler_options) = &config_file.json.compiler_options {
+ let compiler_options: CompilerOptions =
+ serde_json::from_value(compiler_options.clone())?;
+ if let Some(types) = compiler_options.types {
+ for specifier in types {
+ if let Ok(specifier) =
+ resolve_import(&specifier, &referrer.to_string())
+ {
+ self.insert(&specifier, false).await?;
+ }
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+
/// Request a module to be fetched from the handler and queue up its future
/// to be awaited to be resolved.
fn fetch(
@@ -1882,6 +1928,37 @@ impl GraphBuilder {
}
}
+ /// An internal method that fetches the specifier and recursively fetches any
+ /// of the dependencies, adding them to the graph.
+ async fn insert(
+ &mut self,
+ specifier: &ModuleSpecifier,
+ is_dynamic: bool,
+ ) -> Result<(), AnyError> {
+ self.fetch(specifier, &None, is_dynamic);
+
+ loop {
+ match self.pending.next().await {
+ Some(Err((specifier, err))) => {
+ self
+ .graph
+ .modules
+ .insert(specifier, ModuleSlot::Err(Arc::new(err)));
+ }
+ Some(Ok(cached_module)) => {
+ let is_root = &cached_module.specifier == specifier;
+ self.visit(cached_module, is_root, is_dynamic)?;
+ }
+ _ => {}
+ }
+ if self.pending.is_empty() {
+ break;
+ }
+ }
+
+ Ok(())
+ }
+
/// Visit a module that has been fetched, hydrating the module, analyzing its
/// dependencies if required, fetching those dependencies, and inserting the
/// module into the graph.
diff --git a/cli/ops/runtime_compiler.rs b/cli/ops/runtime_compiler.rs
index c2d5582e7..099f2d555 100644
--- a/cli/ops/runtime_compiler.rs
+++ b/cli/ops/runtime_compiler.rs
@@ -108,6 +108,9 @@ async fn op_emit(
&root_specifier
))
})?;
+ builder
+ .analyze_compiler_options(&args.compiler_options)
+ .await?;
let bundle_type = match args.bundle {
Some(RuntimeBundleType::Module) => BundleType::Module,
Some(RuntimeBundleType::Classic) => BundleType::Classic,
diff --git a/cli/program_state.rs b/cli/program_state.rs
index 668c73058..3d4d67f53 100644
--- a/cli/program_state.rs
+++ b/cli/program_state.rs
@@ -174,6 +174,7 @@ impl ProgramState {
for specifier in specifiers {
builder.add(&specifier, false).await?;
}
+ builder.analyze_config_file(&self.maybe_config_file).await?;
let mut graph = builder.get_graph();
let debug = self.flags.log_level == Some(log::Level::Debug);
@@ -248,6 +249,7 @@ impl ProgramState {
let mut builder =
GraphBuilder::new(handler, maybe_import_map, self.lockfile.clone());
builder.add(&specifier, is_dynamic).await?;
+ builder.analyze_config_file(&self.maybe_config_file).await?;
let mut graph = builder.get_graph();
let debug = self.flags.log_level == Some(log::Level::Debug);
let maybe_config_file = self.maybe_config_file.clone();
diff --git a/cli/tests/compiler_api_test.ts b/cli/tests/compiler_api_test.ts
index 00116e7e1..b9a08d5ca 100644
--- a/cli/tests/compiler_api_test.ts
+++ b/cli/tests/compiler_api_test.ts
@@ -93,6 +93,56 @@ Deno.test({
});
Deno.test({
+ name: "Deno.emit() - type references can be loaded",
+ async fn() {
+ const { diagnostics, files, ignoredOptions, stats } = await Deno.emit(
+ "file:///a.ts",
+ {
+ sources: {
+ "file:///a.ts": `/// <reference types="./b.d.ts" />
+ const b = new B();
+ console.log(b.b);`,
+ "file:///b.d.ts": `declare class B {
+ b: string;
+ }`,
+ },
+ },
+ );
+ assertEquals(diagnostics.length, 0);
+ assert(!ignoredOptions);
+ assertEquals(stats.length, 12);
+ const keys = Object.keys(files).sort();
+ assertEquals(keys, ["file:///a.ts.js", "file:///a.ts.js.map"]);
+ },
+});
+
+Deno.test({
+ name: "Deno.emit() - compilerOptions.types",
+ async fn() {
+ const { diagnostics, files, ignoredOptions, stats } = await Deno.emit(
+ "file:///a.ts",
+ {
+ compilerOptions: {
+ types: ["file:///b.d.ts"],
+ },
+ sources: {
+ "file:///a.ts": `const b = new B();
+ console.log(b.b);`,
+ "file:///b.d.ts": `declare class B {
+ b: string;
+ }`,
+ },
+ },
+ );
+ assertEquals(diagnostics.length, 0);
+ assert(!ignoredOptions);
+ assertEquals(stats.length, 12);
+ const keys = Object.keys(files).sort();
+ assertEquals(keys, ["file:///a.ts.js", "file:///a.ts.js.map"]);
+ },
+});
+
+Deno.test({
name: "Deno.emit() - import maps",
async fn() {
const { diagnostics, files, ignoredOptions, stats } = await Deno.emit(
diff --git a/cli/tests/config_types.ts b/cli/tests/config_types.ts
new file mode 100644
index 000000000..f1a8d6583
--- /dev/null
+++ b/cli/tests/config_types.ts
@@ -0,0 +1 @@
+console.log(globalThis.a);
diff --git a/cli/tests/config_types.ts.out b/cli/tests/config_types.ts.out
new file mode 100644
index 000000000..417b7b537
--- /dev/null
+++ b/cli/tests/config_types.ts.out
@@ -0,0 +1 @@
+undefined
diff --git a/cli/tests/config_types.tsconfig.json b/cli/tests/config_types.tsconfig.json
new file mode 100644
index 000000000..3810d4534
--- /dev/null
+++ b/cli/tests/config_types.tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "compilerOptions": {
+ "types": [
+ "./subdir/types.d.ts"
+ ]
+ }
+}
diff --git a/cli/tests/config_types_remote.tsconfig.json b/cli/tests/config_types_remote.tsconfig.json
new file mode 100644
index 000000000..745bb7b20
--- /dev/null
+++ b/cli/tests/config_types_remote.tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "compilerOptions": {
+ "types": [
+ "http://localhost:4545/cli/tests/subdir/types.d.ts"
+ ]
+ }
+}
diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs
index 081ea40e5..af3c9167c 100644
--- a/cli/tests/integration_tests.rs
+++ b/cli/tests/integration_tests.rs
@@ -3421,7 +3421,19 @@ console.log("finish");
output: "config.ts.out",
});
- itest!(emtpy_typescript {
+ itest!(config_types {
+ args:
+ "run --reload --quiet --config config_types.tsconfig.json config_types.ts",
+ output: "config_types.ts.out",
+ });
+
+ itest!(config_types_remote {
+ http_server: true,
+ args: "run --reload --quiet --config config_types_remote.tsconfig.json config_types.ts",
+ output: "config_types.ts.out",
+ });
+
+ itest!(empty_typescript {
args: "run --reload subdir/empty.ts",
output_str: Some("Check file:[WILDCARD]tests/subdir/empty.ts\n"),
});
@@ -4123,6 +4135,17 @@ console.log("finish");
output: "redirect_cache.out",
});
+ itest!(reference_types {
+ args: "run --reload --quiet reference_types.ts",
+ output: "reference_types.ts.out",
+ });
+
+ itest!(references_types_remote {
+ http_server: true,
+ args: "run --reload --quiet reference_types_remote.ts",
+ output: "reference_types_remote.ts.out",
+ });
+
itest!(deno_doc_types_header_direct {
args: "doc --reload http://127.0.0.1:4545/xTypeScriptTypes.js",
output: "doc/types_header.out",
diff --git a/cli/tests/integration_tests_lsp.rs b/cli/tests/integration_tests_lsp.rs
index afc93764a..04c66625c 100644
--- a/cli/tests/integration_tests_lsp.rs
+++ b/cli/tests/integration_tests_lsp.rs
@@ -21,6 +21,12 @@ fn load_fixture(path: &str) -> Value {
serde_json::from_str(&fixture_str).unwrap()
}
+fn load_fixture_str(path: &str) -> String {
+ let fixtures_path = root_path().join("cli/tests/lsp");
+ let path = fixtures_path.join(path);
+ fs::read_to_string(path).unwrap()
+}
+
fn init(init_path: &str) -> LspClient {
let deno_exe = deno_exe_path();
let mut client = LspClient::new(&deno_exe).unwrap();
@@ -123,6 +129,85 @@ fn lsp_init_tsconfig() {
}
#[test]
+fn lsp_tsconfig_types() {
+ let mut params: lsp::InitializeParams =
+ serde_json::from_value(load_fixture("initialize_params.json")).unwrap();
+ let temp_dir = TempDir::new().expect("could not create temp dir");
+ let tsconfig =
+ serde_json::to_vec_pretty(&load_fixture("types.tsconfig.json")).unwrap();
+ fs::write(temp_dir.path().join("types.tsconfig.json"), tsconfig).unwrap();
+ let a_dts = load_fixture_str("a.d.ts");
+ fs::write(temp_dir.path().join("a.d.ts"), a_dts).unwrap();
+
+ params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap());
+ if let Some(Value::Object(mut map)) = params.initialization_options {
+ map.insert("config".to_string(), json!("./types.tsconfig.json"));
+ params.initialization_options = Some(Value::Object(map));
+ }
+
+ let deno_exe = deno_exe_path();
+ let mut client = LspClient::new(&deno_exe).unwrap();
+ client
+ .write_request::<_, _, Value>("initialize", params)
+ .unwrap();
+
+ client.write_notification("initialized", json!({})).unwrap();
+
+ let diagnostics = did_open(
+ &mut client,
+ json!({
+ "textDocument": {
+ "uri": Url::from_file_path(temp_dir.path().join("test.ts")).unwrap(),
+ "languageId": "typescript",
+ "version": 1,
+ "text": "console.log(a);\n"
+ }
+ }),
+ );
+
+ let diagnostics = diagnostics.into_iter().flat_map(|x| x.diagnostics);
+ assert_eq!(diagnostics.count(), 0);
+
+ shutdown(&mut client);
+}
+
+#[test]
+fn lsp_triple_slash_types() {
+ let mut params: lsp::InitializeParams =
+ serde_json::from_value(load_fixture("initialize_params.json")).unwrap();
+ let temp_dir = TempDir::new().expect("could not create temp dir");
+ let a_dts = load_fixture_str("a.d.ts");
+ fs::write(temp_dir.path().join("a.d.ts"), a_dts).unwrap();
+
+ params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap());
+
+ let deno_exe = deno_exe_path();
+ let mut client = LspClient::new(&deno_exe).unwrap();
+ client
+ .write_request::<_, _, Value>("initialize", params)
+ .unwrap();
+
+ client.write_notification("initialized", json!({})).unwrap();
+
+ let diagnostics = did_open(
+ &mut client,
+ json!({
+ "textDocument": {
+ "uri": Url::from_file_path(temp_dir.path().join("test.ts")).unwrap(),
+ "languageId": "typescript",
+ "version": 1,
+ "text": "/// <reference types=\"./a.d.ts\" />\n\nconsole.log(a);\n"
+ }
+ }),
+ );
+
+ let diagnostics = diagnostics.into_iter().flat_map(|x| x.diagnostics);
+ assert_eq!(diagnostics.count(), 0);
+
+ shutdown(&mut client);
+}
+
+#[test]
fn lsp_hover() {
let mut client = init("initialize_params.json");
did_open(
diff --git a/cli/tests/lsp/a.d.ts b/cli/tests/lsp/a.d.ts
new file mode 100644
index 000000000..7f587e144
--- /dev/null
+++ b/cli/tests/lsp/a.d.ts
@@ -0,0 +1 @@
+declare var a: string;
diff --git a/cli/tests/lsp/b.d.ts b/cli/tests/lsp/b.d.ts
new file mode 100644
index 000000000..9d4b96cb8
--- /dev/null
+++ b/cli/tests/lsp/b.d.ts
@@ -0,0 +1 @@
+declare var b: string;
diff --git a/cli/tests/lsp/types.tsconfig.json b/cli/tests/lsp/types.tsconfig.json
new file mode 100644
index 000000000..ba7f3344d
--- /dev/null
+++ b/cli/tests/lsp/types.tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "compilerOptions": {
+ "types": [
+ "./a.d.ts"
+ ]
+ }
+}
diff --git a/cli/tests/reference_types.ts b/cli/tests/reference_types.ts
new file mode 100644
index 000000000..105e23b37
--- /dev/null
+++ b/cli/tests/reference_types.ts
@@ -0,0 +1,3 @@
+/// <reference types="./subdir/types.d.ts" />
+
+console.log(globalThis.a);
diff --git a/cli/tests/reference_types.ts.out b/cli/tests/reference_types.ts.out
new file mode 100644
index 000000000..417b7b537
--- /dev/null
+++ b/cli/tests/reference_types.ts.out
@@ -0,0 +1 @@
+undefined
diff --git a/cli/tests/reference_types_remote.ts b/cli/tests/reference_types_remote.ts
new file mode 100644
index 000000000..2e7a80098
--- /dev/null
+++ b/cli/tests/reference_types_remote.ts
@@ -0,0 +1,3 @@
+/// <reference types="http://localhost:4545/cli/tests/subdir/types.d.ts" />
+
+console.log(globalThis.a);
diff --git a/cli/tests/reference_types_remote.ts.out b/cli/tests/reference_types_remote.ts.out
new file mode 100644
index 000000000..417b7b537
--- /dev/null
+++ b/cli/tests/reference_types_remote.ts.out
@@ -0,0 +1 @@
+undefined
diff --git a/cli/tests/subdir/types.d.ts b/cli/tests/subdir/types.d.ts
new file mode 100644
index 000000000..7f587e144
--- /dev/null
+++ b/cli/tests/subdir/types.d.ts
@@ -0,0 +1 @@
+declare var a: string;
diff --git a/cli/tools/doc.rs b/cli/tools/doc.rs
index 5794b494f..b1ab0174f 100644
--- a/cli/tools/doc.rs
+++ b/cli/tools/doc.rs
@@ -125,6 +125,9 @@ pub async fn print_docs(
program_state.lockfile.clone(),
);
builder.add(&root_specifier, false).await?;
+ builder
+ .analyze_config_file(&program_state.maybe_config_file)
+ .await?;
let graph = builder.get_graph();
let doc_parser = doc::DocParser::new(Box::new(graph), private);
diff --git a/cli/tsc.rs b/cli/tsc.rs
index f87682fca..bfd5e8dbe 100644
--- a/cli/tsc.rs
+++ b/cli/tsc.rs
@@ -12,6 +12,7 @@ use deno_core::error::AnyError;
use deno_core::error::Context;
use deno_core::op_sync;
use deno_core::resolve_url_or_path;
+use deno_core::serde::de;
use deno_core::serde::Deserialize;
use deno_core::serde::Serialize;
use deno_core::serde_json;
@@ -179,6 +180,7 @@ pub struct Request {
pub debug: bool,
pub graph: Arc<Mutex<Graph>>,
pub hash_data: Vec<Vec<u8>>,
+ pub maybe_config_specifier: Option<ModuleSpecifier>,
pub maybe_tsbuildinfo: Option<String>,
/// A vector of strings that represent the root/entry point modules for the
/// program.
@@ -203,6 +205,7 @@ struct State {
hash_data: Vec<Vec<u8>>,
emitted_files: Vec<EmittedFile>,
graph: Arc<Mutex<Graph>>,
+ maybe_config_specifier: Option<ModuleSpecifier>,
maybe_tsbuildinfo: Option<String>,
maybe_response: Option<RespondArgs>,
root_map: HashMap<String, ModuleSpecifier>,
@@ -212,6 +215,7 @@ impl State {
pub fn new(
graph: Arc<Mutex<Graph>>,
hash_data: Vec<Vec<u8>>,
+ maybe_config_specifier: Option<ModuleSpecifier>,
maybe_tsbuildinfo: Option<String>,
root_map: HashMap<String, ModuleSpecifier>,
data_url_map: HashMap<String, ModuleSpecifier>,
@@ -221,6 +225,7 @@ impl State {
hash_data,
emitted_files: Default::default(),
graph,
+ maybe_config_specifier,
maybe_tsbuildinfo,
maybe_response: None,
root_map,
@@ -228,9 +233,16 @@ impl State {
}
}
-fn op<F>(op_fn: F) -> Box<OpFn>
+fn normalize_specifier(specifier: &str) -> Result<ModuleSpecifier, AnyError> {
+ resolve_url_or_path(&specifier.replace(".d.ts.d.ts", ".d.ts"))
+ .map_err(|err| err.into())
+}
+
+fn op<F, V, R>(op_fn: F) -> Box<OpFn>
where
- F: Fn(&mut State, Value) -> Result<Value, AnyError> + 'static,
+ F: Fn(&mut State, V) -> Result<R, AnyError> + 'static,
+ V: de::DeserializeOwned,
+ R: Serialize + 'static,
{
op_sync(move |s, args, _: ()| {
let state = s.borrow_mut::<State>();
@@ -255,6 +267,15 @@ fn op_create_hash(state: &mut State, args: Value) -> Result<Value, AnyError> {
Ok(json!({ "hash": hash }))
}
+fn op_cwd(state: &mut State, _args: Value) -> Result<String, AnyError> {
+ if let Some(config_specifier) = &state.maybe_config_specifier {
+ let cwd = config_specifier.join("./")?;
+ Ok(cwd.to_string())
+ } else {
+ Ok("cache:///".to_string())
+ }
+}
+
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct EmitArgs {
@@ -285,7 +306,7 @@ fn op_emit(state: &mut State, args: Value) -> Result<Value, AnyError> {
} else if let Some(remapped_specifier) = state.root_map.get(s) {
remapped_specifier.clone()
} else {
- resolve_url_or_path(s).unwrap()
+ normalize_specifier(s).unwrap()
}
})
.collect();
@@ -301,6 +322,25 @@ fn op_emit(state: &mut State, args: Value) -> Result<Value, AnyError> {
}
#[derive(Debug, Deserialize)]
+struct ExistsArgs {
+ /// The fully qualified specifier that should be loaded.
+ specifier: String,
+}
+
+fn op_exists(state: &mut State, args: ExistsArgs) -> Result<bool, AnyError> {
+ if let Ok(specifier) = normalize_specifier(&args.specifier) {
+ if specifier.scheme() == "asset" || specifier.scheme() == "data" {
+ Ok(true)
+ } else {
+ let graph = state.graph.lock().unwrap();
+ Ok(graph.contains(&specifier))
+ }
+ } else {
+ Ok(false)
+ }
+}
+
+#[derive(Debug, Deserialize)]
struct LoadArgs {
/// The fully qualified specifier that should be loaded.
specifier: String,
@@ -309,7 +349,7 @@ struct LoadArgs {
fn op_load(state: &mut State, args: Value) -> Result<Value, AnyError> {
let v: LoadArgs = serde_json::from_value(args)
.context("Invalid request from JavaScript for \"op_load\".")?;
- let specifier = resolve_url_or_path(&v.specifier)
+ let specifier = normalize_specifier(&v.specifier)
.context("Error converting a string module specifier for \"op_load\".")?;
let mut hash: Option<String> = None;
let mut media_type = MediaType::Unknown;
@@ -372,7 +412,7 @@ fn op_resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
} else if let Some(remapped_base) = state.root_map.get(&v.base) {
remapped_base.clone()
} else {
- resolve_url_or_path(&v.base).context(
+ normalize_specifier(&v.base).context(
"Error converting a string module specifier for \"op_resolve\".",
)?
};
@@ -490,14 +530,17 @@ pub fn exec(request: Request) -> Result<Response, AnyError> {
op_state.put(State::new(
request.graph.clone(),
request.hash_data.clone(),
+ request.maybe_config_specifier.clone(),
request.maybe_tsbuildinfo.clone(),
root_map,
data_url_map,
));
}
+ runtime.register_op("op_cwd", op(op_cwd));
runtime.register_op("op_create_hash", op(op_create_hash));
runtime.register_op("op_emit", op(op_emit));
+ runtime.register_op("op_exists", op(op_exists));
runtime.register_op("op_load", op(op_load));
runtime.register_op("op_resolve", op(op_resolve));
runtime.register_op("op_respond", op(op_respond));
@@ -573,6 +616,7 @@ mod tests {
State::new(
graph,
hash_data,
+ None,
maybe_tsbuildinfo,
HashMap::new(),
HashMap::new(),
@@ -614,6 +658,7 @@ mod tests {
debug: false,
graph,
hash_data,
+ maybe_config_specifier: None,
maybe_tsbuildinfo: None,
root_names: vec![(specifier.clone(), MediaType::TypeScript)],
};
diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js
index be0ed012a..a2f3af176 100644
--- a/cli/tsc/99_main_compiler.js
+++ b/cli/tsc/99_main_compiler.js
@@ -19,6 +19,9 @@ delete Object.prototype.__proto__;
let logDebug = false;
let logSource = "JS";
+ /** @type {string=} */
+ let cwd;
+
// The map from the normalized specifier to the original.
// TypeScript normalizes the specifier in its internal processing,
// but the original specifier is needed when looking up the source from the runtime.
@@ -130,7 +133,6 @@ delete Object.prototype.__proto__;
// analysis in Rust operates on fully resolved URLs,
// it makes sense to use the same scheme here.
const ASSETS = "asset:///";
- const CACHE = "cache:///";
/** Diagnostics that are intentionally ignored when compiling TypeScript in
* Deno, as they provide misleading or incorrect information. */
@@ -251,9 +253,10 @@ delete Object.prototype.__proto__;
*
* @type {ts.CompilerHost & ts.LanguageServiceHost} */
const host = {
- fileExists(fileName) {
- debug(`host.fileExists("${fileName}")`);
- return false;
+ fileExists(specifier) {
+ debug(`host.fileExists("${specifier}")`);
+ specifier = normalizedToOriginalMap.get(specifier) ?? specifier;
+ return core.opSync("op_exists", { specifier });
},
readFile(specifier) {
debug(`host.readFile("${specifier}")`);
@@ -317,7 +320,8 @@ delete Object.prototype.__proto__;
);
},
getCurrentDirectory() {
- return CACHE;
+ debug(`host.getCurrentDirectory()`);
+ return cwd ?? core.opSync("op_cwd", null);
},
getCanonicalFileName(fileName) {
return fileName;
@@ -787,12 +791,13 @@ delete Object.prototype.__proto__;
}
}
- /** @param {{ debug: boolean; }} init */
- function serverInit({ debug: debugFlag }) {
+ /** @param {{ debug: boolean; rootUri?: string; }} init */
+ function serverInit({ debug: debugFlag, rootUri }) {
if (hasStarted) {
throw new Error("The language server has already been initialized.");
}
hasStarted = true;
+ cwd = rootUri;
languageService = ts.createLanguageService(host);
setLogDebug(debugFlag, "TSLS");
debug("serverInit()");