summaryrefslogtreecommitdiff
path: root/cli/tools/lint/rules/no_sloppy_imports.rs
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2024-07-25 09:07:59 -0400
committerGitHub <noreply@github.com>2024-07-25 09:07:59 -0400
commit763f05e74dfd0032b238603f625893a52e363591 (patch)
treec6a71559472755919358afa53eecac206cad80a9 /cli/tools/lint/rules/no_sloppy_imports.rs
parentef78d317f084ffe633253acd138a48a425113fa7 (diff)
fix(unstable): move sloppy-import warnings to lint rule (#24710)
Adds a new `no-sloppy-imports` lint rule and cleans up the lint code. Closes #22844 Closes https://github.com/denoland/deno_lint/issues/1293
Diffstat (limited to 'cli/tools/lint/rules/no_sloppy_imports.rs')
-rw-r--r--cli/tools/lint/rules/no_sloppy_imports.rs214
1 files changed, 214 insertions, 0 deletions
diff --git a/cli/tools/lint/rules/no_sloppy_imports.rs b/cli/tools/lint/rules/no_sloppy_imports.rs
new file mode 100644
index 000000000..b5e057bfc
--- /dev/null
+++ b/cli/tools/lint/rules/no_sloppy_imports.rs
@@ -0,0 +1,214 @@
+// 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::ResolutionMode;
+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 text_lines::LineAndColumnIndex;
+
+use crate::graph_util::CliJsrUrlProvider;
+use crate::resolver::SloppyImportsResolution;
+use crate::resolver::SloppyImportsResolver;
+
+use super::ExtendedLintRule;
+
+#[derive(Debug)]
+pub struct NoSloppyImportsRule {
+ sloppy_imports_resolver: Option<Arc<SloppyImportsResolver>>,
+ // None for making printing out the lint rules easy
+ workspace_resolver: Option<Arc<WorkspaceResolver>>,
+}
+
+impl NoSloppyImportsRule {
+ pub fn new(
+ sloppy_imports_resolver: Option<Arc<SloppyImportsResolver>>,
+ 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(),
+ };
+
+ 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 (range, sloppy_import) in resolver.captures.borrow_mut().drain() {
+ let start_range =
+ context.text_info().loc_to_source_pos(LineAndColumnIndex {
+ line_index: range.start.line,
+ column_index: range.start.character,
+ });
+ let end_range =
+ context.text_info().loc_to_source_pos(LineAndColumnIndex {
+ line_index: range.end.line,
+ column_index: 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 SloppyImportsResolver,
+ captures: RefCell<HashMap<Range, SloppyImportsResolution>>,
+}
+
+impl<'a> deno_graph::source::Resolver for SloppyImportCaptureResolver<'a> {
+ fn resolve(
+ &self,
+ specifier_text: &str,
+ referrer_range: &Range,
+ mode: ResolutionMode,
+ ) -> 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, mode) {
+ 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::WorkspaceNpmPackage {
+ ..
+ }
+ | deno_config::workspace::MappedResolution::PackageJson { .. } => {
+ Err(ResolveError::Other(anyhow!("")))
+ }
+ }
+ }
+}