diff options
author | David Sherret <dsherret@users.noreply.github.com> | 2024-07-25 09:07:59 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-25 09:07:59 -0400 |
commit | 763f05e74dfd0032b238603f625893a52e363591 (patch) | |
tree | c6a71559472755919358afa53eecac206cad80a9 /cli/tools/lint/rules/no_sloppy_imports.rs | |
parent | ef78d317f084ffe633253acd138a48a425113fa7 (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.rs | 214 |
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!(""))) + } + } + } +} |