mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 09:31:22 -05:00
parent
cda15f2a98
commit
281c4cd8fc
31 changed files with 636 additions and 92 deletions
|
@ -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(
|
||||
|
|
|
@ -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",
|
||||
];
|
||||
|
||||
|
|
20
cli/dts/lib.deno.unstable.d.ts
vendored
20
cli/dts/lib.deno.unstable.d.ts
vendored
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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 ¶ms.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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
181
cli/lsp/tsc.rs
181
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,12 +1916,29 @@ 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)
|
||||
}
|
||||
|
||||
#[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 {
|
||||
|
@ -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(
|
||||
|
|
12
cli/main.rs
12
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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -92,6 +92,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() {
|
||||
|
|
1
cli/tests/config_types.ts
Normal file
1
cli/tests/config_types.ts
Normal file
|
@ -0,0 +1 @@
|
|||
console.log(globalThis.a);
|
1
cli/tests/config_types.ts.out
Normal file
1
cli/tests/config_types.ts.out
Normal file
|
@ -0,0 +1 @@
|
|||
undefined
|
7
cli/tests/config_types.tsconfig.json
Normal file
7
cli/tests/config_types.tsconfig.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"./subdir/types.d.ts"
|
||||
]
|
||||
}
|
||||
}
|
7
cli/tests/config_types_remote.tsconfig.json
Normal file
7
cli/tests/config_types_remote.tsconfig.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"http://localhost:4545/cli/tests/subdir/types.d.ts"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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();
|
||||
|
@ -122,6 +128,85 @@ fn lsp_init_tsconfig() {
|
|||
shutdown(&mut client);
|
||||
}
|
||||
|
||||
#[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");
|
||||
|
|
1
cli/tests/lsp/a.d.ts
vendored
Normal file
1
cli/tests/lsp/a.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
declare var a: string;
|
1
cli/tests/lsp/b.d.ts
vendored
Normal file
1
cli/tests/lsp/b.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
declare var b: string;
|
7
cli/tests/lsp/types.tsconfig.json
Normal file
7
cli/tests/lsp/types.tsconfig.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"./a.d.ts"
|
||||
]
|
||||
}
|
||||
}
|
3
cli/tests/reference_types.ts
Normal file
3
cli/tests/reference_types.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
/// <reference types="./subdir/types.d.ts" />
|
||||
|
||||
console.log(globalThis.a);
|
1
cli/tests/reference_types.ts.out
Normal file
1
cli/tests/reference_types.ts.out
Normal file
|
@ -0,0 +1 @@
|
|||
undefined
|
3
cli/tests/reference_types_remote.ts
Normal file
3
cli/tests/reference_types_remote.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
/// <reference types="http://localhost:4545/cli/tests/subdir/types.d.ts" />
|
||||
|
||||
console.log(globalThis.a);
|
1
cli/tests/reference_types_remote.ts.out
Normal file
1
cli/tests/reference_types_remote.ts.out
Normal file
|
@ -0,0 +1 @@
|
|||
undefined
|
1
cli/tests/subdir/types.d.ts
vendored
Normal file
1
cli/tests/subdir/types.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
declare var a: string;
|
|
@ -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);
|
||||
|
|
55
cli/tsc.rs
55
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();
|
||||
|
@ -300,6 +321,25 @@ fn op_emit(state: &mut State, args: Value) -> Result<Value, AnyError> {
|
|||
Ok(json!(true))
|
||||
}
|
||||
|
||||
#[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.
|
||||
|
@ -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)],
|
||||
};
|
||||
|
|
|
@ -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()");
|
||||
|
|
|
@ -197,3 +197,10 @@ The biggest "danger" when doing something like this, is that the type checking
|
|||
is significantly looser, and there is no way to validate that you are doing
|
||||
sufficient and effective feature detection in your code, which may lead to what
|
||||
could be trivial errors becoming runtime errors.
|
||||
|
||||
### Using the "types" property
|
||||
|
||||
The `"types"` property in `"compilerOptions"` can be used to specify arbitrary
|
||||
type definitions to include when type checking a programme. For more information
|
||||
on this see
|
||||
[Using ambient or global types](./types#using-ambient-or-global-types).
|
||||
|
|
|
@ -101,6 +101,67 @@ When seeing this header, Deno would attempt to retrieve
|
|||
`https://example.com/coolLib.d.ts` and use that when type checking the original
|
||||
module.
|
||||
|
||||
### Using ambient or global types
|
||||
|
||||
Overall it is better to use module/UMD type definitions with Deno, where a
|
||||
module expressly imports the types it depends upon. Modular type definitions can
|
||||
express
|
||||
[augmentation of the global scope](https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-modifying-module-d-ts.html)
|
||||
via the `declare global` in the type definition. For example:
|
||||
|
||||
```ts
|
||||
declare global {
|
||||
var AGlobalString: string;
|
||||
}
|
||||
```
|
||||
|
||||
This would make `AGlobalString` available in the global namespace when importing
|
||||
the type definition.
|
||||
|
||||
In some cases though, when leveraging other existing type libraries, it may not
|
||||
be possible to leverage modular type definitions. Therefore there are ways to
|
||||
include arbitrary type definitions when type checking programmes.
|
||||
|
||||
#### Using a triple-slash directive
|
||||
|
||||
This option couples the type definitions to the code itself. By adding a
|
||||
triple-slash `types` directive near the type of a module, type checking the file
|
||||
will include the type definition. For example:
|
||||
|
||||
```ts
|
||||
/// <reference types="./types.d.ts" />
|
||||
```
|
||||
|
||||
The specifier provided is resolved just like any other specifier in Deno, which
|
||||
means it requires an extension, and is relative to the module referencing it. It
|
||||
can be a fully qualified URL as well:
|
||||
|
||||
```ts
|
||||
/// <reference types="https://deno.land/x/pkg@1.0.0/types.d.ts" />
|
||||
```
|
||||
|
||||
#### Using a `tsconfig.json` file
|
||||
|
||||
Another option is to use a `tsconfig.json` file that is configured to include
|
||||
the type definitions, by supplying a `"types"` value to the `"compilerOptions"`.
|
||||
For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"./types.d.ts",
|
||||
"https://deno.land/x/pkg@1.0.0/types.d.ts",
|
||||
"/Users/me/pkg/types.d.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Like the triple-slash reference above, the specifier supplied in the `"types"`
|
||||
array will be resolved like other specifiers in Deno. In the case of relative
|
||||
specifiers, it will be resolved relative to the path to the `tsconfig.json`.
|
||||
|
||||
### Type Checking Web Workers
|
||||
|
||||
When Deno loads a TypeScript module in a web worker, it will automatically type
|
||||
|
|
Loading…
Add table
Reference in a new issue