mirror of
https://github.com/denoland/deno.git
synced 2024-12-20 22:34:46 -05:00
115a306656
Ensures a dynamic import in a CJS file will consider the referrer as an import for node resolution. Also adds fixes (adds) support for `"resolution-mode"` in TypeScript.
228 lines
7.2 KiB
Rust
228 lines
7.2 KiB
Rust
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
|
|
|
use std::borrow::Cow;
|
|
use std::cell::RefCell;
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
|
|
use deno_ast::SourceRange;
|
|
use deno_config::workspace::WorkspaceResolver;
|
|
use deno_core::anyhow::anyhow;
|
|
use deno_graph::source::ResolutionKind;
|
|
use deno_graph::source::ResolveError;
|
|
use deno_graph::Range;
|
|
use deno_lint::diagnostic::LintDiagnosticDetails;
|
|
use deno_lint::diagnostic::LintDiagnosticRange;
|
|
use deno_lint::diagnostic::LintFix;
|
|
use deno_lint::diagnostic::LintFixChange;
|
|
use deno_lint::rules::LintRule;
|
|
use deno_resolver::sloppy_imports::SloppyImportsResolution;
|
|
use deno_resolver::sloppy_imports::SloppyImportsResolutionKind;
|
|
use text_lines::LineAndColumnIndex;
|
|
|
|
use crate::graph_util::CliJsrUrlProvider;
|
|
use crate::resolver::CliSloppyImportsResolver;
|
|
|
|
use super::ExtendedLintRule;
|
|
|
|
#[derive(Debug)]
|
|
pub struct NoSloppyImportsRule {
|
|
sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
|
|
// None for making printing out the lint rules easy
|
|
workspace_resolver: Option<Arc<WorkspaceResolver>>,
|
|
}
|
|
|
|
impl NoSloppyImportsRule {
|
|
pub fn new(
|
|
sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
|
|
workspace_resolver: Option<Arc<WorkspaceResolver>>,
|
|
) -> Self {
|
|
NoSloppyImportsRule {
|
|
sloppy_imports_resolver,
|
|
workspace_resolver,
|
|
}
|
|
}
|
|
}
|
|
|
|
const CODE: &str = "no-sloppy-imports";
|
|
const DOCS_URL: &str = "https://docs.deno.com/runtime/manual/tools/unstable_flags/#--unstable-sloppy-imports";
|
|
|
|
impl ExtendedLintRule for NoSloppyImportsRule {
|
|
fn supports_incremental_cache(&self) -> bool {
|
|
// only allow the incremental cache when we don't
|
|
// do sloppy import resolution because sloppy import
|
|
// resolution requires knowing about the surrounding files
|
|
// in addition to the current one
|
|
self.sloppy_imports_resolver.is_none() || self.workspace_resolver.is_none()
|
|
}
|
|
|
|
fn help_docs_url(&self) -> Cow<'static, str> {
|
|
Cow::Borrowed(DOCS_URL)
|
|
}
|
|
|
|
fn into_base(self: Box<Self>) -> Box<dyn LintRule> {
|
|
self
|
|
}
|
|
}
|
|
|
|
impl LintRule for NoSloppyImportsRule {
|
|
fn lint_program_with_ast_view<'view>(
|
|
&self,
|
|
context: &mut deno_lint::context::Context<'view>,
|
|
_program: deno_lint::Program<'view>,
|
|
) {
|
|
let Some(workspace_resolver) = &self.workspace_resolver else {
|
|
return;
|
|
};
|
|
let Some(sloppy_imports_resolver) = &self.sloppy_imports_resolver else {
|
|
return;
|
|
};
|
|
if context.specifier().scheme() != "file" {
|
|
return;
|
|
}
|
|
|
|
let resolver = SloppyImportCaptureResolver {
|
|
workspace_resolver,
|
|
sloppy_imports_resolver,
|
|
captures: Default::default(),
|
|
};
|
|
|
|
// fill this and capture the sloppy imports in the resolver
|
|
deno_graph::parse_module_from_ast(deno_graph::ParseModuleFromAstOptions {
|
|
graph_kind: deno_graph::GraphKind::All,
|
|
specifier: context.specifier().clone(),
|
|
maybe_headers: None,
|
|
parsed_source: context.parsed_source(),
|
|
// ignore resolving dynamic imports like import(`./dir/${something}`)
|
|
file_system: &deno_graph::source::NullFileSystem,
|
|
jsr_url_provider: &CliJsrUrlProvider,
|
|
maybe_resolver: Some(&resolver),
|
|
// don't bother resolving npm specifiers
|
|
maybe_npm_resolver: None,
|
|
});
|
|
|
|
for (referrer, sloppy_import) in resolver.captures.borrow_mut().drain() {
|
|
let start_range =
|
|
context.text_info().loc_to_source_pos(LineAndColumnIndex {
|
|
line_index: referrer.range.start.line,
|
|
column_index: referrer.range.start.character,
|
|
});
|
|
let end_range =
|
|
context.text_info().loc_to_source_pos(LineAndColumnIndex {
|
|
line_index: referrer.range.end.line,
|
|
column_index: referrer.range.end.character,
|
|
});
|
|
let source_range = SourceRange::new(start_range, end_range);
|
|
context.add_diagnostic_details(
|
|
Some(LintDiagnosticRange {
|
|
range: source_range,
|
|
description: None,
|
|
text_info: context.text_info().clone(),
|
|
}),
|
|
LintDiagnosticDetails {
|
|
message: "Sloppy imports are not allowed.".to_string(),
|
|
code: CODE.to_string(),
|
|
custom_docs_url: Some(DOCS_URL.to_string()),
|
|
fixes: context
|
|
.specifier()
|
|
.make_relative(sloppy_import.as_specifier())
|
|
.map(|relative| {
|
|
vec![LintFix {
|
|
description: Cow::Owned(sloppy_import.as_quick_fix_message()),
|
|
changes: vec![LintFixChange {
|
|
new_text: Cow::Owned({
|
|
let relative = if relative.starts_with("../") {
|
|
relative
|
|
} else {
|
|
format!("./{}", relative)
|
|
};
|
|
let current_text =
|
|
context.text_info().range_text(&source_range);
|
|
if current_text.starts_with('"') {
|
|
format!("\"{}\"", relative)
|
|
} else if current_text.starts_with('\'') {
|
|
format!("'{}'", relative)
|
|
} else {
|
|
relative
|
|
}
|
|
}),
|
|
range: source_range,
|
|
}],
|
|
}]
|
|
})
|
|
.unwrap_or_default(),
|
|
hint: None,
|
|
info: vec![],
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
fn code(&self) -> &'static str {
|
|
CODE
|
|
}
|
|
|
|
fn docs(&self) -> &'static str {
|
|
include_str!("no_sloppy_imports.md")
|
|
}
|
|
|
|
fn tags(&self) -> &'static [&'static str] {
|
|
&["recommended"]
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct SloppyImportCaptureResolver<'a> {
|
|
workspace_resolver: &'a WorkspaceResolver,
|
|
sloppy_imports_resolver: &'a CliSloppyImportsResolver,
|
|
captures: RefCell<HashMap<Range, SloppyImportsResolution>>,
|
|
}
|
|
|
|
impl<'a> deno_graph::source::Resolver for SloppyImportCaptureResolver<'a> {
|
|
fn resolve(
|
|
&self,
|
|
specifier_text: &str,
|
|
referrer_range: &Range,
|
|
resolution_kind: ResolutionKind,
|
|
) -> Result<deno_ast::ModuleSpecifier, deno_graph::source::ResolveError> {
|
|
let resolution = self
|
|
.workspace_resolver
|
|
.resolve(specifier_text, &referrer_range.specifier)
|
|
.map_err(|err| ResolveError::Other(err.into()))?;
|
|
|
|
match resolution {
|
|
deno_config::workspace::MappedResolution::Normal {
|
|
specifier, ..
|
|
}
|
|
| deno_config::workspace::MappedResolution::ImportMap {
|
|
specifier, ..
|
|
} => match self.sloppy_imports_resolver.resolve(
|
|
&specifier,
|
|
match resolution_kind {
|
|
ResolutionKind::Execution => SloppyImportsResolutionKind::Execution,
|
|
ResolutionKind::Types => SloppyImportsResolutionKind::Types,
|
|
},
|
|
) {
|
|
Some(res) => {
|
|
self
|
|
.captures
|
|
.borrow_mut()
|
|
.entry(referrer_range.clone())
|
|
.or_insert_with(|| res.clone());
|
|
Ok(res.into_specifier())
|
|
}
|
|
None => Ok(specifier),
|
|
},
|
|
deno_config::workspace::MappedResolution::WorkspaceJsrPackage {
|
|
..
|
|
}
|
|
| deno_config::workspace::MappedResolution::WorkspaceNpmPackage {
|
|
..
|
|
}
|
|
| deno_config::workspace::MappedResolution::PackageJson { .. } => {
|
|
// this error is ignored
|
|
Err(ResolveError::Other(anyhow!("")))
|
|
}
|
|
}
|
|
}
|
|
}
|