mirror of
https://github.com/denoland/deno.git
synced 2025-08-04 19:08:15 +00:00
fix(check/lsp): fall back to @types/*
packages if npm package doesn't have types (#28185)
Fixes https://github.com/denoland/deno/issues/27569. Fixes https://github.com/denoland/deno/issues/27215. This PR makes it so type resolution falls back to looking for definitely typed packages (`@types/foo`) if a given NPM package does not contain type declarations. One complication is choosing _which_ version of the `@types/*` package to use, if the project depends on multiple versions. The heuristic here is to try to match the major and minor versions, falling back to the latest version. So if you have ``` @types/foo: 0.1.0, 0.2.0, 3.1.0, 3.1.2, 4.0.0 foo: 3.1.0 ``` we would choose `@types/foo@3.1.2` when resolving types for `foo`. --- Note that this only uses `@types/` packages if you _already_ depend on them. So a follow up to this PR could be to add a diagnostic and quickfix to install `@types/foo` if we don't find types for `foo`.
This commit is contained in:
parent
3da3fe8f7b
commit
08f5e797b6
23 changed files with 567 additions and 389 deletions
|
@ -18,9 +18,9 @@ use deno_core::serde_json::json;
|
|||
use deno_core::ModuleSpecifier;
|
||||
use deno_error::JsErrorBox;
|
||||
use deno_lint::diagnostic::LintDiagnosticRange;
|
||||
use deno_npm::NpmPackageId;
|
||||
use deno_path_util::url_to_file_path;
|
||||
use deno_resolver::npm::managed::NpmResolutionCell;
|
||||
use deno_resolver::workspace::MappedResolution;
|
||||
use deno_runtime::deno_node::PathClean;
|
||||
use deno_semver::jsr::JsrPackageNvReference;
|
||||
use deno_semver::jsr::JsrPackageReqReference;
|
||||
|
@ -38,7 +38,6 @@ use node_resolver::NodeResolutionKind;
|
|||
use node_resolver::ResolutionMode;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use text_lines::LineAndColumnIndex;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tower_lsp::lsp_types as lsp;
|
||||
use tower_lsp::lsp_types::Position;
|
||||
|
@ -46,7 +45,6 @@ use tower_lsp::lsp_types::Range;
|
|||
|
||||
use super::diagnostics::DenoDiagnostic;
|
||||
use super::diagnostics::DiagnosticSource;
|
||||
use super::documents::Document;
|
||||
use super::documents::Documents;
|
||||
use super::language_server;
|
||||
use super::resolver::LspResolver;
|
||||
|
@ -54,7 +52,6 @@ use super::tsc;
|
|||
use super::urls::url_to_uri;
|
||||
use crate::args::jsr_url;
|
||||
use crate::lsp::logging::lsp_warn;
|
||||
use crate::lsp::search::PackageSearchApi;
|
||||
use crate::tools::lint::CliLinter;
|
||||
use crate::util::path::relative_specifier;
|
||||
|
||||
|
@ -370,9 +367,13 @@ impl<'a> TsResponseImportMapper<'a> {
|
|||
if let Ok(Some(pkg_id)) =
|
||||
npm_resolver.resolve_pkg_id_from_specifier(specifier)
|
||||
{
|
||||
let pkg_reqs = npm_resolver
|
||||
.resolution()
|
||||
.resolve_pkg_reqs_from_pkg_id(&pkg_id);
|
||||
let pkg_reqs =
|
||||
maybe_reverse_definitely_typed(&pkg_id, npm_resolver.resolution())
|
||||
.unwrap_or_else(|| {
|
||||
npm_resolver
|
||||
.resolution()
|
||||
.resolve_pkg_reqs_from_pkg_id(&pkg_id)
|
||||
});
|
||||
// check if any pkg reqs match what is found in an import map
|
||||
if !pkg_reqs.is_empty() {
|
||||
let sub_path = npm_resolver
|
||||
|
@ -558,6 +559,30 @@ impl<'a> TsResponseImportMapper<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn maybe_reverse_definitely_typed(
|
||||
pkg_id: &NpmPackageId,
|
||||
resolution: &NpmResolutionCell,
|
||||
) -> Option<Vec<PackageReq>> {
|
||||
let rest = pkg_id.nv.name.strip_prefix("@types/")?;
|
||||
let package_name = if rest.contains("__") {
|
||||
Cow::Owned(format!("@{}", rest.replace("__", "/")))
|
||||
} else {
|
||||
Cow::Borrowed(rest)
|
||||
};
|
||||
|
||||
let reqs = resolution
|
||||
.package_reqs()
|
||||
.into_iter()
|
||||
.filter_map(|(req, nv)| (*nv.name == package_name).then_some(req))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if reqs.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(reqs)
|
||||
}
|
||||
}
|
||||
|
||||
fn try_reverse_map_package_json_exports(
|
||||
root_path: &Path,
|
||||
target_path: &Path,
|
||||
|
@ -1267,205 +1292,6 @@ impl CodeActionCollection {
|
|||
..Default::default()
|
||||
}));
|
||||
}
|
||||
|
||||
pub async fn add_source_actions(
|
||||
&mut self,
|
||||
document: &Document,
|
||||
range: &lsp::Range,
|
||||
language_server: &language_server::Inner,
|
||||
) {
|
||||
fn import_start_from_specifier(
|
||||
document: &Document,
|
||||
import: &deno_graph::Import,
|
||||
) -> Option<LineAndColumnIndex> {
|
||||
// find the top level statement that contains the specifier
|
||||
let parsed_source = document.maybe_parsed_source()?.as_ref().ok()?;
|
||||
let text_info = parsed_source.text_info_lazy();
|
||||
let specifier_range = SourceRange::new(
|
||||
text_info.loc_to_source_pos(LineAndColumnIndex {
|
||||
line_index: import.specifier_range.range.start.line,
|
||||
column_index: import.specifier_range.range.start.character,
|
||||
}),
|
||||
text_info.loc_to_source_pos(LineAndColumnIndex {
|
||||
line_index: import.specifier_range.range.end.line,
|
||||
column_index: import.specifier_range.range.end.character,
|
||||
}),
|
||||
);
|
||||
|
||||
parsed_source
|
||||
.program_ref()
|
||||
.body()
|
||||
.find(|i| i.range().contains(&specifier_range))
|
||||
.map(|i| text_info.line_and_column_index(i.range().start))
|
||||
}
|
||||
|
||||
async fn deno_types_for_npm_action(
|
||||
document: &Document,
|
||||
range: &lsp::Range,
|
||||
language_server: &language_server::Inner,
|
||||
) -> Option<lsp::CodeAction> {
|
||||
fn top_package_req_for_name(
|
||||
resolution: &NpmResolutionCell,
|
||||
name: &str,
|
||||
) -> Option<PackageReq> {
|
||||
let package_reqs = resolution.package_reqs();
|
||||
let mut entries = package_reqs
|
||||
.into_iter()
|
||||
.filter(|(_, nv)| nv.name == name)
|
||||
.collect::<Vec<_>>();
|
||||
entries.sort_by(|a, b| a.1.version.cmp(&b.1.version));
|
||||
Some(entries.pop()?.0)
|
||||
}
|
||||
|
||||
let (dep_key, dependency, _) =
|
||||
document.get_maybe_dependency(&range.end)?;
|
||||
if dependency.maybe_deno_types_specifier.is_some() {
|
||||
return None;
|
||||
}
|
||||
if dependency.maybe_code.maybe_specifier().is_none()
|
||||
&& dependency.maybe_type.maybe_specifier().is_none()
|
||||
{
|
||||
// We're using byonm and the package is not cached.
|
||||
return None;
|
||||
}
|
||||
let position = deno_graph::Position::new(
|
||||
range.end.line as usize,
|
||||
range.end.character as usize,
|
||||
);
|
||||
let import_start = dependency.imports.iter().find_map(|i| {
|
||||
if json!(i.kind) != json!("es") && json!(i.kind) != json!("tsType") {
|
||||
return None;
|
||||
}
|
||||
if !i.specifier_range.includes(position) {
|
||||
return None;
|
||||
}
|
||||
|
||||
import_start_from_specifier(document, i)
|
||||
})?;
|
||||
let referrer = document.specifier();
|
||||
let resolution_mode = document.resolution_mode();
|
||||
let file_referrer = document.file_referrer();
|
||||
let config_data = language_server
|
||||
.config
|
||||
.tree
|
||||
.data_for_specifier(file_referrer?)?;
|
||||
let workspace_resolver = config_data.resolver.clone();
|
||||
let npm_ref = if let Ok(resolution) = workspace_resolver.resolve(
|
||||
&dep_key,
|
||||
document.specifier(),
|
||||
deno_resolver::workspace::ResolutionKind::Execution,
|
||||
) {
|
||||
let specifier = match resolution {
|
||||
MappedResolution::Normal { specifier, .. } => specifier,
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
NpmPackageReqReference::from_specifier(&specifier).ok()?
|
||||
} else {
|
||||
// Only resolve bare package.json deps for byonm.
|
||||
if !config_data.byonm {
|
||||
return None;
|
||||
}
|
||||
if !language_server.resolver.is_bare_package_json_dep(
|
||||
&dep_key,
|
||||
referrer,
|
||||
resolution_mode,
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
NpmPackageReqReference::from_str(&format!("npm:{}", &dep_key)).ok()?
|
||||
};
|
||||
let package_name = &npm_ref.req().name;
|
||||
if package_name.starts_with("@types/") {
|
||||
return None;
|
||||
}
|
||||
let managed_npm_resolver = language_server
|
||||
.resolver
|
||||
.maybe_managed_npm_resolver(file_referrer);
|
||||
if let Some(npm_resolver) = managed_npm_resolver {
|
||||
if !npm_resolver.is_pkg_req_folder_cached(npm_ref.req()) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
if language_server
|
||||
.resolver
|
||||
.npm_to_file_url(&npm_ref, referrer, resolution_mode, file_referrer)
|
||||
.is_some()
|
||||
{
|
||||
// The package import has types.
|
||||
return None;
|
||||
}
|
||||
let types_package_name = format!("@types/{package_name}");
|
||||
let types_package_version = language_server
|
||||
.npm_search_api
|
||||
.versions(&types_package_name)
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|versions| versions.first().cloned())?;
|
||||
let types_specifier_text =
|
||||
if let Some(npm_resolver) = managed_npm_resolver {
|
||||
let mut specifier_text = if let Some(req) = top_package_req_for_name(
|
||||
npm_resolver.resolution(),
|
||||
&types_package_name,
|
||||
) {
|
||||
format!("npm:{req}")
|
||||
} else {
|
||||
format!("npm:{}@^{}", &types_package_name, types_package_version)
|
||||
};
|
||||
let specifier = ModuleSpecifier::parse(&specifier_text).ok()?;
|
||||
if let Some(file_referrer) = file_referrer {
|
||||
if let Some(text) = language_server
|
||||
.get_ts_response_import_mapper(file_referrer)
|
||||
.check_specifier(&specifier, referrer)
|
||||
{
|
||||
specifier_text = text;
|
||||
}
|
||||
}
|
||||
specifier_text
|
||||
} else {
|
||||
types_package_name.clone()
|
||||
};
|
||||
let uri = language_server
|
||||
.url_map
|
||||
.specifier_to_uri(referrer, file_referrer)
|
||||
.ok()?;
|
||||
let position = lsp::Position {
|
||||
line: import_start.line_index as u32,
|
||||
character: import_start.column_index as u32,
|
||||
};
|
||||
let new_text = format!(
|
||||
"{}// @ts-types=\"{}\"\n",
|
||||
if position.character == 0 { "" } else { "\n" },
|
||||
&types_specifier_text
|
||||
);
|
||||
let text_edit = lsp::TextEdit {
|
||||
range: lsp::Range {
|
||||
start: position,
|
||||
end: position,
|
||||
},
|
||||
new_text,
|
||||
};
|
||||
Some(lsp::CodeAction {
|
||||
title: format!(
|
||||
"Add @ts-types directive for \"{}\"",
|
||||
&types_specifier_text
|
||||
),
|
||||
kind: Some(lsp::CodeActionKind::QUICKFIX),
|
||||
diagnostics: None,
|
||||
edit: Some(lsp::WorkspaceEdit {
|
||||
changes: Some([(uri, vec![text_edit])].into_iter().collect()),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
if let Some(action) =
|
||||
deno_types_for_npm_action(document, range, language_server).await
|
||||
{
|
||||
self.actions.push(CodeActionKind::Deno(action));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepend the whitespace characters found at the start of line_content to content.
|
||||
|
|
|
@ -1847,11 +1847,7 @@ impl Inner {
|
|||
}
|
||||
}
|
||||
}
|
||||
if let Some(document) = asset_or_doc.document() {
|
||||
code_actions
|
||||
.add_source_actions(document, ¶ms.range, self)
|
||||
.await;
|
||||
}
|
||||
|
||||
code_actions.set_preferred_fixes();
|
||||
all_actions.extend(code_actions.get_response());
|
||||
|
||||
|
|
|
@ -189,32 +189,34 @@ impl LspScopeResolver {
|
|||
let result = dependencies
|
||||
.iter()
|
||||
.flat_map(|(name, _)| {
|
||||
let req_ref =
|
||||
NpmPackageReqReference::from_str(&format!("npm:{name}")).ok()?;
|
||||
let specifier = into_specifier_and_media_type(Some(
|
||||
npm_pkg_req_resolver
|
||||
let mut deps = Vec::with_capacity(2);
|
||||
let Some(req_ref) =
|
||||
NpmPackageReqReference::from_str(&format!("npm:{name}")).ok()
|
||||
else {
|
||||
return vec![];
|
||||
};
|
||||
for kind in [NodeResolutionKind::Types, NodeResolutionKind::Execution]
|
||||
{
|
||||
let Some(req) = npm_pkg_req_resolver
|
||||
.resolve_req_reference(
|
||||
&req_ref,
|
||||
&referrer,
|
||||
// todo(dsherret): this is wrong because it doesn't consider CJS referrers
|
||||
ResolutionMode::Import,
|
||||
NodeResolutionKind::Types,
|
||||
kind,
|
||||
)
|
||||
.or_else(|_| {
|
||||
npm_pkg_req_resolver.resolve_req_reference(
|
||||
&req_ref,
|
||||
&referrer,
|
||||
// todo(dsherret): this is wrong because it doesn't consider CJS referrers
|
||||
ResolutionMode::Import,
|
||||
NodeResolutionKind::Execution,
|
||||
)
|
||||
})
|
||||
.ok()?
|
||||
.into_url()
|
||||
.ok()?,
|
||||
))
|
||||
.0;
|
||||
Some((specifier, name.clone()))
|
||||
.ok()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(url) = req.into_url().ok() else {
|
||||
continue;
|
||||
};
|
||||
let specifier = into_specifier_and_media_type(Some(url)).0;
|
||||
deps.push((specifier, name.clone()))
|
||||
}
|
||||
deps
|
||||
})
|
||||
.collect();
|
||||
Some(result)
|
||||
|
@ -579,29 +581,6 @@ impl LspResolver {
|
|||
has_node_modules_dir(specifier)
|
||||
}
|
||||
|
||||
pub fn is_bare_package_json_dep(
|
||||
&self,
|
||||
specifier_text: &str,
|
||||
referrer: &ModuleSpecifier,
|
||||
resolution_mode: ResolutionMode,
|
||||
) -> bool {
|
||||
let resolver = self.get_scope_resolver(Some(referrer));
|
||||
let Some(npm_pkg_req_resolver) = resolver.npm_pkg_req_resolver.as_ref()
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
npm_pkg_req_resolver
|
||||
.resolve_if_for_npm_pkg(
|
||||
specifier_text,
|
||||
referrer,
|
||||
resolution_mode,
|
||||
NodeResolutionKind::Types,
|
||||
)
|
||||
.ok()
|
||||
.flatten()
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub fn resolve_redirects(
|
||||
&self,
|
||||
specifier: &ModuleSpecifier,
|
||||
|
|
|
@ -1131,8 +1131,42 @@ fn resolve_graph_specifier_types(
|
|||
let maybe_url = match res_result {
|
||||
Ok(path_or_url) => Some(path_or_url.into_url()?),
|
||||
Err(err) => match err.code() {
|
||||
NodeJsErrorCode::ERR_TYPES_NOT_FOUND
|
||||
| NodeJsErrorCode::ERR_MODULE_NOT_FOUND => None,
|
||||
NodeJsErrorCode::ERR_TYPES_NOT_FOUND => {
|
||||
let reqs = npm
|
||||
.npm_resolver
|
||||
.as_managed()
|
||||
.unwrap()
|
||||
.resolution()
|
||||
.package_reqs();
|
||||
if let Some((_, types_nv)) =
|
||||
deno_resolver::npm::find_definitely_typed_package(
|
||||
module.nv_reference.nv(),
|
||||
reqs.iter().map(|tup| (&tup.0, &tup.1)),
|
||||
)
|
||||
{
|
||||
let package_folder = npm
|
||||
.npm_resolver
|
||||
.as_managed()
|
||||
.unwrap() // should never be byonm because it won't create Module::Npm
|
||||
.resolve_pkg_folder_from_deno_module(types_nv)?;
|
||||
let res_result =
|
||||
npm.node_resolver.resolve_package_subpath_from_deno_module(
|
||||
&package_folder,
|
||||
module.nv_reference.sub_path(),
|
||||
Some(referrer),
|
||||
resolution_mode,
|
||||
NodeResolutionKind::Types,
|
||||
);
|
||||
if let Ok(res_result) = res_result {
|
||||
Some(res_result.into_url()?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
NodeJsErrorCode::ERR_MODULE_NOT_FOUND => None,
|
||||
_ => return Err(ResolveError::PackageSubpathResolve(err)),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@ use std::path::PathBuf;
|
|||
use boxed_error::Boxed;
|
||||
use deno_error::JsError;
|
||||
use deno_semver::npm::NpmPackageReqReference;
|
||||
use deno_semver::package::PackageNv;
|
||||
use deno_semver::package::PackageReq;
|
||||
use node_resolver::errors::NodeResolveError;
|
||||
use node_resolver::errors::NodeResolveErrorKind;
|
||||
|
@ -15,6 +16,8 @@ use node_resolver::errors::PackageFolderResolveIoError;
|
|||
use node_resolver::errors::PackageNotFoundError;
|
||||
use node_resolver::errors::PackageResolveErrorKind;
|
||||
use node_resolver::errors::PackageSubpathResolveError;
|
||||
use node_resolver::errors::TypesNotFoundError;
|
||||
use node_resolver::types_package_name;
|
||||
use node_resolver::InNpmPackageChecker;
|
||||
use node_resolver::IsBuiltInNodeModuleChecker;
|
||||
use node_resolver::NodeResolution;
|
||||
|
@ -133,6 +136,18 @@ pub enum ResolveReqWithSubPathErrorKind {
|
|||
PackageSubpathResolve(#[from] PackageSubpathResolveError),
|
||||
}
|
||||
|
||||
impl ResolveReqWithSubPathErrorKind {
|
||||
pub fn as_types_not_found(&self) -> Option<&TypesNotFoundError> {
|
||||
match self {
|
||||
ResolveReqWithSubPathErrorKind::MissingPackageNodeModulesFolder(_)
|
||||
| ResolveReqWithSubPathErrorKind::ResolvePkgFolderFromDenoReq(_) => None,
|
||||
ResolveReqWithSubPathErrorKind::PackageSubpathResolve(
|
||||
package_subpath_resolve_error,
|
||||
) => package_subpath_resolve_error.as_types_not_found(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, JsError)]
|
||||
pub enum ResolvePkgFolderFromDenoReqError {
|
||||
#[class(inherit)]
|
||||
|
@ -365,6 +380,41 @@ impl<
|
|||
match resolution_result {
|
||||
Ok(url) => Ok(url),
|
||||
Err(err) => {
|
||||
if err.as_types_not_found().is_some() {
|
||||
let maybe_definitely_typed_req =
|
||||
if let Some(npm_resolver) = self.npm_resolver.as_managed() {
|
||||
let snapshot = npm_resolver.resolution().snapshot();
|
||||
if let Some(nv) = snapshot.package_reqs().get(req) {
|
||||
let type_req = find_definitely_typed_package(
|
||||
nv,
|
||||
snapshot.package_reqs().iter(),
|
||||
);
|
||||
|
||||
type_req.map(|(r, _)| r).cloned()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some(
|
||||
PackageReq::from_str(&format!(
|
||||
"{}@*",
|
||||
types_package_name(&req.name)
|
||||
))
|
||||
.unwrap(),
|
||||
)
|
||||
};
|
||||
if let Some(req) = maybe_definitely_typed_req {
|
||||
if let Ok(resolved) = self.resolve_req_with_sub_path(
|
||||
&req,
|
||||
sub_path,
|
||||
referrer,
|
||||
resolution_mode,
|
||||
resolution_kind,
|
||||
) {
|
||||
return Ok(resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
if matches!(self.npm_resolver, NpmResolver::Byonm(_)) {
|
||||
let package_json_path = package_folder.join("package.json");
|
||||
if !self.sys.fs_exists_no_err(&package_json_path) {
|
||||
|
@ -489,3 +539,41 @@ impl<
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to choose the "best" `@types/*` package
|
||||
/// if possible. If multiple versions exist, try to match
|
||||
/// the major and minor versions of the `@types` package with the
|
||||
/// actual package, falling back to the latest @types version present.
|
||||
pub fn find_definitely_typed_package<'a>(
|
||||
nv: &'a PackageNv,
|
||||
packages: impl IntoIterator<Item = (&'a PackageReq, &'a PackageNv)>,
|
||||
) -> Option<(&PackageReq, &PackageNv)> {
|
||||
let types_name = types_package_name(&nv.name);
|
||||
let mut best_patch = 0;
|
||||
let mut highest: Option<(&PackageReq, &PackageNv)> = None;
|
||||
let mut best = None;
|
||||
|
||||
for (req, type_nv) in packages {
|
||||
if type_nv.name != types_name {
|
||||
continue;
|
||||
}
|
||||
if type_nv.version.major == nv.version.major
|
||||
&& type_nv.version.minor == nv.version.minor
|
||||
&& type_nv.version.patch >= best_patch
|
||||
&& type_nv.version.pre == nv.version.pre
|
||||
{
|
||||
best = Some((req, type_nv));
|
||||
best_patch = type_nv.version.patch;
|
||||
}
|
||||
|
||||
if let Some((_, highest_nv)) = highest {
|
||||
if type_nv.version > highest_nv.version {
|
||||
highest = Some((req, type_nv));
|
||||
}
|
||||
} else {
|
||||
highest = Some((req, type_nv));
|
||||
}
|
||||
}
|
||||
|
||||
best.or(highest)
|
||||
}
|
||||
|
|
|
@ -203,6 +203,12 @@ impl NodeJsErrorCoded for PackageSubpathResolveError {
|
|||
}
|
||||
}
|
||||
|
||||
impl PackageSubpathResolveError {
|
||||
pub fn as_types_not_found(&self) -> Option<&TypesNotFoundError> {
|
||||
self.as_kind().as_types_not_found()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Boxed, JsError)]
|
||||
pub struct PackageSubpathResolveError(pub Box<PackageSubpathResolveErrorKind>);
|
||||
|
||||
|
@ -222,6 +228,35 @@ pub enum PackageSubpathResolveErrorKind {
|
|||
FinalizeResolution(#[from] FinalizeResolutionError),
|
||||
}
|
||||
|
||||
impl PackageSubpathResolveErrorKind {
|
||||
pub fn as_types_not_found(&self) -> Option<&TypesNotFoundError> {
|
||||
match self {
|
||||
PackageSubpathResolveErrorKind::PkgJsonLoad(_) => None,
|
||||
PackageSubpathResolveErrorKind::Exports(err) => match err.as_kind() {
|
||||
PackageExportsResolveErrorKind::PackagePathNotExported(_) => None,
|
||||
PackageExportsResolveErrorKind::PackageTargetResolve(err) => {
|
||||
match err.as_kind() {
|
||||
PackageTargetResolveErrorKind::TypesNotFound(not_found) => {
|
||||
Some(not_found)
|
||||
}
|
||||
PackageTargetResolveErrorKind::NotFound(_)
|
||||
| PackageTargetResolveErrorKind::InvalidPackageTarget(_)
|
||||
| PackageTargetResolveErrorKind::InvalidModuleSpecifier(_)
|
||||
| PackageTargetResolveErrorKind::PackageResolve(_)
|
||||
| PackageTargetResolveErrorKind::UrlToFilePath(_) => None,
|
||||
}
|
||||
}
|
||||
},
|
||||
PackageSubpathResolveErrorKind::LegacyResolve(err) => match err.as_kind()
|
||||
{
|
||||
LegacyResolveErrorKind::TypesNotFound(not_found) => Some(not_found),
|
||||
LegacyResolveErrorKind::ModuleNotFound(_) => None,
|
||||
},
|
||||
PackageSubpathResolveErrorKind::FinalizeResolution(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, JsError)]
|
||||
#[class(generic)]
|
||||
#[error(
|
||||
|
@ -297,6 +332,15 @@ pub enum PackageTargetResolveErrorKind {
|
|||
UrlToFilePath(#[from] deno_path_util::UrlToFilePathError),
|
||||
}
|
||||
|
||||
impl PackageTargetResolveErrorKind {
|
||||
pub fn as_types_not_found(&self) -> Option<&TypesNotFoundError> {
|
||||
match self {
|
||||
Self::TypesNotFound(not_found) => Some(not_found),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeJsErrorCoded for PackageExportsResolveError {
|
||||
fn code(&self) -> NodeJsErrorCode {
|
||||
match self.as_kind() {
|
||||
|
@ -417,6 +461,15 @@ pub enum PackageImportsResolveErrorKind {
|
|||
Target(#[from] PackageTargetResolveError),
|
||||
}
|
||||
|
||||
impl PackageImportsResolveErrorKind {
|
||||
pub fn as_types_not_found(&self) -> Option<&TypesNotFoundError> {
|
||||
match self {
|
||||
Self::Target(err) => err.as_types_not_found(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeJsErrorCoded for PackageImportsResolveErrorKind {
|
||||
fn code(&self) -> NodeJsErrorCode {
|
||||
match self {
|
||||
|
@ -468,6 +521,19 @@ pub enum PackageResolveErrorKind {
|
|||
UrlToFilePath(#[from] UrlToFilePathError),
|
||||
}
|
||||
|
||||
impl PackageResolveErrorKind {
|
||||
pub fn as_types_not_found(&self) -> Option<&TypesNotFoundError> {
|
||||
match self {
|
||||
PackageResolveErrorKind::ClosestPkgJson(_)
|
||||
| PackageResolveErrorKind::InvalidModuleSpecifier(_)
|
||||
| PackageResolveErrorKind::PackageFolderResolve(_)
|
||||
| PackageResolveErrorKind::ExportsResolve(_)
|
||||
| PackageResolveErrorKind::UrlToFilePath(_) => None,
|
||||
PackageResolveErrorKind::SubpathResolve(err) => err.as_types_not_found(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, JsError)]
|
||||
#[class(generic)]
|
||||
#[error("Failed joining '{path}' from '{base}'.")]
|
||||
|
@ -520,6 +586,26 @@ pub enum NodeResolveErrorKind {
|
|||
FinalizeResolution(#[from] FinalizeResolutionError),
|
||||
}
|
||||
|
||||
impl NodeResolveErrorKind {
|
||||
pub fn as_types_not_found(&self) -> Option<&TypesNotFoundError> {
|
||||
match self {
|
||||
NodeResolveErrorKind::TypesNotFound(not_found) => Some(not_found),
|
||||
NodeResolveErrorKind::PackageImportsResolve(err) => {
|
||||
err.as_kind().as_types_not_found()
|
||||
}
|
||||
NodeResolveErrorKind::PackageResolve(package_resolve_error) => {
|
||||
package_resolve_error.as_types_not_found()
|
||||
}
|
||||
NodeResolveErrorKind::UnsupportedEsmUrlScheme(_)
|
||||
| NodeResolveErrorKind::DataUrlReferrer(_)
|
||||
| NodeResolveErrorKind::FinalizeResolution(_)
|
||||
| NodeResolveErrorKind::RelativeJoin(_)
|
||||
| NodeResolveErrorKind::PathToUrl(_)
|
||||
| NodeResolveErrorKind::UrlToFilePath(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Boxed, JsError)]
|
||||
pub struct FinalizeResolutionError(pub Box<FinalizeResolutionErrorKind>);
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ pub use path::UrlOrPath;
|
|||
pub use path::UrlOrPathRef;
|
||||
pub use resolution::parse_npm_pkg_name;
|
||||
pub use resolution::resolve_specifier_into_node_modules;
|
||||
pub use resolution::types_package_name;
|
||||
pub use resolution::ConditionsFromResolutionMode;
|
||||
pub use resolution::NodeResolution;
|
||||
pub use resolution::NodeResolutionKind;
|
||||
|
|
|
@ -2090,11 +2090,14 @@ fn pattern_key_compare(a: &str, b: &str) -> i32 {
|
|||
}
|
||||
|
||||
/// Gets the corresponding @types package for the provided package name.
|
||||
fn types_package_name(package_name: &str) -> String {
|
||||
pub fn types_package_name(package_name: &str) -> String {
|
||||
debug_assert!(!package_name.starts_with("@types/"));
|
||||
// Scoped packages will get two underscores for each slash
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/tree/15f1ece08f7b498f4b9a2147c2a46e94416ca777#what-about-scoped-packages
|
||||
format!("@types/{}", package_name.replace('/', "__"))
|
||||
format!(
|
||||
"@types/{}",
|
||||
package_name.trim_start_matches('@').replace('/', "__")
|
||||
)
|
||||
}
|
||||
|
||||
/// Ex. returns `fs` for `node:fs`
|
||||
|
@ -2307,7 +2310,7 @@ mod tests {
|
|||
assert_eq!(types_package_name("name"), "@types/name");
|
||||
assert_eq!(
|
||||
types_package_name("@scoped/package"),
|
||||
"@types/@scoped__package"
|
||||
"@types/scoped__package"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6493,136 +6493,6 @@ fn lsp_code_actions_deno_cache_all() {
|
|||
client.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[timeout(300_000)]
|
||||
fn lsp_code_actions_deno_types_for_npm() {
|
||||
let context = TestContextBuilder::new()
|
||||
.use_http_server()
|
||||
.use_temp_cwd()
|
||||
.add_npm_env_vars()
|
||||
.build();
|
||||
let temp_dir = context.temp_dir();
|
||||
temp_dir.write("deno.json", json!({}).to_string());
|
||||
temp_dir.write(
|
||||
"package.json",
|
||||
json!({
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"@types/react": "^18.3.10",
|
||||
},
|
||||
})
|
||||
.to_string(),
|
||||
);
|
||||
temp_dir.write(
|
||||
"managed_node_modules/deno.json",
|
||||
json!({
|
||||
"nodeModulesDir": false,
|
||||
})
|
||||
.to_string(),
|
||||
);
|
||||
context.run_npm("install");
|
||||
let mut client = context.new_lsp_command().build();
|
||||
client.initialize_default();
|
||||
client.did_open(json!({
|
||||
"textDocument": {
|
||||
"uri": temp_dir.url().join("file.ts").unwrap(),
|
||||
"languageId": "typescript",
|
||||
"version": 1,
|
||||
"text": "import \"react\";\n",
|
||||
}
|
||||
}));
|
||||
let res = client.write_request(
|
||||
"textDocument/codeAction",
|
||||
json!({
|
||||
"textDocument": {
|
||||
"uri": temp_dir.url().join("file.ts").unwrap(),
|
||||
},
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 7 },
|
||||
"end": { "line": 0, "character": 7 },
|
||||
},
|
||||
"context": { "diagnostics": [], "only": ["quickfix"] },
|
||||
}),
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
json!([
|
||||
{
|
||||
"title": "Add @ts-types directive for \"@types/react\"",
|
||||
"kind": "quickfix",
|
||||
"edit": {
|
||||
"changes": {
|
||||
temp_dir.url().join("file.ts").unwrap(): [
|
||||
{
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 0 },
|
||||
"end": { "line": 0, "character": 0 },
|
||||
},
|
||||
"newText": "// @ts-types=\"@types/react\"\n",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
]),
|
||||
);
|
||||
client.did_open(json!({
|
||||
"textDocument": {
|
||||
"uri": temp_dir.url().join("managed_node_modules/file.ts").unwrap(),
|
||||
"languageId": "typescript",
|
||||
"version": 1,
|
||||
"text": "import \"npm:react\";\n",
|
||||
}
|
||||
}));
|
||||
client.write_request(
|
||||
"workspace/executeCommand",
|
||||
json!({
|
||||
"command": "deno.cache",
|
||||
"arguments": [
|
||||
[],
|
||||
temp_dir.url().join("managed_node_modules/file.ts").unwrap(),
|
||||
],
|
||||
}),
|
||||
);
|
||||
client.read_diagnostics();
|
||||
let res = client.write_request(
|
||||
"textDocument/codeAction",
|
||||
json!({
|
||||
"textDocument": {
|
||||
"uri": temp_dir.url().join("managed_node_modules/file.ts").unwrap(),
|
||||
},
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 7 },
|
||||
"end": { "line": 0, "character": 7 },
|
||||
},
|
||||
"context": { "diagnostics": [], "only": ["quickfix"] },
|
||||
}),
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
json!([
|
||||
{
|
||||
"title": "Add @ts-types directive for \"npm:@types/react@^18.3.10\"",
|
||||
"kind": "quickfix",
|
||||
"edit": {
|
||||
"changes": {
|
||||
temp_dir.url().join("managed_node_modules/file.ts").unwrap(): [
|
||||
{
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 0 },
|
||||
"end": { "line": 0, "character": 0 },
|
||||
},
|
||||
"newText": "// @ts-types=\"npm:@types/react@^18.3.10\"\n",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
]),
|
||||
);
|
||||
client.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[timeout(300_000)]
|
||||
fn lsp_cache_on_save() {
|
||||
|
@ -17713,3 +17583,151 @@ fn ambient_module_errors_suppressed() {
|
|||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[timeout(300_000)]
|
||||
fn definitely_typed_fallback() {
|
||||
let context = TestContextBuilder::for_npm().use_temp_cwd().build();
|
||||
let mut client = context.new_lsp_command().build();
|
||||
let temp = context.temp_dir();
|
||||
let temp_dir = temp.path();
|
||||
let source = source_file(
|
||||
temp_dir.join("index.ts"),
|
||||
r#"
|
||||
import { foo } from "@denotest/index-export-no-types";
|
||||
|
||||
const _res: boolean = foo(1, 2);
|
||||
console.log(_res);
|
||||
"#,
|
||||
);
|
||||
|
||||
let deno_json = json!({
|
||||
"imports": {
|
||||
"@denotest/index-export-no-types": "npm:@denotest/index-export-no-types@1.0.0",
|
||||
"@types/denotest__index-export-no-types": "npm:@types/denotest__index-export-no-types@1.0.0",
|
||||
}
|
||||
});
|
||||
|
||||
temp.write("deno.json", deno_json.to_string());
|
||||
|
||||
client.initialize_default();
|
||||
|
||||
for node_modules_dir in ["none", "auto", "manual"] {
|
||||
let mut deno_json = deno_json.clone();
|
||||
deno_json["nodeModulesDir"] = json!(node_modules_dir);
|
||||
temp.write("deno.json", deno_json.to_string());
|
||||
context.run_deno("install");
|
||||
client.did_change_watched_files(json!({
|
||||
"changes": [{
|
||||
"uri": temp.url().join("deno.json").unwrap(),
|
||||
"type": 2,
|
||||
}],
|
||||
}));
|
||||
client.read_diagnostics();
|
||||
|
||||
let diagnostics = client.did_open_file(&source);
|
||||
eprintln!("{:#?}", diagnostics.all());
|
||||
assert_eq!(diagnostics.all().len(), 1);
|
||||
assert_eq!(
|
||||
json!(diagnostics.all()),
|
||||
json!([
|
||||
{
|
||||
"range": source.range_of("_res"),
|
||||
"severity": 1,
|
||||
"code": 2322,
|
||||
"source": "deno-ts",
|
||||
"message": "Type 'number' is not assignable to type 'boolean'."
|
||||
}
|
||||
])
|
||||
);
|
||||
client.did_close_file(&source);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[timeout(300_000)]
|
||||
fn do_not_auto_import_from_definitely_typed() {
|
||||
for node_modules_dir in ["none", "auto", "manual"] {
|
||||
let context = TestContextBuilder::for_npm().use_temp_cwd().build();
|
||||
let mut client = context.new_lsp_command().build();
|
||||
let temp = context.temp_dir();
|
||||
let temp_dir = temp.path();
|
||||
let source = source_file(
|
||||
temp_dir.join("index.ts"),
|
||||
r#"
|
||||
import {} from "@denotest/index-export-no-types";
|
||||
|
||||
foo
|
||||
"#,
|
||||
);
|
||||
|
||||
let deno_json = json!({
|
||||
"imports": {
|
||||
"@denotest/index-export-no-types": "npm:@denotest/index-export-no-types@1.0.0",
|
||||
"@types/denotest__index-export-no-types": "npm:@types/denotest__index-export-no-types@1.0.0",
|
||||
},
|
||||
"nodeModulesDir": node_modules_dir
|
||||
});
|
||||
if node_modules_dir == "manual" {
|
||||
// TODO: there's a (pre-existing) bug that prevents auto-imports
|
||||
// from working with nodeModuleDir "manual" w/o a package.json
|
||||
temp.write(
|
||||
"package.json",
|
||||
json!({
|
||||
"dependencies": {
|
||||
"@denotest/index-export-no-types": "1.0.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/denotest__index-export-no-types": "1.0.0",
|
||||
}
|
||||
})
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
temp.write("deno.json", deno_json.to_string());
|
||||
context.run_deno("install");
|
||||
|
||||
client.initialize_default();
|
||||
|
||||
let mut deno_json = deno_json.clone();
|
||||
deno_json["nodeModulesDir"] = json!(node_modules_dir);
|
||||
temp.write("deno.json", deno_json.to_string());
|
||||
client.did_change_watched_files(json!({
|
||||
"changes": [{
|
||||
"uri": temp.url().join("deno.json").unwrap(),
|
||||
"type": 2,
|
||||
}],
|
||||
}));
|
||||
|
||||
client.did_open_file(&source);
|
||||
let pos = source.range_of("foo").end;
|
||||
let completions = client.get_completion_list(
|
||||
source.uri().as_str(),
|
||||
(pos.line as usize, pos.character as usize),
|
||||
json!({
|
||||
"triggerKind": 2,
|
||||
}),
|
||||
);
|
||||
let item = completions
|
||||
.items
|
||||
.iter()
|
||||
.find(|it| it.label == "foo")
|
||||
.unwrap();
|
||||
eprintln!("item: {item:#?}");
|
||||
let res = client.write_request("completionItem/resolve", json!(item));
|
||||
eprintln!("resolved: {res}");
|
||||
assert_json_subset(
|
||||
res,
|
||||
json!({
|
||||
"label": "foo",
|
||||
"detail": "Update import from \"@denotest/index-export-no-types\"\n\nfunction foo(a: number, b: number): number",
|
||||
"additionalTextEdits": [json!({
|
||||
"range": source.range_of("{}"),
|
||||
"newText": "{ foo }"
|
||||
})]
|
||||
}),
|
||||
);
|
||||
|
||||
client.did_close_file(&source);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export function foo(a, b) {
|
||||
return a + b;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "@denoetst/index-export-no-types",
|
||||
"version": "1.0.0",
|
||||
"type": "module"
|
||||
}
|
1
tests/registry/npm/@types/denotest__index-export-no-types/0.5.0/index.d.ts
vendored
Normal file
1
tests/registry/npm/@types/denotest__index-export-no-types/0.5.0/index.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
export function foo(a: number, b: number): string;
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "@types/denotest__index-export-no-types",
|
||||
"version": "0.5.0",
|
||||
"main": "",
|
||||
"types": "index.d.ts",
|
||||
"type": "module"
|
||||
}
|
1
tests/registry/npm/@types/denotest__index-export-no-types/1.0.0/index.d.ts
vendored
Normal file
1
tests/registry/npm/@types/denotest__index-export-no-types/1.0.0/index.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
export function foo(a: number, b: number): number;
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "@types/denotest__index-export-no-types",
|
||||
"version": "1.0.0",
|
||||
"main": "",
|
||||
"types": "index.d.ts",
|
||||
"type": "module"
|
||||
}
|
76
tests/specs/check/definitely_typed/__test__.jsonc
Normal file
76
tests/specs/check/definitely_typed/__test__.jsonc
Normal file
|
@ -0,0 +1,76 @@
|
|||
{
|
||||
"tempDir": true,
|
||||
"envs": {
|
||||
"RUST_BACKTRACE": "0"
|
||||
},
|
||||
"tests": {
|
||||
"node_modules_dir_auto": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "run -A ./set_node_modules_dir.ts auto",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "i",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "check main.ts",
|
||||
"output": "type_mismatch.out",
|
||||
"exitCode": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules_dir_none": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "run -A ./set_node_modules_dir.ts none",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "i",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "check main.ts",
|
||||
"output": "type_mismatch.out",
|
||||
"exitCode": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules_dir_manual": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "run -A ./set_node_modules_dir.ts manual",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "i",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "check main.ts",
|
||||
"output": "type_mismatch.out",
|
||||
"exitCode": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"respects_ts_types": {
|
||||
"steps": [
|
||||
{
|
||||
"args": "run -A ./set_node_modules_dir.ts auto",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "i",
|
||||
"output": "[WILDCARD]"
|
||||
},
|
||||
{
|
||||
"args": "check ts_types.ts",
|
||||
"output": "ts_types_mismatch.out",
|
||||
"exitCode": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
7
tests/specs/check/definitely_typed/deno.json
Normal file
7
tests/specs/check/definitely_typed/deno.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"imports": {
|
||||
"@denotest/index-export-no-types": "npm:@denotest/index-export-no-types@1.0.0",
|
||||
"@types/denotest__index-export-no-types": "npm:@types/denotest__index-export-no-types@1.0.0",
|
||||
"oldtypes": "npm:@types/denotest__index-export-no-types@0.5.0"
|
||||
}
|
||||
}
|
3
tests/specs/check/definitely_typed/main.ts
Normal file
3
tests/specs/check/definitely_typed/main.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import { foo } from "@denotest/index-export-no-types";
|
||||
|
||||
const _res: boolean = foo(1, 2);
|
|
@ -0,0 +1,8 @@
|
|||
if (Deno.args.length !== 1) {
|
||||
console.error("Usage: set_node_modules_dir.ts <setting>");
|
||||
Deno.exit(1);
|
||||
}
|
||||
const setting = Deno.args[0].trim();
|
||||
const denoJson = JSON.parse(Deno.readTextFileSync("./deno.json"));
|
||||
denoJson["nodeModulesDir"] = setting;
|
||||
Deno.writeTextFileSync("./deno.json", JSON.stringify(denoJson, null, 2));
|
4
tests/specs/check/definitely_typed/ts_types.ts
Normal file
4
tests/specs/check/definitely_typed/ts_types.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
// @ts-types="oldtypes"
|
||||
import { foo } from "@denotest/index-export-no-types";
|
||||
|
||||
const _res: boolean = foo(1, 2);
|
7
tests/specs/check/definitely_typed/ts_types_mismatch.out
Normal file
7
tests/specs/check/definitely_typed/ts_types_mismatch.out
Normal file
|
@ -0,0 +1,7 @@
|
|||
Check [WILDCARD]ts_types.ts
|
||||
TS2322 [ERROR]: Type 'string' is not assignable to type 'boolean'.
|
||||
const _res: boolean = foo(1, 2);
|
||||
~~~~
|
||||
at [WILDCARD]ts_types.ts:4:7
|
||||
|
||||
error: Type checking failed.
|
7
tests/specs/check/definitely_typed/type_mismatch.out
Normal file
7
tests/specs/check/definitely_typed/type_mismatch.out
Normal file
|
@ -0,0 +1,7 @@
|
|||
Check [WILDCARD]main.ts
|
||||
TS2322 [ERROR]: Type 'number' is not assignable to type 'boolean'.
|
||||
const _res: boolean = foo(1, 2);
|
||||
~~~~
|
||||
at [WILDCARD]main.ts:3:7
|
||||
|
||||
error: Type checking failed.
|
|
@ -186,6 +186,17 @@ impl TestNpmRegistry {
|
|||
return Some((DENOTEST3_SCOPE_NAME, package_name_with_path));
|
||||
}
|
||||
|
||||
let prefix1 = format!("/{}/", "@types");
|
||||
let prefix2 = format!("/{}%2f", "@types");
|
||||
let maybe_package_name_with_path = uri_path
|
||||
.strip_prefix(&prefix1)
|
||||
.or_else(|| uri_path.strip_prefix(&prefix2));
|
||||
if let Some(package_name_with_path) = maybe_package_name_with_path {
|
||||
if package_name_with_path.starts_with("denotest") {
|
||||
return Some(("@types", package_name_with_path));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue