mirror of
https://github.com/denoland/deno.git
synced 2024-11-24 15:19:26 -05:00
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
This commit is contained in:
parent
ef78d317f0
commit
763f05e74d
63 changed files with 1601 additions and 955 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -1650,9 +1650,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deno_lint"
|
name = "deno_lint"
|
||||||
version = "0.60.1"
|
version = "0.61.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "598de34cdfb2a8ed335d8f2e33a75249445a9f81c8092a069fc562c2d5cdb9b6"
|
checksum = "d127c05c87cb0fa2a59ad9bc70084f06731a5117c14888253269b6e921cfaef1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"deno_ast",
|
"deno_ast",
|
||||||
|
|
|
@ -70,7 +70,7 @@ deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"]
|
||||||
deno_doc = { version = "0.144.0", features = ["html", "syntect"] }
|
deno_doc = { version = "0.144.0", features = ["html", "syntect"] }
|
||||||
deno_emit = "=0.43.1"
|
deno_emit = "=0.43.1"
|
||||||
deno_graph = { version = "=0.80.1", features = ["tokio_executor"] }
|
deno_graph = { version = "=0.80.1", features = ["tokio_executor"] }
|
||||||
deno_lint = { version = "=0.60.1", features = ["docs"] }
|
deno_lint = { version = "=0.61.0", features = ["docs"] }
|
||||||
deno_lockfile.workspace = true
|
deno_lockfile.workspace = true
|
||||||
deno_npm = "=0.21.4"
|
deno_npm = "=0.21.4"
|
||||||
deno_package_json.workspace = true
|
deno_package_json.workspace = true
|
||||||
|
|
|
@ -45,6 +45,7 @@ use crate::resolver::SloppyImportsResolver;
|
||||||
use crate::standalone::DenoCompileBinaryWriter;
|
use crate::standalone::DenoCompileBinaryWriter;
|
||||||
use crate::tools::check::TypeChecker;
|
use crate::tools::check::TypeChecker;
|
||||||
use crate::tools::coverage::CoverageCollector;
|
use crate::tools::coverage::CoverageCollector;
|
||||||
|
use crate::tools::lint::LintRuleProvider;
|
||||||
use crate::tools::run::hmr::HmrRunner;
|
use crate::tools::run::hmr::HmrRunner;
|
||||||
use crate::util::file_watcher::WatcherCommunicator;
|
use crate::util::file_watcher::WatcherCommunicator;
|
||||||
use crate::util::fs::canonicalize_path_maybe_not_exists;
|
use crate::util::fs::canonicalize_path_maybe_not_exists;
|
||||||
|
@ -179,6 +180,7 @@ struct CliFactoryServices {
|
||||||
node_code_translator: Deferred<Arc<CliNodeCodeTranslator>>,
|
node_code_translator: Deferred<Arc<CliNodeCodeTranslator>>,
|
||||||
node_resolver: Deferred<Arc<NodeResolver>>,
|
node_resolver: Deferred<Arc<NodeResolver>>,
|
||||||
npm_resolver: Deferred<Arc<dyn CliNpmResolver>>,
|
npm_resolver: Deferred<Arc<dyn CliNpmResolver>>,
|
||||||
|
sloppy_imports_resolver: Deferred<Option<Arc<SloppyImportsResolver>>>,
|
||||||
text_only_progress_bar: Deferred<ProgressBar>,
|
text_only_progress_bar: Deferred<ProgressBar>,
|
||||||
type_checker: Deferred<Arc<TypeChecker>>,
|
type_checker: Deferred<Arc<TypeChecker>>,
|
||||||
cjs_resolutions: Deferred<Arc<CjsResolutionStore>>,
|
cjs_resolutions: Deferred<Arc<CjsResolutionStore>>,
|
||||||
|
@ -397,6 +399,23 @@ impl CliFactory {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sloppy_imports_resolver(
|
||||||
|
&self,
|
||||||
|
) -> Result<Option<&Arc<SloppyImportsResolver>>, AnyError> {
|
||||||
|
self
|
||||||
|
.services
|
||||||
|
.sloppy_imports_resolver
|
||||||
|
.get_or_try_init(|| {
|
||||||
|
Ok(
|
||||||
|
self
|
||||||
|
.cli_options()?
|
||||||
|
.unstable_sloppy_imports()
|
||||||
|
.then(|| Arc::new(SloppyImportsResolver::new(self.fs().clone()))),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map(|maybe| maybe.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn workspace_resolver(
|
pub async fn workspace_resolver(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<&Arc<WorkspaceResolver>, AnyError> {
|
) -> Result<&Arc<WorkspaceResolver>, AnyError> {
|
||||||
|
@ -440,11 +459,7 @@ impl CliFactory {
|
||||||
async {
|
async {
|
||||||
let cli_options = self.cli_options()?;
|
let cli_options = self.cli_options()?;
|
||||||
Ok(Arc::new(CliGraphResolver::new(CliGraphResolverOptions {
|
Ok(Arc::new(CliGraphResolver::new(CliGraphResolverOptions {
|
||||||
sloppy_imports_resolver: if cli_options.unstable_sloppy_imports() {
|
sloppy_imports_resolver: self.sloppy_imports_resolver()?.cloned(),
|
||||||
Some(SloppyImportsResolver::new(self.fs().clone()))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
node_resolver: Some(self.cli_node_resolver().await?.clone()),
|
node_resolver: Some(self.cli_node_resolver().await?.clone()),
|
||||||
npm_resolver: if cli_options.no_npm() {
|
npm_resolver: if cli_options.no_npm() {
|
||||||
None
|
None
|
||||||
|
@ -524,6 +539,13 @@ impl CliFactory {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn lint_rule_provider(&self) -> Result<LintRuleProvider, AnyError> {
|
||||||
|
Ok(LintRuleProvider::new(
|
||||||
|
self.sloppy_imports_resolver()?.cloned(),
|
||||||
|
Some(self.workspace_resolver().await?.clone()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn node_resolver(&self) -> Result<&Arc<NodeResolver>, AnyError> {
|
pub async fn node_resolver(&self) -> Result<&Arc<NodeResolver>, AnyError> {
|
||||||
self
|
self
|
||||||
.services
|
.services
|
||||||
|
|
|
@ -750,8 +750,8 @@ fn enhanced_sloppy_imports_error_message(
|
||||||
ModuleError::LoadingErr(specifier, _, ModuleLoadError::Loader(_)) // ex. "Is a directory" error
|
ModuleError::LoadingErr(specifier, _, ModuleLoadError::Loader(_)) // ex. "Is a directory" error
|
||||||
| ModuleError::Missing(specifier, _) => {
|
| ModuleError::Missing(specifier, _) => {
|
||||||
let additional_message = SloppyImportsResolver::new(fs.clone())
|
let additional_message = SloppyImportsResolver::new(fs.clone())
|
||||||
.resolve(specifier, ResolutionMode::Execution)
|
.resolve(specifier, ResolutionMode::Execution)?
|
||||||
.as_suggestion_message()?;
|
.as_suggestion_message();
|
||||||
Some(format!(
|
Some(format!(
|
||||||
"{} {} or run with --unstable-sloppy-imports",
|
"{} {} or run with --unstable-sloppy-imports",
|
||||||
error,
|
error,
|
||||||
|
|
|
@ -8,8 +8,8 @@ use super::resolver::LspResolver;
|
||||||
use super::tsc;
|
use super::tsc;
|
||||||
|
|
||||||
use crate::args::jsr_url;
|
use crate::args::jsr_url;
|
||||||
use crate::tools::lint::create_linter;
|
use crate::tools::lint::CliLinter;
|
||||||
use deno_lint::linter::LintConfig;
|
use deno_lint::diagnostic::LintDiagnosticRange;
|
||||||
use deno_runtime::fs_util::specifier_to_file_path;
|
use deno_runtime::fs_util::specifier_to_file_path;
|
||||||
|
|
||||||
use deno_ast::SourceRange;
|
use deno_ast::SourceRange;
|
||||||
|
@ -23,8 +23,6 @@ use deno_core::serde::Serialize;
|
||||||
use deno_core::serde_json;
|
use deno_core::serde_json;
|
||||||
use deno_core::serde_json::json;
|
use deno_core::serde_json::json;
|
||||||
use deno_core::ModuleSpecifier;
|
use deno_core::ModuleSpecifier;
|
||||||
use deno_lint::diagnostic::LintDiagnostic;
|
|
||||||
use deno_lint::rules::LintRule;
|
|
||||||
use deno_runtime::deno_node::NpmResolver;
|
use deno_runtime::deno_node::NpmResolver;
|
||||||
use deno_runtime::deno_node::PathClean;
|
use deno_runtime::deno_node::PathClean;
|
||||||
use deno_semver::jsr::JsrPackageNvReference;
|
use deno_semver::jsr::JsrPackageNvReference;
|
||||||
|
@ -149,8 +147,10 @@ impl Reference {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_lsp_range_from_diagnostic(diagnostic: &LintDiagnostic) -> Range {
|
fn as_lsp_range_from_lint_diagnostic(
|
||||||
as_lsp_range(diagnostic.range, &diagnostic.text_info)
|
diagnostic_range: &LintDiagnosticRange,
|
||||||
|
) -> Range {
|
||||||
|
as_lsp_range(diagnostic_range.range, &diagnostic_range.text_info)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_lsp_range(
|
fn as_lsp_range(
|
||||||
|
@ -173,37 +173,39 @@ fn as_lsp_range(
|
||||||
|
|
||||||
pub fn get_lint_references(
|
pub fn get_lint_references(
|
||||||
parsed_source: &deno_ast::ParsedSource,
|
parsed_source: &deno_ast::ParsedSource,
|
||||||
lint_rules: Vec<&'static dyn LintRule>,
|
linter: &CliLinter,
|
||||||
lint_config: LintConfig,
|
|
||||||
) -> Result<Vec<Reference>, AnyError> {
|
) -> Result<Vec<Reference>, AnyError> {
|
||||||
let linter = create_linter(lint_rules);
|
let lint_diagnostics = linter.lint_with_ast(parsed_source);
|
||||||
let lint_diagnostics = linter.lint_with_ast(parsed_source, lint_config);
|
|
||||||
|
|
||||||
Ok(
|
Ok(
|
||||||
lint_diagnostics
|
lint_diagnostics
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|d| Reference {
|
.filter_map(|d| {
|
||||||
range: as_lsp_range_from_diagnostic(&d),
|
let range = d.range.as_ref()?;
|
||||||
category: Category::Lint {
|
Some(Reference {
|
||||||
message: d.message,
|
range: as_lsp_range_from_lint_diagnostic(range),
|
||||||
code: d.code,
|
category: Category::Lint {
|
||||||
hint: d.hint,
|
message: d.details.message,
|
||||||
quick_fixes: d
|
code: d.details.code.to_string(),
|
||||||
.fixes
|
hint: d.details.hint,
|
||||||
.into_iter()
|
quick_fixes: d
|
||||||
.map(|f| DataQuickFix {
|
.details
|
||||||
description: f.description.to_string(),
|
.fixes
|
||||||
changes: f
|
.into_iter()
|
||||||
.changes
|
.map(|f| DataQuickFix {
|
||||||
.into_iter()
|
description: f.description.to_string(),
|
||||||
.map(|change| DataQuickFixChange {
|
changes: f
|
||||||
range: as_lsp_range(change.range, &d.text_info),
|
.changes
|
||||||
new_text: change.new_text.to_string(),
|
.into_iter()
|
||||||
})
|
.map(|change| DataQuickFixChange {
|
||||||
.collect(),
|
range: as_lsp_range(change.range, &range.text_info),
|
||||||
})
|
new_text: change.new_text.to_string(),
|
||||||
.collect(),
|
})
|
||||||
},
|
.collect(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
},
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -52,12 +52,16 @@ use crate::args::discover_npmrc_from_workspace;
|
||||||
use crate::args::has_flag_env_var;
|
use crate::args::has_flag_env_var;
|
||||||
use crate::args::CliLockfile;
|
use crate::args::CliLockfile;
|
||||||
use crate::args::ConfigFile;
|
use crate::args::ConfigFile;
|
||||||
|
use crate::args::LintFlags;
|
||||||
|
use crate::args::LintOptions;
|
||||||
use crate::args::DENO_FUTURE;
|
use crate::args::DENO_FUTURE;
|
||||||
use crate::cache::FastInsecureHasher;
|
use crate::cache::FastInsecureHasher;
|
||||||
use crate::file_fetcher::FileFetcher;
|
use crate::file_fetcher::FileFetcher;
|
||||||
use crate::lsp::logging::lsp_warn;
|
use crate::lsp::logging::lsp_warn;
|
||||||
use crate::tools::lint::get_configured_rules;
|
use crate::resolver::SloppyImportsResolver;
|
||||||
use crate::tools::lint::ConfiguredRules;
|
use crate::tools::lint::CliLinter;
|
||||||
|
use crate::tools::lint::CliLinterOptions;
|
||||||
|
use crate::tools::lint::LintRuleProvider;
|
||||||
use crate::util::fs::canonicalize_path_maybe_not_exists;
|
use crate::util::fs::canonicalize_path_maybe_not_exists;
|
||||||
|
|
||||||
pub const SETTINGS_SECTION: &str = "deno";
|
pub const SETTINGS_SECTION: &str = "deno";
|
||||||
|
@ -1116,8 +1120,7 @@ pub struct ConfigData {
|
||||||
pub lint_config: Arc<LintConfig>,
|
pub lint_config: Arc<LintConfig>,
|
||||||
pub test_config: Arc<TestConfig>,
|
pub test_config: Arc<TestConfig>,
|
||||||
pub exclude_files: Arc<PathOrPatternSet>,
|
pub exclude_files: Arc<PathOrPatternSet>,
|
||||||
pub deno_lint_config: DenoLintConfig,
|
pub linter: Arc<CliLinter>,
|
||||||
pub lint_rules: Arc<ConfiguredRules>,
|
|
||||||
pub ts_config: Arc<LspTsConfig>,
|
pub ts_config: Arc<LspTsConfig>,
|
||||||
pub byonm: bool,
|
pub byonm: bool,
|
||||||
pub node_modules_dir: Option<PathBuf>,
|
pub node_modules_dir: Option<PathBuf>,
|
||||||
|
@ -1125,6 +1128,7 @@ pub struct ConfigData {
|
||||||
pub lockfile: Option<Arc<CliLockfile>>,
|
pub lockfile: Option<Arc<CliLockfile>>,
|
||||||
pub npmrc: Option<Arc<ResolvedNpmRc>>,
|
pub npmrc: Option<Arc<ResolvedNpmRc>>,
|
||||||
pub resolver: Arc<WorkspaceResolver>,
|
pub resolver: Arc<WorkspaceResolver>,
|
||||||
|
pub sloppy_imports_resolver: Option<Arc<SloppyImportsResolver>>,
|
||||||
pub import_map_from_settings: Option<ModuleSpecifier>,
|
pub import_map_from_settings: Option<ModuleSpecifier>,
|
||||||
watched_files: HashMap<ModuleSpecifier, ConfigWatchedFileType>,
|
watched_files: HashMap<ModuleSpecifier, ConfigWatchedFileType>,
|
||||||
}
|
}
|
||||||
|
@ -1310,10 +1314,7 @@ impl ConfigData {
|
||||||
LintConfig::new_with_base(default_file_pattern_base.clone())
|
LintConfig::new_with_base(default_file_pattern_base.clone())
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let lint_rules = Arc::new(get_configured_rules(
|
|
||||||
lint_config.options.rules.clone(),
|
|
||||||
member_dir.maybe_deno_json().map(|c| c.as_ref()),
|
|
||||||
));
|
|
||||||
let test_config = Arc::new(
|
let test_config = Arc::new(
|
||||||
member_dir
|
member_dir
|
||||||
.to_test_config(FilePatterns::new_with_base(member_dir.dir_path()))
|
.to_test_config(FilePatterns::new_with_base(member_dir.dir_path()))
|
||||||
|
@ -1532,16 +1533,38 @@ impl ConfigData {
|
||||||
.join("\n")
|
.join("\n")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
let unstable_sloppy_imports = std::env::var("DENO_UNSTABLE_SLOPPY_IMPORTS")
|
||||||
|
.is_ok()
|
||||||
|
|| member_dir.workspace.has_unstable("sloppy-imports");
|
||||||
|
let sloppy_imports_resolver = unstable_sloppy_imports.then(|| {
|
||||||
|
Arc::new(SloppyImportsResolver::new_without_stat_cache(Arc::new(
|
||||||
|
deno_runtime::deno_fs::RealFs,
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let resolver = Arc::new(resolver);
|
||||||
|
let lint_rule_provider = LintRuleProvider::new(
|
||||||
|
sloppy_imports_resolver.clone(),
|
||||||
|
Some(resolver.clone()),
|
||||||
|
);
|
||||||
|
let linter = Arc::new(CliLinter::new(CliLinterOptions {
|
||||||
|
configured_rules: lint_rule_provider.resolve_lint_rules(
|
||||||
|
LintOptions::resolve((*lint_config).clone(), &LintFlags::default())
|
||||||
|
.rules,
|
||||||
|
member_dir.maybe_deno_json().map(|c| c.as_ref()),
|
||||||
|
),
|
||||||
|
fix: false,
|
||||||
|
deno_lint_config,
|
||||||
|
}));
|
||||||
|
|
||||||
ConfigData {
|
ConfigData {
|
||||||
scope,
|
scope,
|
||||||
member_dir,
|
member_dir,
|
||||||
resolver: Arc::new(resolver),
|
resolver,
|
||||||
|
sloppy_imports_resolver,
|
||||||
fmt_config,
|
fmt_config,
|
||||||
lint_config,
|
lint_config,
|
||||||
test_config,
|
test_config,
|
||||||
deno_lint_config,
|
linter,
|
||||||
lint_rules,
|
|
||||||
exclude_files,
|
exclude_files,
|
||||||
ts_config: Arc::new(ts_config),
|
ts_config: Arc::new(ts_config),
|
||||||
byonm,
|
byonm,
|
||||||
|
|
|
@ -20,6 +20,9 @@ use crate::graph_util::enhanced_resolution_error_message;
|
||||||
use crate::lsp::lsp_custom::DiagnosticBatchNotificationParams;
|
use crate::lsp::lsp_custom::DiagnosticBatchNotificationParams;
|
||||||
use crate::resolver::SloppyImportsResolution;
|
use crate::resolver::SloppyImportsResolution;
|
||||||
use crate::resolver::SloppyImportsResolver;
|
use crate::resolver::SloppyImportsResolver;
|
||||||
|
use crate::tools::lint::CliLinter;
|
||||||
|
use crate::tools::lint::CliLinterOptions;
|
||||||
|
use crate::tools::lint::LintRuleProvider;
|
||||||
use crate::util::path::to_percent_decoded_str;
|
use crate::util::path::to_percent_decoded_str;
|
||||||
|
|
||||||
use deno_ast::MediaType;
|
use deno_ast::MediaType;
|
||||||
|
@ -40,8 +43,6 @@ use deno_graph::source::ResolveError;
|
||||||
use deno_graph::Resolution;
|
use deno_graph::Resolution;
|
||||||
use deno_graph::ResolutionError;
|
use deno_graph::ResolutionError;
|
||||||
use deno_graph::SpecifierError;
|
use deno_graph::SpecifierError;
|
||||||
use deno_lint::linter::LintConfig as DenoLintConfig;
|
|
||||||
use deno_lint::rules::LintRule;
|
|
||||||
use deno_runtime::deno_fs;
|
use deno_runtime::deno_fs;
|
||||||
use deno_runtime::deno_node;
|
use deno_runtime::deno_node;
|
||||||
use deno_runtime::tokio_util::create_basic_runtime;
|
use deno_runtime::tokio_util::create_basic_runtime;
|
||||||
|
@ -817,25 +818,25 @@ fn generate_lint_diagnostics(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let version = document.maybe_lsp_version();
|
let version = document.maybe_lsp_version();
|
||||||
let (lint_config, deno_lint_config, lint_rules) = config
|
let (lint_config, linter) = config
|
||||||
.tree
|
.tree
|
||||||
.scope_for_specifier(specifier)
|
.scope_for_specifier(specifier)
|
||||||
.and_then(|s| config_data_by_scope.get(s))
|
.and_then(|s| config_data_by_scope.get(s))
|
||||||
.map(|d| {
|
.map(|d| (d.lint_config.clone(), d.linter.clone()))
|
||||||
(
|
|
||||||
d.lint_config.clone(),
|
|
||||||
d.deno_lint_config.clone(),
|
|
||||||
d.lint_rules.clone(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
(
|
(
|
||||||
Arc::new(LintConfig::new_with_base(PathBuf::from("/"))),
|
Arc::new(LintConfig::new_with_base(PathBuf::from("/"))),
|
||||||
DenoLintConfig {
|
Arc::new(CliLinter::new(CliLinterOptions {
|
||||||
default_jsx_factory: None,
|
configured_rules: {
|
||||||
default_jsx_fragment_factory: None,
|
let lint_rule_provider = LintRuleProvider::new(None, None);
|
||||||
},
|
lint_rule_provider.resolve_lint_rules(Default::default(), None)
|
||||||
Arc::default(),
|
},
|
||||||
|
fix: false,
|
||||||
|
deno_lint_config: deno_lint::linter::LintConfig {
|
||||||
|
default_jsx_factory: None,
|
||||||
|
default_jsx_fragment_factory: None,
|
||||||
|
},
|
||||||
|
})),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
diagnostics_vec.push(DiagnosticRecord {
|
diagnostics_vec.push(DiagnosticRecord {
|
||||||
|
@ -845,8 +846,7 @@ fn generate_lint_diagnostics(
|
||||||
diagnostics: generate_document_lint_diagnostics(
|
diagnostics: generate_document_lint_diagnostics(
|
||||||
&document,
|
&document,
|
||||||
&lint_config,
|
&lint_config,
|
||||||
deno_lint_config,
|
&linter,
|
||||||
lint_rules.rules.clone(),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -857,19 +857,16 @@ fn generate_lint_diagnostics(
|
||||||
fn generate_document_lint_diagnostics(
|
fn generate_document_lint_diagnostics(
|
||||||
document: &Document,
|
document: &Document,
|
||||||
lint_config: &LintConfig,
|
lint_config: &LintConfig,
|
||||||
deno_lint_config: DenoLintConfig,
|
linter: &CliLinter,
|
||||||
lint_rules: Vec<&'static dyn LintRule>,
|
|
||||||
) -> Vec<lsp::Diagnostic> {
|
) -> Vec<lsp::Diagnostic> {
|
||||||
if !lint_config.files.matches_specifier(document.specifier()) {
|
if !lint_config.files.matches_specifier(document.specifier()) {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
match document.maybe_parsed_source() {
|
match document.maybe_parsed_source() {
|
||||||
Some(Ok(parsed_source)) => {
|
Some(Ok(parsed_source)) => {
|
||||||
if let Ok(references) = analysis::get_lint_references(
|
if let Ok(references) =
|
||||||
parsed_source,
|
analysis::get_lint_references(parsed_source, linter)
|
||||||
lint_rules,
|
{
|
||||||
deno_lint_config,
|
|
||||||
) {
|
|
||||||
references
|
references
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|r| r.to_diagnostic())
|
.map(|r| r.to_diagnostic())
|
||||||
|
@ -1237,16 +1234,14 @@ impl DenoDiagnostic {
|
||||||
pub fn to_lsp_diagnostic(&self, range: &lsp::Range) -> lsp::Diagnostic {
|
pub fn to_lsp_diagnostic(&self, range: &lsp::Range) -> lsp::Diagnostic {
|
||||||
fn no_local_message(
|
fn no_local_message(
|
||||||
specifier: &ModuleSpecifier,
|
specifier: &ModuleSpecifier,
|
||||||
sloppy_resolution: SloppyImportsResolution,
|
maybe_sloppy_resolution: Option<&SloppyImportsResolution>,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut message = format!(
|
let mut message = format!(
|
||||||
"Unable to load a local module: {}\n",
|
"Unable to load a local module: {}\n",
|
||||||
to_percent_decoded_str(specifier.as_ref())
|
to_percent_decoded_str(specifier.as_ref())
|
||||||
);
|
);
|
||||||
if let Some(additional_message) =
|
if let Some(res) = maybe_sloppy_resolution {
|
||||||
sloppy_resolution.as_suggestion_message()
|
message.push_str(&res.as_suggestion_message());
|
||||||
{
|
|
||||||
message.push_str(&additional_message);
|
|
||||||
message.push('.');
|
message.push('.');
|
||||||
} else {
|
} else {
|
||||||
message.push_str("Please check the file path.");
|
message.push_str("Please check the file path.");
|
||||||
|
@ -1263,15 +1258,15 @@ impl DenoDiagnostic {
|
||||||
Self::NoCacheJsr(pkg_req, specifier) => (lsp::DiagnosticSeverity::ERROR, format!("Uncached or missing jsr package: {}", pkg_req), Some(json!({ "specifier": specifier }))),
|
Self::NoCacheJsr(pkg_req, specifier) => (lsp::DiagnosticSeverity::ERROR, format!("Uncached or missing jsr package: {}", pkg_req), Some(json!({ "specifier": specifier }))),
|
||||||
Self::NoCacheNpm(pkg_req, specifier) => (lsp::DiagnosticSeverity::ERROR, format!("Uncached or missing npm package: {}", pkg_req), Some(json!({ "specifier": specifier }))),
|
Self::NoCacheNpm(pkg_req, specifier) => (lsp::DiagnosticSeverity::ERROR, format!("Uncached or missing npm package: {}", pkg_req), Some(json!({ "specifier": specifier }))),
|
||||||
Self::NoLocal(specifier) => {
|
Self::NoLocal(specifier) => {
|
||||||
let sloppy_resolution = SloppyImportsResolver::new(Arc::new(deno_fs::RealFs)).resolve(specifier, ResolutionMode::Execution);
|
let maybe_sloppy_resolution = SloppyImportsResolver::new(Arc::new(deno_fs::RealFs)).resolve(specifier, ResolutionMode::Execution);
|
||||||
let data = sloppy_resolution.as_lsp_quick_fix_message().map(|message| {
|
let data = maybe_sloppy_resolution.as_ref().map(|res| {
|
||||||
json!({
|
json!({
|
||||||
"specifier": specifier,
|
"specifier": specifier,
|
||||||
"to": sloppy_resolution.as_specifier(),
|
"to": res.as_specifier(),
|
||||||
"message": message,
|
"message": res.as_quick_fix_message(),
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
(lsp::DiagnosticSeverity::ERROR, no_local_message(specifier, sloppy_resolution), data)
|
(lsp::DiagnosticSeverity::ERROR, no_local_message(specifier, maybe_sloppy_resolution.as_ref()), data)
|
||||||
},
|
},
|
||||||
Self::Redirect { from, to} => (lsp::DiagnosticSeverity::INFORMATION, format!("The import of \"{from}\" was redirected to \"{to}\"."), Some(json!({ "specifier": from, "redirect": to }))),
|
Self::Redirect { from, to} => (lsp::DiagnosticSeverity::INFORMATION, format!("The import of \"{from}\" was redirected to \"{to}\"."), Some(json!({ "specifier": from, "redirect": to }))),
|
||||||
Self::ResolutionError(err) => {
|
Self::ResolutionError(err) => {
|
||||||
|
|
|
@ -21,7 +21,6 @@ use crate::resolver::CjsResolutionStore;
|
||||||
use crate::resolver::CliGraphResolver;
|
use crate::resolver::CliGraphResolver;
|
||||||
use crate::resolver::CliGraphResolverOptions;
|
use crate::resolver::CliGraphResolverOptions;
|
||||||
use crate::resolver::CliNodeResolver;
|
use crate::resolver::CliNodeResolver;
|
||||||
use crate::resolver::SloppyImportsResolver;
|
|
||||||
use crate::resolver::WorkerCliNpmGraphResolver;
|
use crate::resolver::WorkerCliNpmGraphResolver;
|
||||||
use crate::util::progress_bar::ProgressBar;
|
use crate::util::progress_bar::ProgressBar;
|
||||||
use crate::util::progress_bar::ProgressBarStyle;
|
use crate::util::progress_bar::ProgressBarStyle;
|
||||||
|
@ -514,8 +513,6 @@ fn create_graph_resolver(
|
||||||
node_resolver: Option<&Arc<CliNodeResolver>>,
|
node_resolver: Option<&Arc<CliNodeResolver>>,
|
||||||
) -> Arc<CliGraphResolver> {
|
) -> Arc<CliGraphResolver> {
|
||||||
let workspace = config_data.map(|d| &d.member_dir.workspace);
|
let workspace = config_data.map(|d| &d.member_dir.workspace);
|
||||||
let unstable_sloppy_imports =
|
|
||||||
workspace.is_some_and(|dir| dir.has_unstable("sloppy-imports"));
|
|
||||||
Arc::new(CliGraphResolver::new(CliGraphResolverOptions {
|
Arc::new(CliGraphResolver::new(CliGraphResolverOptions {
|
||||||
node_resolver: node_resolver.cloned(),
|
node_resolver: node_resolver.cloned(),
|
||||||
npm_resolver: npm_resolver.cloned(),
|
npm_resolver: npm_resolver.cloned(),
|
||||||
|
@ -536,9 +533,8 @@ fn create_graph_resolver(
|
||||||
maybe_vendor_dir: config_data.and_then(|d| d.vendor_dir.as_ref()),
|
maybe_vendor_dir: config_data.and_then(|d| d.vendor_dir.as_ref()),
|
||||||
bare_node_builtins_enabled: workspace
|
bare_node_builtins_enabled: workspace
|
||||||
.is_some_and(|workspace| workspace.has_unstable("bare-node-builtins")),
|
.is_some_and(|workspace| workspace.has_unstable("bare-node-builtins")),
|
||||||
sloppy_imports_resolver: unstable_sloppy_imports.then(|| {
|
sloppy_imports_resolver: config_data
|
||||||
SloppyImportsResolver::new_without_stat_cache(Arc::new(deno_fs::RealFs))
|
.and_then(|d| d.sloppy_imports_resolver.clone()),
|
||||||
}),
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
244
cli/resolver.rs
244
cli/resolver.rs
|
@ -47,21 +47,11 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use crate::args::JsxImportSourceConfig;
|
use crate::args::JsxImportSourceConfig;
|
||||||
use crate::args::DENO_DISABLE_PEDANTIC_NODE_WARNINGS;
|
use crate::args::DENO_DISABLE_PEDANTIC_NODE_WARNINGS;
|
||||||
use crate::colors;
|
|
||||||
use crate::node::CliNodeCodeTranslator;
|
use crate::node::CliNodeCodeTranslator;
|
||||||
use crate::npm::CliNpmResolver;
|
use crate::npm::CliNpmResolver;
|
||||||
use crate::npm::InnerCliNpmResolverRef;
|
use crate::npm::InnerCliNpmResolverRef;
|
||||||
use crate::util::sync::AtomicFlag;
|
use crate::util::sync::AtomicFlag;
|
||||||
|
|
||||||
pub fn format_range_with_colors(range: &deno_graph::Range) -> String {
|
|
||||||
format!(
|
|
||||||
"{}:{}:{}",
|
|
||||||
colors::cyan(range.specifier.as_str()),
|
|
||||||
colors::yellow(&(range.start.line + 1).to_string()),
|
|
||||||
colors::yellow(&(range.start.character + 1).to_string())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ModuleCodeStringSource {
|
pub struct ModuleCodeStringSource {
|
||||||
pub code: ModuleSourceCode,
|
pub code: ModuleSourceCode,
|
||||||
pub found_url: ModuleSpecifier,
|
pub found_url: ModuleSpecifier,
|
||||||
|
@ -268,8 +258,8 @@ impl CliNodeResolver {
|
||||||
|
|
||||||
pub fn handle_if_in_node_modules(
|
pub fn handle_if_in_node_modules(
|
||||||
&self,
|
&self,
|
||||||
specifier: ModuleSpecifier,
|
specifier: &ModuleSpecifier,
|
||||||
) -> Result<ModuleSpecifier, AnyError> {
|
) -> Result<Option<ModuleSpecifier>, AnyError> {
|
||||||
// skip canonicalizing if we definitely know it's unnecessary
|
// skip canonicalizing if we definitely know it's unnecessary
|
||||||
if specifier.scheme() == "file"
|
if specifier.scheme() == "file"
|
||||||
&& specifier.path().contains("/node_modules/")
|
&& specifier.path().contains("/node_modules/")
|
||||||
|
@ -279,18 +269,18 @@ impl CliNodeResolver {
|
||||||
// If so, check if we need to store this specifier as being a CJS
|
// If so, check if we need to store this specifier as being a CJS
|
||||||
// resolution.
|
// resolution.
|
||||||
let specifier =
|
let specifier =
|
||||||
crate::node::resolve_specifier_into_node_modules(&specifier);
|
crate::node::resolve_specifier_into_node_modules(specifier);
|
||||||
if self.in_npm_package(&specifier) {
|
if self.in_npm_package(&specifier) {
|
||||||
let resolution =
|
let resolution =
|
||||||
self.node_resolver.url_to_node_resolution(specifier)?;
|
self.node_resolver.url_to_node_resolution(specifier)?;
|
||||||
if let NodeResolution::CommonJs(specifier) = &resolution {
|
if let NodeResolution::CommonJs(specifier) = &resolution {
|
||||||
self.cjs_resolutions.insert(specifier.clone());
|
self.cjs_resolutions.insert(specifier.clone());
|
||||||
}
|
}
|
||||||
return Ok(resolution.into_url());
|
return Ok(Some(resolution.into_url()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(specifier)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn url_to_node_resolution(
|
pub fn url_to_node_resolution(
|
||||||
|
@ -436,7 +426,7 @@ impl CjsResolutionStore {
|
||||||
pub struct CliGraphResolver {
|
pub struct CliGraphResolver {
|
||||||
node_resolver: Option<Arc<CliNodeResolver>>,
|
node_resolver: Option<Arc<CliNodeResolver>>,
|
||||||
npm_resolver: Option<Arc<dyn CliNpmResolver>>,
|
npm_resolver: Option<Arc<dyn CliNpmResolver>>,
|
||||||
sloppy_imports_resolver: Option<SloppyImportsResolver>,
|
sloppy_imports_resolver: Option<Arc<SloppyImportsResolver>>,
|
||||||
workspace_resolver: Arc<WorkspaceResolver>,
|
workspace_resolver: Arc<WorkspaceResolver>,
|
||||||
maybe_default_jsx_import_source: Option<String>,
|
maybe_default_jsx_import_source: Option<String>,
|
||||||
maybe_default_jsx_import_source_types: Option<String>,
|
maybe_default_jsx_import_source_types: Option<String>,
|
||||||
|
@ -449,7 +439,7 @@ pub struct CliGraphResolver {
|
||||||
pub struct CliGraphResolverOptions<'a> {
|
pub struct CliGraphResolverOptions<'a> {
|
||||||
pub node_resolver: Option<Arc<CliNodeResolver>>,
|
pub node_resolver: Option<Arc<CliNodeResolver>>,
|
||||||
pub npm_resolver: Option<Arc<dyn CliNpmResolver>>,
|
pub npm_resolver: Option<Arc<dyn CliNpmResolver>>,
|
||||||
pub sloppy_imports_resolver: Option<SloppyImportsResolver>,
|
pub sloppy_imports_resolver: Option<Arc<SloppyImportsResolver>>,
|
||||||
pub workspace_resolver: Arc<WorkspaceResolver>,
|
pub workspace_resolver: Arc<WorkspaceResolver>,
|
||||||
pub bare_node_builtins_enabled: bool,
|
pub bare_node_builtins_enabled: bool,
|
||||||
pub maybe_jsx_import_source_config: Option<JsxImportSourceConfig>,
|
pub maybe_jsx_import_source_config: Option<JsxImportSourceConfig>,
|
||||||
|
@ -552,12 +542,12 @@ impl Resolver for CliGraphResolver {
|
||||||
| MappedResolution::ImportMap(specifier) => {
|
| MappedResolution::ImportMap(specifier) => {
|
||||||
// do sloppy imports resolution if enabled
|
// do sloppy imports resolution if enabled
|
||||||
if let Some(sloppy_imports_resolver) = &self.sloppy_imports_resolver {
|
if let Some(sloppy_imports_resolver) = &self.sloppy_imports_resolver {
|
||||||
Ok(sloppy_imports_resolve(
|
Ok(
|
||||||
sloppy_imports_resolver,
|
sloppy_imports_resolver
|
||||||
specifier,
|
.resolve(&specifier, mode)
|
||||||
referrer_range,
|
.map(|s| s.into_specifier())
|
||||||
mode,
|
.unwrap_or(specifier),
|
||||||
))
|
)
|
||||||
} else {
|
} else {
|
||||||
Ok(specifier)
|
Ok(specifier)
|
||||||
}
|
}
|
||||||
|
@ -681,7 +671,10 @@ impl Resolver for CliGraphResolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(node_resolver.handle_if_in_node_modules(specifier)?)
|
Ok(match node_resolver.handle_if_in_node_modules(&specifier)? {
|
||||||
|
Some(specifier) => specifier,
|
||||||
|
None => specifier,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// If byonm, check if the bare specifier resolves to an npm package
|
// If byonm, check if the bare specifier resolves to an npm package
|
||||||
|
@ -700,65 +693,6 @@ impl Resolver for CliGraphResolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sloppy_imports_resolve(
|
|
||||||
resolver: &SloppyImportsResolver,
|
|
||||||
specifier: ModuleSpecifier,
|
|
||||||
referrer_range: &deno_graph::Range,
|
|
||||||
mode: ResolutionMode,
|
|
||||||
) -> ModuleSpecifier {
|
|
||||||
let resolution = resolver.resolve(&specifier, mode);
|
|
||||||
if mode.is_types() {
|
|
||||||
// don't bother warning for types resolution because
|
|
||||||
// we already probably warned during execution resolution
|
|
||||||
match resolution {
|
|
||||||
SloppyImportsResolution::None(_) => return specifier, // avoid a clone
|
|
||||||
_ => return resolution.into_specifier().into_owned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let hint_message = match &resolution {
|
|
||||||
SloppyImportsResolution::JsToTs(to_specifier) => {
|
|
||||||
let to_media_type = MediaType::from_specifier(to_specifier);
|
|
||||||
let from_media_type = MediaType::from_specifier(&specifier);
|
|
||||||
format!(
|
|
||||||
"update {} extension to {}",
|
|
||||||
from_media_type.as_ts_extension(),
|
|
||||||
to_media_type.as_ts_extension()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
SloppyImportsResolution::NoExtension(to_specifier) => {
|
|
||||||
let to_media_type = MediaType::from_specifier(to_specifier);
|
|
||||||
format!("add {} extension", to_media_type.as_ts_extension())
|
|
||||||
}
|
|
||||||
SloppyImportsResolution::Directory(to_specifier) => {
|
|
||||||
let file_name = to_specifier
|
|
||||||
.path()
|
|
||||||
.rsplit_once('/')
|
|
||||||
.map(|(_, file_name)| file_name)
|
|
||||||
.unwrap_or(to_specifier.path());
|
|
||||||
format!("specify path to {} file in directory instead", file_name)
|
|
||||||
}
|
|
||||||
SloppyImportsResolution::None(_) => return specifier,
|
|
||||||
};
|
|
||||||
// show a warning when this happens in order to drive
|
|
||||||
// the user towards correcting these specifiers
|
|
||||||
if !*DENO_DISABLE_PEDANTIC_NODE_WARNINGS {
|
|
||||||
log::warn!(
|
|
||||||
"{} Sloppy module resolution {}\n at {}",
|
|
||||||
crate::colors::yellow("Warning"),
|
|
||||||
crate::colors::gray(format!("(hint: {})", hint_message)).to_string(),
|
|
||||||
if referrer_range.end == deno_graph::Position::zeroed() {
|
|
||||||
// not worth showing the range in this case
|
|
||||||
crate::colors::cyan(referrer_range.specifier.as_str()).to_string()
|
|
||||||
} else {
|
|
||||||
format_range_with_colors(referrer_range)
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
resolution.into_specifier().into_owned()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct WorkerCliNpmGraphResolver<'a> {
|
pub struct WorkerCliNpmGraphResolver<'a> {
|
||||||
npm_resolver: Option<&'a Arc<dyn CliNpmResolver>>,
|
npm_resolver: Option<&'a Arc<dyn CliNpmResolver>>,
|
||||||
|
@ -902,10 +836,8 @@ impl SloppyImportsFsEntry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum SloppyImportsResolution<'a> {
|
pub enum SloppyImportsResolution {
|
||||||
/// No sloppy resolution was found.
|
|
||||||
None(&'a ModuleSpecifier),
|
|
||||||
/// Ex. `./file.js` to `./file.ts`
|
/// Ex. `./file.js` to `./file.ts`
|
||||||
JsToTs(ModuleSpecifier),
|
JsToTs(ModuleSpecifier),
|
||||||
/// Ex. `./file` to `./file.ts`
|
/// Ex. `./file` to `./file.ts`
|
||||||
|
@ -914,55 +846,46 @@ pub enum SloppyImportsResolution<'a> {
|
||||||
Directory(ModuleSpecifier),
|
Directory(ModuleSpecifier),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SloppyImportsResolution<'a> {
|
impl SloppyImportsResolution {
|
||||||
pub fn as_specifier(&self) -> &ModuleSpecifier {
|
pub fn as_specifier(&self) -> &ModuleSpecifier {
|
||||||
match self {
|
match self {
|
||||||
Self::None(specifier) => specifier,
|
|
||||||
Self::JsToTs(specifier) => specifier,
|
Self::JsToTs(specifier) => specifier,
|
||||||
Self::NoExtension(specifier) => specifier,
|
Self::NoExtension(specifier) => specifier,
|
||||||
Self::Directory(specifier) => specifier,
|
Self::Directory(specifier) => specifier,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_specifier(self) -> Cow<'a, ModuleSpecifier> {
|
pub fn into_specifier(self) -> ModuleSpecifier {
|
||||||
match self {
|
match self {
|
||||||
Self::None(specifier) => Cow::Borrowed(specifier),
|
Self::JsToTs(specifier) => specifier,
|
||||||
Self::JsToTs(specifier) => Cow::Owned(specifier),
|
Self::NoExtension(specifier) => specifier,
|
||||||
Self::NoExtension(specifier) => Cow::Owned(specifier),
|
Self::Directory(specifier) => specifier,
|
||||||
Self::Directory(specifier) => Cow::Owned(specifier),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_suggestion_message(&self) -> Option<String> {
|
pub fn as_suggestion_message(&self) -> String {
|
||||||
Some(format!("Maybe {}", self.as_base_message()?))
|
format!("Maybe {}", self.as_base_message())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_lsp_quick_fix_message(&self) -> Option<String> {
|
pub fn as_quick_fix_message(&self) -> String {
|
||||||
let message = self.as_base_message()?;
|
let message = self.as_base_message();
|
||||||
let mut chars = message.chars();
|
let mut chars = message.chars();
|
||||||
Some(format!(
|
format!(
|
||||||
"{}{}.",
|
"{}{}.",
|
||||||
chars.next().unwrap().to_uppercase(),
|
chars.next().unwrap().to_uppercase(),
|
||||||
chars.as_str()
|
chars.as_str()
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_base_message(&self) -> Option<String> {
|
fn as_base_message(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
SloppyImportsResolution::None(_) => None,
|
|
||||||
SloppyImportsResolution::JsToTs(specifier) => {
|
SloppyImportsResolution::JsToTs(specifier) => {
|
||||||
let media_type = MediaType::from_specifier(specifier);
|
let media_type = MediaType::from_specifier(specifier);
|
||||||
Some(format!(
|
format!("change the extension to '{}'", media_type.as_ts_extension())
|
||||||
"change the extension to '{}'",
|
|
||||||
media_type.as_ts_extension()
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
SloppyImportsResolution::NoExtension(specifier) => {
|
SloppyImportsResolution::NoExtension(specifier) => {
|
||||||
let media_type = MediaType::from_specifier(specifier);
|
let media_type = MediaType::from_specifier(specifier);
|
||||||
Some(format!(
|
format!("add a '{}' extension", media_type.as_ts_extension())
|
||||||
"add a '{}' extension",
|
|
||||||
media_type.as_ts_extension()
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
SloppyImportsResolution::Directory(specifier) => {
|
SloppyImportsResolution::Directory(specifier) => {
|
||||||
let file_name = specifier
|
let file_name = specifier
|
||||||
|
@ -970,10 +893,7 @@ impl<'a> SloppyImportsResolution<'a> {
|
||||||
.rsplit_once('/')
|
.rsplit_once('/')
|
||||||
.map(|(_, file_name)| file_name)
|
.map(|(_, file_name)| file_name)
|
||||||
.unwrap_or(specifier.path());
|
.unwrap_or(specifier.path());
|
||||||
Some(format!(
|
format!("specify path to '{}' file in directory instead", file_name)
|
||||||
"specify path to '{}' file in directory instead",
|
|
||||||
file_name
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -997,11 +917,11 @@ impl SloppyImportsResolver {
|
||||||
Self { fs, cache: None }
|
Self { fs, cache: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve<'a>(
|
pub fn resolve(
|
||||||
&self,
|
&self,
|
||||||
specifier: &'a ModuleSpecifier,
|
specifier: &ModuleSpecifier,
|
||||||
mode: ResolutionMode,
|
mode: ResolutionMode,
|
||||||
) -> SloppyImportsResolution<'a> {
|
) -> Option<SloppyImportsResolution> {
|
||||||
fn path_without_ext(
|
fn path_without_ext(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
media_type: MediaType,
|
media_type: MediaType,
|
||||||
|
@ -1017,11 +937,13 @@ impl SloppyImportsResolver {
|
||||||
|
|
||||||
fn media_types_to_paths(
|
fn media_types_to_paths(
|
||||||
path_no_ext: &str,
|
path_no_ext: &str,
|
||||||
|
original_media_type: MediaType,
|
||||||
probe_media_type_types: Vec<MediaType>,
|
probe_media_type_types: Vec<MediaType>,
|
||||||
reason: SloppyImportsResolutionReason,
|
reason: SloppyImportsResolutionReason,
|
||||||
) -> Vec<(PathBuf, SloppyImportsResolutionReason)> {
|
) -> Vec<(PathBuf, SloppyImportsResolutionReason)> {
|
||||||
probe_media_type_types
|
probe_media_type_types
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
.filter(|media_type| *media_type != original_media_type)
|
||||||
.map(|media_type| {
|
.map(|media_type| {
|
||||||
(
|
(
|
||||||
PathBuf::from(format!(
|
PathBuf::from(format!(
|
||||||
|
@ -1036,12 +958,10 @@ impl SloppyImportsResolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
if specifier.scheme() != "file" {
|
if specifier.scheme() != "file" {
|
||||||
return SloppyImportsResolution::None(specifier);
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Ok(path) = specifier_to_file_path(specifier) else {
|
let path = specifier_to_file_path(specifier).ok()?;
|
||||||
return SloppyImportsResolution::None(specifier);
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
enum SloppyImportsResolutionReason {
|
enum SloppyImportsResolutionReason {
|
||||||
|
@ -1066,18 +986,17 @@ impl SloppyImportsResolver {
|
||||||
MediaType::Cjs => {
|
MediaType::Cjs => {
|
||||||
vec![MediaType::Dcts, MediaType::Dts, MediaType::Cjs]
|
vec![MediaType::Dcts, MediaType::Dts, MediaType::Cjs]
|
||||||
}
|
}
|
||||||
_ => return SloppyImportsResolution::None(specifier),
|
_ => return None,
|
||||||
};
|
|
||||||
let Some(path_no_ext) = path_without_ext(&path, media_type) else {
|
|
||||||
return SloppyImportsResolution::None(specifier);
|
|
||||||
};
|
};
|
||||||
|
let path_no_ext = path_without_ext(&path, media_type)?;
|
||||||
media_types_to_paths(
|
media_types_to_paths(
|
||||||
&path_no_ext,
|
&path_no_ext,
|
||||||
|
media_type,
|
||||||
probe_media_type_types,
|
probe_media_type_types,
|
||||||
SloppyImportsResolutionReason::JsToTs,
|
SloppyImportsResolutionReason::JsToTs,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return SloppyImportsResolution::None(specifier);
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entry @ None | entry @ Some(SloppyImportsFsEntry::Dir) => {
|
entry @ None | entry @ Some(SloppyImportsFsEntry::Dir) => {
|
||||||
|
@ -1121,7 +1040,7 @@ impl SloppyImportsResolver {
|
||||||
| MediaType::Wasm
|
| MediaType::Wasm
|
||||||
| MediaType::TsBuildInfo
|
| MediaType::TsBuildInfo
|
||||||
| MediaType::SourceMap => {
|
| MediaType::SourceMap => {
|
||||||
return SloppyImportsResolution::None(specifier)
|
return None;
|
||||||
}
|
}
|
||||||
MediaType::Unknown => (
|
MediaType::Unknown => (
|
||||||
if mode.is_types() {
|
if mode.is_types() {
|
||||||
|
@ -1152,6 +1071,7 @@ impl SloppyImportsResolver {
|
||||||
let mut probe_paths = match path_without_ext(&path, media_type) {
|
let mut probe_paths = match path_without_ext(&path, media_type) {
|
||||||
Some(path_no_ext) => media_types_to_paths(
|
Some(path_no_ext) => media_types_to_paths(
|
||||||
&path_no_ext,
|
&path_no_ext,
|
||||||
|
media_type,
|
||||||
probe_media_type_types.0,
|
probe_media_type_types.0,
|
||||||
probe_media_type_types.1,
|
probe_media_type_types.1,
|
||||||
),
|
),
|
||||||
|
@ -1222,7 +1142,7 @@ impl SloppyImportsResolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if probe_paths.is_empty() {
|
if probe_paths.is_empty() {
|
||||||
return SloppyImportsResolution::None(specifier);
|
return None;
|
||||||
}
|
}
|
||||||
probe_paths
|
probe_paths
|
||||||
}
|
}
|
||||||
|
@ -1233,20 +1153,20 @@ impl SloppyImportsResolver {
|
||||||
if let Ok(specifier) = ModuleSpecifier::from_file_path(probe_path) {
|
if let Ok(specifier) = ModuleSpecifier::from_file_path(probe_path) {
|
||||||
match reason {
|
match reason {
|
||||||
SloppyImportsResolutionReason::JsToTs => {
|
SloppyImportsResolutionReason::JsToTs => {
|
||||||
return SloppyImportsResolution::JsToTs(specifier)
|
return Some(SloppyImportsResolution::JsToTs(specifier));
|
||||||
}
|
}
|
||||||
SloppyImportsResolutionReason::NoExtension => {
|
SloppyImportsResolutionReason::NoExtension => {
|
||||||
return SloppyImportsResolution::NoExtension(specifier)
|
return Some(SloppyImportsResolution::NoExtension(specifier));
|
||||||
}
|
}
|
||||||
SloppyImportsResolutionReason::Directory => {
|
SloppyImportsResolutionReason::Directory => {
|
||||||
return SloppyImportsResolution::Directory(specifier)
|
return Some(SloppyImportsResolution::Directory(specifier));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SloppyImportsResolution::None(specifier)
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stat_sync(&self, path: &Path) -> Option<SloppyImportsFsEntry> {
|
fn stat_sync(&self, path: &Path) -> Option<SloppyImportsFsEntry> {
|
||||||
|
@ -1276,9 +1196,22 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_unstable_sloppy_imports() {
|
fn test_unstable_sloppy_imports() {
|
||||||
fn resolve(specifier: &ModuleSpecifier) -> SloppyImportsResolution {
|
fn resolve(specifier: &ModuleSpecifier) -> Option<SloppyImportsResolution> {
|
||||||
|
resolve_with_mode(specifier, ResolutionMode::Execution)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_types(
|
||||||
|
specifier: &ModuleSpecifier,
|
||||||
|
) -> Option<SloppyImportsResolution> {
|
||||||
|
resolve_with_mode(specifier, ResolutionMode::Types)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_with_mode(
|
||||||
|
specifier: &ModuleSpecifier,
|
||||||
|
mode: ResolutionMode,
|
||||||
|
) -> Option<SloppyImportsResolution> {
|
||||||
SloppyImportsResolver::new(Arc::new(deno_fs::RealFs))
|
SloppyImportsResolver::new(Arc::new(deno_fs::RealFs))
|
||||||
.resolve(specifier, ResolutionMode::Execution)
|
.resolve(specifier, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
let context = TestContext::default();
|
let context = TestContext::default();
|
||||||
|
@ -1288,11 +1221,7 @@ mod test {
|
||||||
for (ext_from, ext_to) in [("js", "ts"), ("js", "tsx"), ("mjs", "mts")] {
|
for (ext_from, ext_to) in [("js", "ts"), ("js", "tsx"), ("mjs", "mts")] {
|
||||||
let ts_file = temp_dir.join(format!("file.{}", ext_to));
|
let ts_file = temp_dir.join(format!("file.{}", ext_to));
|
||||||
ts_file.write("");
|
ts_file.write("");
|
||||||
let ts_file_uri = ts_file.uri_file();
|
assert_eq!(resolve(&ts_file.uri_file()), None);
|
||||||
assert_eq!(
|
|
||||||
resolve(&ts_file.uri_file()),
|
|
||||||
SloppyImportsResolution::None(&ts_file_uri),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resolve(
|
resolve(
|
||||||
&temp_dir
|
&temp_dir
|
||||||
|
@ -1300,7 +1229,7 @@ mod test {
|
||||||
.join(&format!("file.{}", ext_from))
|
.join(&format!("file.{}", ext_from))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
),
|
),
|
||||||
SloppyImportsResolution::JsToTs(ts_file.uri_file()),
|
Some(SloppyImportsResolution::JsToTs(ts_file.uri_file())),
|
||||||
);
|
);
|
||||||
ts_file.remove_file();
|
ts_file.remove_file();
|
||||||
}
|
}
|
||||||
|
@ -1316,7 +1245,7 @@ mod test {
|
||||||
.join("file") // no ext
|
.join("file") // no ext
|
||||||
.unwrap()
|
.unwrap()
|
||||||
),
|
),
|
||||||
SloppyImportsResolution::NoExtension(file.uri_file()),
|
Some(SloppyImportsResolution::NoExtension(file.uri_file()))
|
||||||
);
|
);
|
||||||
file.remove_file();
|
file.remove_file();
|
||||||
}
|
}
|
||||||
|
@ -1327,11 +1256,15 @@ mod test {
|
||||||
ts_file.write("");
|
ts_file.write("");
|
||||||
let js_file = temp_dir.join("file.js");
|
let js_file = temp_dir.join("file.js");
|
||||||
js_file.write("");
|
js_file.write("");
|
||||||
let js_file_uri = js_file.uri_file();
|
assert_eq!(resolve(&js_file.uri_file()), None);
|
||||||
assert_eq!(
|
}
|
||||||
resolve(&js_file.uri_file()),
|
|
||||||
SloppyImportsResolution::None(&js_file_uri),
|
// only js exists, .js specified
|
||||||
);
|
{
|
||||||
|
let js_only_file = temp_dir.join("js_only.js");
|
||||||
|
js_only_file.write("");
|
||||||
|
assert_eq!(resolve(&js_only_file.uri_file()), None);
|
||||||
|
assert_eq!(resolve_types(&js_only_file.uri_file()), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolving a directory to an index file
|
// resolving a directory to an index file
|
||||||
|
@ -1342,7 +1275,7 @@ mod test {
|
||||||
index_file.write("");
|
index_file.write("");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resolve(&routes_dir.uri_file()),
|
resolve(&routes_dir.uri_file()),
|
||||||
SloppyImportsResolution::Directory(index_file.uri_file()),
|
Some(SloppyImportsResolution::Directory(index_file.uri_file())),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1356,26 +1289,19 @@ mod test {
|
||||||
api_file.write("");
|
api_file.write("");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resolve(&api_dir.uri_file()),
|
resolve(&api_dir.uri_file()),
|
||||||
SloppyImportsResolution::NoExtension(api_file.uri_file()),
|
Some(SloppyImportsResolution::NoExtension(api_file.uri_file())),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sloppy_import_resolution_suggestion_message() {
|
fn test_sloppy_import_resolution_suggestion_message() {
|
||||||
// none
|
|
||||||
let url = ModuleSpecifier::parse("file:///dir/index.js").unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
SloppyImportsResolution::None(&url).as_suggestion_message(),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
// directory
|
// directory
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
SloppyImportsResolution::Directory(
|
SloppyImportsResolution::Directory(
|
||||||
ModuleSpecifier::parse("file:///dir/index.js").unwrap()
|
ModuleSpecifier::parse("file:///dir/index.js").unwrap()
|
||||||
)
|
)
|
||||||
.as_suggestion_message()
|
.as_suggestion_message(),
|
||||||
.unwrap(),
|
|
||||||
"Maybe specify path to 'index.js' file in directory instead"
|
"Maybe specify path to 'index.js' file in directory instead"
|
||||||
);
|
);
|
||||||
// no ext
|
// no ext
|
||||||
|
@ -1383,8 +1309,7 @@ mod test {
|
||||||
SloppyImportsResolution::NoExtension(
|
SloppyImportsResolution::NoExtension(
|
||||||
ModuleSpecifier::parse("file:///dir/index.mjs").unwrap()
|
ModuleSpecifier::parse("file:///dir/index.mjs").unwrap()
|
||||||
)
|
)
|
||||||
.as_suggestion_message()
|
.as_suggestion_message(),
|
||||||
.unwrap(),
|
|
||||||
"Maybe add a '.mjs' extension"
|
"Maybe add a '.mjs' extension"
|
||||||
);
|
);
|
||||||
// js to ts
|
// js to ts
|
||||||
|
@ -1392,8 +1317,7 @@ mod test {
|
||||||
SloppyImportsResolution::JsToTs(
|
SloppyImportsResolution::JsToTs(
|
||||||
ModuleSpecifier::parse("file:///dir/index.mts").unwrap()
|
ModuleSpecifier::parse("file:///dir/index.mts").unwrap()
|
||||||
)
|
)
|
||||||
.as_suggestion_message()
|
.as_suggestion_message(),
|
||||||
.unwrap(),
|
|
||||||
"Maybe change the extension to '.mts'"
|
"Maybe change the extension to '.mts'"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -241,10 +241,13 @@ impl ModuleLoader for EmbeddedModuleLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
Ok(
|
||||||
.shared
|
self
|
||||||
.node_resolver
|
.shared
|
||||||
.handle_if_in_node_modules(specifier)
|
.node_resolver
|
||||||
|
.handle_if_in_node_modules(&specifier)?
|
||||||
|
.unwrap_or(specifier),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Err(err)
|
Err(err)
|
||||||
if err.is_unmapped_bare_specifier() && referrer.scheme() == "file" =>
|
if err.is_unmapped_bare_specifier() && referrer.scheme() == "file" =>
|
||||||
|
|
242
cli/tools/lint/linter.rs
Normal file
242
cli/tools/lint/linter.rs
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use deno_ast::MediaType;
|
||||||
|
use deno_ast::ModuleSpecifier;
|
||||||
|
use deno_ast::ParsedSource;
|
||||||
|
use deno_ast::SourceTextInfo;
|
||||||
|
use deno_core::anyhow::Context;
|
||||||
|
use deno_core::error::AnyError;
|
||||||
|
use deno_graph::ModuleGraph;
|
||||||
|
use deno_lint::diagnostic::LintDiagnostic;
|
||||||
|
use deno_lint::linter::LintConfig as DenoLintConfig;
|
||||||
|
use deno_lint::linter::LintFileOptions;
|
||||||
|
use deno_lint::linter::Linter as DenoLintLinter;
|
||||||
|
use deno_lint::linter::LinterOptions;
|
||||||
|
|
||||||
|
use crate::util::fs::atomic_write_file_with_retries;
|
||||||
|
use crate::util::fs::specifier_from_file_path;
|
||||||
|
|
||||||
|
use super::rules::FileOrPackageLintRule;
|
||||||
|
use super::rules::PackageLintRule;
|
||||||
|
use super::ConfiguredRules;
|
||||||
|
|
||||||
|
pub struct CliLinterOptions {
|
||||||
|
pub configured_rules: ConfiguredRules,
|
||||||
|
pub fix: bool,
|
||||||
|
pub deno_lint_config: DenoLintConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CliLinter {
|
||||||
|
fix: bool,
|
||||||
|
package_rules: Vec<Box<dyn PackageLintRule>>,
|
||||||
|
linter: DenoLintLinter,
|
||||||
|
deno_lint_config: DenoLintConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CliLinter {
|
||||||
|
pub fn new(options: CliLinterOptions) -> Self {
|
||||||
|
let rules = options.configured_rules.rules;
|
||||||
|
let mut deno_lint_rules = Vec::with_capacity(rules.len());
|
||||||
|
let mut package_rules = Vec::with_capacity(rules.len());
|
||||||
|
for rule in rules {
|
||||||
|
match rule.into_file_or_pkg_rule() {
|
||||||
|
FileOrPackageLintRule::File(rule) => {
|
||||||
|
deno_lint_rules.push(rule);
|
||||||
|
}
|
||||||
|
FileOrPackageLintRule::Package(rule) => {
|
||||||
|
package_rules.push(rule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
fix: options.fix,
|
||||||
|
package_rules,
|
||||||
|
linter: DenoLintLinter::new(LinterOptions {
|
||||||
|
rules: deno_lint_rules,
|
||||||
|
all_rule_codes: options.configured_rules.all_rule_codes,
|
||||||
|
custom_ignore_file_directive: None,
|
||||||
|
custom_ignore_diagnostic_directive: None,
|
||||||
|
}),
|
||||||
|
deno_lint_config: options.deno_lint_config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_package_rules(&self) -> bool {
|
||||||
|
!self.package_rules.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lint_package(
|
||||||
|
&self,
|
||||||
|
graph: &ModuleGraph,
|
||||||
|
entrypoints: &[ModuleSpecifier],
|
||||||
|
) -> Vec<LintDiagnostic> {
|
||||||
|
let mut diagnostics = Vec::new();
|
||||||
|
for rule in &self.package_rules {
|
||||||
|
diagnostics.extend(rule.lint_package(graph, entrypoints));
|
||||||
|
}
|
||||||
|
diagnostics
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lint_with_ast(
|
||||||
|
&self,
|
||||||
|
parsed_source: &ParsedSource,
|
||||||
|
) -> Vec<LintDiagnostic> {
|
||||||
|
self
|
||||||
|
.linter
|
||||||
|
.lint_with_ast(parsed_source, self.deno_lint_config.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lint_file(
|
||||||
|
&self,
|
||||||
|
file_path: &Path,
|
||||||
|
source_code: String,
|
||||||
|
) -> Result<(ParsedSource, Vec<LintDiagnostic>), AnyError> {
|
||||||
|
let specifier = specifier_from_file_path(file_path)?;
|
||||||
|
let media_type = MediaType::from_specifier(&specifier);
|
||||||
|
|
||||||
|
if self.fix {
|
||||||
|
self.lint_file_and_fix(&specifier, media_type, source_code, file_path)
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
.linter
|
||||||
|
.lint_file(LintFileOptions {
|
||||||
|
specifier,
|
||||||
|
media_type,
|
||||||
|
source_code,
|
||||||
|
config: self.deno_lint_config.clone(),
|
||||||
|
})
|
||||||
|
.map_err(AnyError::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lint_file_and_fix(
|
||||||
|
&self,
|
||||||
|
specifier: &ModuleSpecifier,
|
||||||
|
media_type: MediaType,
|
||||||
|
source_code: String,
|
||||||
|
file_path: &Path,
|
||||||
|
) -> Result<(ParsedSource, Vec<LintDiagnostic>), deno_core::anyhow::Error> {
|
||||||
|
// initial lint
|
||||||
|
let (source, diagnostics) = self.linter.lint_file(LintFileOptions {
|
||||||
|
specifier: specifier.clone(),
|
||||||
|
media_type,
|
||||||
|
source_code,
|
||||||
|
config: self.deno_lint_config.clone(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Try applying fixes repeatedly until the file has none left or
|
||||||
|
// a maximum number of iterations is reached. This is necessary
|
||||||
|
// because lint fixes may overlap and so we can't always apply
|
||||||
|
// them in one pass.
|
||||||
|
let mut source = source;
|
||||||
|
let mut diagnostics = diagnostics;
|
||||||
|
let mut fix_iterations = 0;
|
||||||
|
loop {
|
||||||
|
let change = apply_lint_fixes_and_relint(
|
||||||
|
specifier,
|
||||||
|
media_type,
|
||||||
|
&self.linter,
|
||||||
|
self.deno_lint_config.clone(),
|
||||||
|
source.text_info_lazy(),
|
||||||
|
&diagnostics,
|
||||||
|
)?;
|
||||||
|
match change {
|
||||||
|
Some(change) => {
|
||||||
|
source = change.0;
|
||||||
|
diagnostics = change.1;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fix_iterations += 1;
|
||||||
|
if fix_iterations > 5 {
|
||||||
|
log::warn!(
|
||||||
|
concat!(
|
||||||
|
"Reached maximum number of fix iterations for '{}'. There's ",
|
||||||
|
"probably a bug in Deno. Please fix this file manually.",
|
||||||
|
),
|
||||||
|
specifier,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fix_iterations > 0 {
|
||||||
|
// everything looks good and the file still parses, so write it out
|
||||||
|
atomic_write_file_with_retries(
|
||||||
|
file_path,
|
||||||
|
source.text().as_ref(),
|
||||||
|
crate::cache::CACHE_PERM,
|
||||||
|
)
|
||||||
|
.context("Failed writing fix to file.")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((source, diagnostics))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_lint_fixes_and_relint(
|
||||||
|
specifier: &ModuleSpecifier,
|
||||||
|
media_type: MediaType,
|
||||||
|
linter: &DenoLintLinter,
|
||||||
|
config: DenoLintConfig,
|
||||||
|
text_info: &SourceTextInfo,
|
||||||
|
diagnostics: &[LintDiagnostic],
|
||||||
|
) -> Result<Option<(ParsedSource, Vec<LintDiagnostic>)>, AnyError> {
|
||||||
|
let Some(new_text) = apply_lint_fixes(text_info, diagnostics) else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
linter
|
||||||
|
.lint_file(LintFileOptions {
|
||||||
|
specifier: specifier.clone(),
|
||||||
|
source_code: new_text,
|
||||||
|
media_type,
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
.map(Some)
|
||||||
|
.context(
|
||||||
|
"An applied lint fix caused a syntax error. Please report this bug.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_lint_fixes(
|
||||||
|
text_info: &SourceTextInfo,
|
||||||
|
diagnostics: &[LintDiagnostic],
|
||||||
|
) -> Option<String> {
|
||||||
|
if diagnostics.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_start = text_info.range().start;
|
||||||
|
let mut quick_fixes = diagnostics
|
||||||
|
.iter()
|
||||||
|
// use the first quick fix
|
||||||
|
.filter_map(|d| d.details.fixes.first())
|
||||||
|
.flat_map(|fix| fix.changes.iter())
|
||||||
|
.map(|change| deno_ast::TextChange {
|
||||||
|
range: change.range.as_byte_range(file_start),
|
||||||
|
new_text: change.new_text.to_string(),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if quick_fixes.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// remove any overlapping text changes, we'll circle
|
||||||
|
// back for another pass to fix the remaining
|
||||||
|
quick_fixes.sort_by_key(|change| change.range.start);
|
||||||
|
for i in (1..quick_fixes.len()).rev() {
|
||||||
|
let cur = &quick_fixes[i];
|
||||||
|
let previous = &quick_fixes[i - 1];
|
||||||
|
let is_overlapping = cur.range.start < previous.range.end;
|
||||||
|
if is_overlapping {
|
||||||
|
quick_fixes.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let new_text =
|
||||||
|
deno_ast::apply_text_changes(text_info.text_str(), quick_fixes);
|
||||||
|
Some(new_text)
|
||||||
|
}
|
|
@ -3,18 +3,13 @@
|
||||||
//! This module provides file linting utilities using
|
//! This module provides file linting utilities using
|
||||||
//! [`deno_lint`](https://github.com/denoland/deno_lint).
|
//! [`deno_lint`](https://github.com/denoland/deno_lint).
|
||||||
use deno_ast::diagnostics::Diagnostic;
|
use deno_ast::diagnostics::Diagnostic;
|
||||||
use deno_ast::MediaType;
|
|
||||||
use deno_ast::ModuleSpecifier;
|
use deno_ast::ModuleSpecifier;
|
||||||
use deno_ast::ParsedSource;
|
use deno_ast::ParsedSource;
|
||||||
use deno_ast::SourceRange;
|
use deno_config::deno_json::LintRulesConfig;
|
||||||
use deno_ast::SourceTextInfo;
|
|
||||||
use deno_config::deno_json::ConfigFile;
|
|
||||||
use deno_config::glob::FileCollector;
|
use deno_config::glob::FileCollector;
|
||||||
use deno_config::glob::FilePatterns;
|
use deno_config::glob::FilePatterns;
|
||||||
use deno_config::workspace::WorkspaceDirectory;
|
use deno_config::workspace::WorkspaceDirectory;
|
||||||
use deno_core::anyhow::anyhow;
|
use deno_core::anyhow::anyhow;
|
||||||
use deno_core::anyhow::bail;
|
|
||||||
use deno_core::anyhow::Context;
|
|
||||||
use deno_core::error::generic_error;
|
use deno_core::error::generic_error;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_core::futures::future::LocalBoxFuture;
|
use deno_core::futures::future::LocalBoxFuture;
|
||||||
|
@ -23,19 +18,12 @@ use deno_core::parking_lot::Mutex;
|
||||||
use deno_core::serde_json;
|
use deno_core::serde_json;
|
||||||
use deno_core::unsync::future::LocalFutureExt;
|
use deno_core::unsync::future::LocalFutureExt;
|
||||||
use deno_core::unsync::future::SharedLocal;
|
use deno_core::unsync::future::SharedLocal;
|
||||||
use deno_graph::FastCheckDiagnostic;
|
|
||||||
use deno_graph::ModuleGraph;
|
use deno_graph::ModuleGraph;
|
||||||
use deno_lint::diagnostic::LintDiagnostic;
|
use deno_lint::diagnostic::LintDiagnostic;
|
||||||
use deno_lint::linter::LintConfig;
|
use deno_lint::linter::LintConfig;
|
||||||
use deno_lint::linter::LintFileOptions;
|
|
||||||
use deno_lint::linter::Linter;
|
|
||||||
use deno_lint::linter::LinterBuilder;
|
|
||||||
use deno_lint::rules;
|
|
||||||
use deno_lint::rules::LintRule;
|
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use log::info;
|
use log::info;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::stdin;
|
use std::io::stdin;
|
||||||
|
@ -50,7 +38,6 @@ use crate::args::Flags;
|
||||||
use crate::args::LintFlags;
|
use crate::args::LintFlags;
|
||||||
use crate::args::LintOptions;
|
use crate::args::LintOptions;
|
||||||
use crate::args::LintReporterKind;
|
use crate::args::LintReporterKind;
|
||||||
use crate::args::LintRulesConfig;
|
|
||||||
use crate::args::WorkspaceLintOptions;
|
use crate::args::WorkspaceLintOptions;
|
||||||
use crate::cache::Caches;
|
use crate::cache::Caches;
|
||||||
use crate::cache::IncrementalCache;
|
use crate::cache::IncrementalCache;
|
||||||
|
@ -60,11 +47,17 @@ use crate::graph_util::ModuleGraphCreator;
|
||||||
use crate::tools::fmt::run_parallelized;
|
use crate::tools::fmt::run_parallelized;
|
||||||
use crate::util::file_watcher;
|
use crate::util::file_watcher;
|
||||||
use crate::util::fs::canonicalize_path;
|
use crate::util::fs::canonicalize_path;
|
||||||
use crate::util::fs::specifier_from_file_path;
|
|
||||||
use crate::util::path::is_script_ext;
|
use crate::util::path::is_script_ext;
|
||||||
use crate::util::sync::AtomicFlag;
|
use crate::util::sync::AtomicFlag;
|
||||||
|
|
||||||
pub mod no_slow_types;
|
mod linter;
|
||||||
|
mod rules;
|
||||||
|
|
||||||
|
pub use linter::CliLinter;
|
||||||
|
pub use linter::CliLinterOptions;
|
||||||
|
pub use rules::collect_no_slow_type_diagnostics;
|
||||||
|
pub use rules::ConfiguredRules;
|
||||||
|
pub use rules::LintRuleProvider;
|
||||||
|
|
||||||
static STDIN_FILE_NAME: &str = "$deno$stdin.ts";
|
static STDIN_FILE_NAME: &str = "$deno$stdin.ts";
|
||||||
|
|
||||||
|
@ -120,6 +113,7 @@ pub async fn lint(
|
||||||
|
|
||||||
let mut linter = WorkspaceLinter::new(
|
let mut linter = WorkspaceLinter::new(
|
||||||
factory.caches()?.clone(),
|
factory.caches()?.clone(),
|
||||||
|
factory.lint_rule_provider().await?,
|
||||||
factory.module_graph_creator().await?.clone(),
|
factory.module_graph_creator().await?.clone(),
|
||||||
cli_options.start_dir.clone(),
|
cli_options.start_dir.clone(),
|
||||||
&cli_options.resolve_workspace_lint_options(&lint_flags)?,
|
&cli_options.resolve_workspace_lint_options(&lint_flags)?,
|
||||||
|
@ -157,12 +151,15 @@ pub async fn lint(
|
||||||
let lint_config = start_dir
|
let lint_config = start_dir
|
||||||
.to_lint_config(FilePatterns::new_with_base(start_dir.dir_path()))?;
|
.to_lint_config(FilePatterns::new_with_base(start_dir.dir_path()))?;
|
||||||
let lint_options = LintOptions::resolve(lint_config, &lint_flags);
|
let lint_options = LintOptions::resolve(lint_config, &lint_flags);
|
||||||
let lint_rules = get_config_rules_err_empty(
|
let lint_rules = factory
|
||||||
lint_options.rules,
|
.lint_rule_provider()
|
||||||
start_dir.maybe_deno_json().map(|c| c.as_ref()),
|
.await?
|
||||||
)?;
|
.resolve_lint_rules_err_empty(
|
||||||
|
lint_options.rules,
|
||||||
|
start_dir.maybe_deno_json().map(|c| c.as_ref()),
|
||||||
|
)?;
|
||||||
let file_path = cli_options.initial_cwd().join(STDIN_FILE_NAME);
|
let file_path = cli_options.initial_cwd().join(STDIN_FILE_NAME);
|
||||||
let r = lint_stdin(&file_path, lint_rules.rules, deno_lint_config);
|
let r = lint_stdin(&file_path, lint_rules, deno_lint_config);
|
||||||
let success = handle_lint_result(
|
let success = handle_lint_result(
|
||||||
&file_path.to_string_lossy(),
|
&file_path.to_string_lossy(),
|
||||||
r,
|
r,
|
||||||
|
@ -173,6 +170,7 @@ pub async fn lint(
|
||||||
} else {
|
} else {
|
||||||
let mut linter = WorkspaceLinter::new(
|
let mut linter = WorkspaceLinter::new(
|
||||||
factory.caches()?.clone(),
|
factory.caches()?.clone(),
|
||||||
|
factory.lint_rule_provider().await?,
|
||||||
factory.module_graph_creator().await?.clone(),
|
factory.module_graph_creator().await?.clone(),
|
||||||
cli_options.start_dir.clone(),
|
cli_options.start_dir.clone(),
|
||||||
&workspace_lint_options,
|
&workspace_lint_options,
|
||||||
|
@ -234,6 +232,7 @@ type WorkspaceModuleGraphFuture =
|
||||||
|
|
||||||
struct WorkspaceLinter {
|
struct WorkspaceLinter {
|
||||||
caches: Arc<Caches>,
|
caches: Arc<Caches>,
|
||||||
|
lint_rule_provider: LintRuleProvider,
|
||||||
module_graph_creator: Arc<ModuleGraphCreator>,
|
module_graph_creator: Arc<ModuleGraphCreator>,
|
||||||
workspace_dir: Arc<WorkspaceDirectory>,
|
workspace_dir: Arc<WorkspaceDirectory>,
|
||||||
reporter_lock: Arc<Mutex<Box<dyn LintReporter + Send>>>,
|
reporter_lock: Arc<Mutex<Box<dyn LintReporter + Send>>>,
|
||||||
|
@ -245,6 +244,7 @@ struct WorkspaceLinter {
|
||||||
impl WorkspaceLinter {
|
impl WorkspaceLinter {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
caches: Arc<Caches>,
|
caches: Arc<Caches>,
|
||||||
|
lint_rule_provider: LintRuleProvider,
|
||||||
module_graph_creator: Arc<ModuleGraphCreator>,
|
module_graph_creator: Arc<ModuleGraphCreator>,
|
||||||
workspace_dir: Arc<WorkspaceDirectory>,
|
workspace_dir: Arc<WorkspaceDirectory>,
|
||||||
workspace_options: &WorkspaceLintOptions,
|
workspace_options: &WorkspaceLintOptions,
|
||||||
|
@ -253,6 +253,7 @@ impl WorkspaceLinter {
|
||||||
Arc::new(Mutex::new(create_reporter(workspace_options.reporter_kind)));
|
Arc::new(Mutex::new(create_reporter(workspace_options.reporter_kind)));
|
||||||
Self {
|
Self {
|
||||||
caches,
|
caches,
|
||||||
|
lint_rule_provider,
|
||||||
module_graph_creator,
|
module_graph_creator,
|
||||||
workspace_dir,
|
workspace_dir,
|
||||||
reporter_lock,
|
reporter_lock,
|
||||||
|
@ -271,18 +272,27 @@ impl WorkspaceLinter {
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
self.file_count += paths.len();
|
self.file_count += paths.len();
|
||||||
|
|
||||||
let lint_rules = get_config_rules_err_empty(
|
let lint_rules = self.lint_rule_provider.resolve_lint_rules_err_empty(
|
||||||
lint_options.rules,
|
lint_options.rules,
|
||||||
member_dir.maybe_deno_json().map(|c| c.as_ref()),
|
member_dir.maybe_deno_json().map(|c| c.as_ref()),
|
||||||
)?;
|
)?;
|
||||||
let incremental_cache = Arc::new(IncrementalCache::new(
|
let maybe_incremental_cache =
|
||||||
self.caches.lint_incremental_cache_db(),
|
lint_rules.incremental_cache_state().map(|state| {
|
||||||
&lint_rules.incremental_cache_state(),
|
Arc::new(IncrementalCache::new(
|
||||||
&paths,
|
self.caches.lint_incremental_cache_db(),
|
||||||
));
|
&state,
|
||||||
|
&paths,
|
||||||
|
))
|
||||||
|
});
|
||||||
|
|
||||||
|
let linter = Arc::new(CliLinter::new(CliLinterOptions {
|
||||||
|
configured_rules: lint_rules,
|
||||||
|
fix: lint_options.fix,
|
||||||
|
deno_lint_config: lint_config,
|
||||||
|
}));
|
||||||
|
|
||||||
let mut futures = Vec::with_capacity(2);
|
let mut futures = Vec::with_capacity(2);
|
||||||
if lint_rules.no_slow_types {
|
if linter.has_package_rules() {
|
||||||
if self.workspace_module_graph.is_none() {
|
if self.workspace_module_graph.is_none() {
|
||||||
let module_graph_creator = self.module_graph_creator.clone();
|
let module_graph_creator = self.module_graph_creator.clone();
|
||||||
let packages = self.workspace_dir.jsr_packages_for_publish();
|
let packages = self.workspace_dir.jsr_packages_for_publish();
|
||||||
|
@ -304,6 +314,7 @@ impl WorkspaceLinter {
|
||||||
if let Some(publish_config) = publish_config {
|
if let Some(publish_config) = publish_config {
|
||||||
let has_error = self.has_error.clone();
|
let has_error = self.has_error.clone();
|
||||||
let reporter_lock = self.reporter_lock.clone();
|
let reporter_lock = self.reporter_lock.clone();
|
||||||
|
let linter = linter.clone();
|
||||||
let path_urls = paths
|
let path_urls = paths
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|p| ModuleSpecifier::from_file_path(p).ok())
|
.filter_map(|p| ModuleSpecifier::from_file_path(p).ok())
|
||||||
|
@ -318,16 +329,12 @@ impl WorkspaceLinter {
|
||||||
if !export_urls.iter().any(|url| path_urls.contains(url)) {
|
if !export_urls.iter().any(|url| path_urls.contains(url)) {
|
||||||
return Ok(()); // entrypoint is not specified, so skip
|
return Ok(()); // entrypoint is not specified, so skip
|
||||||
}
|
}
|
||||||
let diagnostics = no_slow_types::collect_no_slow_type_diagnostics(
|
let diagnostics = linter.lint_package(&graph, &export_urls);
|
||||||
&export_urls,
|
|
||||||
&graph,
|
|
||||||
);
|
|
||||||
if !diagnostics.is_empty() {
|
if !diagnostics.is_empty() {
|
||||||
has_error.raise();
|
has_error.raise();
|
||||||
let mut reporter = reporter_lock.lock();
|
let mut reporter = reporter_lock.lock();
|
||||||
for diagnostic in &diagnostics {
|
for diagnostic in &diagnostics {
|
||||||
reporter
|
reporter.visit_diagnostic(diagnostic);
|
||||||
.visit_diagnostic(LintOrCliDiagnostic::FastCheck(diagnostic));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -339,11 +346,9 @@ impl WorkspaceLinter {
|
||||||
|
|
||||||
futures.push({
|
futures.push({
|
||||||
let has_error = self.has_error.clone();
|
let has_error = self.has_error.clone();
|
||||||
let linter = create_linter(lint_rules.rules);
|
|
||||||
let reporter_lock = self.reporter_lock.clone();
|
let reporter_lock = self.reporter_lock.clone();
|
||||||
let incremental_cache = incremental_cache.clone();
|
let maybe_incremental_cache = maybe_incremental_cache.clone();
|
||||||
let lint_config = lint_config.clone();
|
let linter = linter.clone();
|
||||||
let fix = lint_options.fix;
|
|
||||||
async move {
|
async move {
|
||||||
run_parallelized(paths, {
|
run_parallelized(paths, {
|
||||||
move |file_path| {
|
move |file_path| {
|
||||||
|
@ -351,19 +356,23 @@ impl WorkspaceLinter {
|
||||||
deno_ast::strip_bom(fs::read_to_string(&file_path)?);
|
deno_ast::strip_bom(fs::read_to_string(&file_path)?);
|
||||||
|
|
||||||
// don't bother rechecking this file if it didn't have any diagnostics before
|
// don't bother rechecking this file if it didn't have any diagnostics before
|
||||||
if incremental_cache.is_file_same(&file_path, &file_text) {
|
if let Some(incremental_cache) = &maybe_incremental_cache {
|
||||||
return Ok(());
|
if incremental_cache.is_file_same(&file_path, &file_text) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let r = lint_file(&linter, &file_path, file_text, lint_config, fix);
|
let r = linter.lint_file(&file_path, file_text);
|
||||||
if let Ok((file_source, file_diagnostics)) = &r {
|
if let Ok((file_source, file_diagnostics)) = &r {
|
||||||
if file_diagnostics.is_empty() {
|
if let Some(incremental_cache) = &maybe_incremental_cache {
|
||||||
// update the incremental cache if there were no diagnostics
|
if file_diagnostics.is_empty() {
|
||||||
incremental_cache.update_file(
|
// update the incremental cache if there were no diagnostics
|
||||||
&file_path,
|
incremental_cache.update_file(
|
||||||
// ensure the returned text is used here as it may have been modified via --fix
|
&file_path,
|
||||||
file_source.text(),
|
// ensure the returned text is used here as it may have been modified via --fix
|
||||||
)
|
file_source.text(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,9 +393,21 @@ impl WorkspaceLinter {
|
||||||
.boxed_local()
|
.boxed_local()
|
||||||
});
|
});
|
||||||
|
|
||||||
deno_core::futures::future::try_join_all(futures).await?;
|
if lint_options.fix {
|
||||||
|
// run sequentially when using `--fix` to lower the chances of weird
|
||||||
|
// bugs where a file level fix affects a package level diagnostic though
|
||||||
|
// it probably will happen anyway
|
||||||
|
for future in futures {
|
||||||
|
future.await?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deno_core::futures::future::try_join_all(futures).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(incremental_cache) = &maybe_incremental_cache {
|
||||||
|
incremental_cache.wait_completion().await;
|
||||||
|
}
|
||||||
|
|
||||||
incremental_cache.wait_completion().await;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -410,11 +431,17 @@ fn collect_lint_files(
|
||||||
|
|
||||||
#[allow(clippy::print_stdout)]
|
#[allow(clippy::print_stdout)]
|
||||||
pub fn print_rules_list(json: bool, maybe_rules_tags: Option<Vec<String>>) {
|
pub fn print_rules_list(json: bool, maybe_rules_tags: Option<Vec<String>>) {
|
||||||
let lint_rules = if maybe_rules_tags.is_none() {
|
let rule_provider = LintRuleProvider::new(None, None);
|
||||||
rules::get_all_rules()
|
let lint_rules = rule_provider
|
||||||
} else {
|
.resolve_lint_rules(
|
||||||
rules::get_filtered_rules(maybe_rules_tags, None, None)
|
LintRulesConfig {
|
||||||
};
|
tags: maybe_rules_tags.clone(),
|
||||||
|
include: None,
|
||||||
|
exclude: None,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.rules;
|
||||||
|
|
||||||
if json {
|
if json {
|
||||||
let json_rules: Vec<serde_json::Value> = lint_rules
|
let json_rules: Vec<serde_json::Value> = lint_rules
|
||||||
|
@ -442,186 +469,19 @@ pub fn print_rules_list(json: bool, maybe_rules_tags: Option<Vec<String>>) {
|
||||||
}
|
}
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
colors::gray(format!(
|
colors::gray(format!(" help: {}", rule.help_docs_url()))
|
||||||
" help: https://lint.deno.land/#{}",
|
|
||||||
rule.code()
|
|
||||||
))
|
|
||||||
);
|
);
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_linter(rules: Vec<&'static dyn LintRule>) -> Linter {
|
|
||||||
LinterBuilder::default()
|
|
||||||
.ignore_file_directive("deno-lint-ignore-file")
|
|
||||||
.ignore_diagnostic_directive("deno-lint-ignore")
|
|
||||||
.rules(rules)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lint_file(
|
|
||||||
linter: &Linter,
|
|
||||||
file_path: &Path,
|
|
||||||
source_code: String,
|
|
||||||
config: LintConfig,
|
|
||||||
fix: bool,
|
|
||||||
) -> Result<(ParsedSource, Vec<LintDiagnostic>), AnyError> {
|
|
||||||
let specifier = specifier_from_file_path(file_path)?;
|
|
||||||
let media_type = MediaType::from_specifier(&specifier);
|
|
||||||
|
|
||||||
if fix {
|
|
||||||
lint_file_and_fix(
|
|
||||||
linter,
|
|
||||||
&specifier,
|
|
||||||
media_type,
|
|
||||||
source_code,
|
|
||||||
file_path,
|
|
||||||
config,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
linter
|
|
||||||
.lint_file(LintFileOptions {
|
|
||||||
specifier,
|
|
||||||
media_type,
|
|
||||||
source_code,
|
|
||||||
config,
|
|
||||||
})
|
|
||||||
.map_err(AnyError::from)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lint_file_and_fix(
|
|
||||||
linter: &Linter,
|
|
||||||
specifier: &ModuleSpecifier,
|
|
||||||
media_type: MediaType,
|
|
||||||
source_code: String,
|
|
||||||
file_path: &Path,
|
|
||||||
config: LintConfig,
|
|
||||||
) -> Result<(ParsedSource, Vec<LintDiagnostic>), deno_core::anyhow::Error> {
|
|
||||||
// initial lint
|
|
||||||
let (source, diagnostics) = linter.lint_file(LintFileOptions {
|
|
||||||
specifier: specifier.clone(),
|
|
||||||
media_type,
|
|
||||||
source_code,
|
|
||||||
config: config.clone(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Try applying fixes repeatedly until the file has none left or
|
|
||||||
// a maximum number of iterations is reached. This is necessary
|
|
||||||
// because lint fixes may overlap and so we can't always apply
|
|
||||||
// them in one pass.
|
|
||||||
let mut source = source;
|
|
||||||
let mut diagnostics = diagnostics;
|
|
||||||
let mut fix_iterations = 0;
|
|
||||||
loop {
|
|
||||||
let change = apply_lint_fixes_and_relint(
|
|
||||||
specifier,
|
|
||||||
media_type,
|
|
||||||
linter,
|
|
||||||
config.clone(),
|
|
||||||
source.text_info_lazy(),
|
|
||||||
&diagnostics,
|
|
||||||
)?;
|
|
||||||
match change {
|
|
||||||
Some(change) => {
|
|
||||||
source = change.0;
|
|
||||||
diagnostics = change.1;
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fix_iterations += 1;
|
|
||||||
if fix_iterations > 5 {
|
|
||||||
log::warn!(
|
|
||||||
concat!(
|
|
||||||
"Reached maximum number of fix iterations for '{}'. There's ",
|
|
||||||
"probably a bug in Deno. Please fix this file manually.",
|
|
||||||
),
|
|
||||||
specifier,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if fix_iterations > 0 {
|
|
||||||
// everything looks good and the file still parses, so write it out
|
|
||||||
fs::write(file_path, source.text().as_ref())
|
|
||||||
.context("Failed writing fix to file.")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((source, diagnostics))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_lint_fixes_and_relint(
|
|
||||||
specifier: &ModuleSpecifier,
|
|
||||||
media_type: MediaType,
|
|
||||||
linter: &Linter,
|
|
||||||
config: LintConfig,
|
|
||||||
text_info: &SourceTextInfo,
|
|
||||||
diagnostics: &[LintDiagnostic],
|
|
||||||
) -> Result<Option<(ParsedSource, Vec<LintDiagnostic>)>, AnyError> {
|
|
||||||
let Some(new_text) = apply_lint_fixes(text_info, diagnostics) else {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
linter
|
|
||||||
.lint_file(LintFileOptions {
|
|
||||||
specifier: specifier.clone(),
|
|
||||||
source_code: new_text,
|
|
||||||
media_type,
|
|
||||||
config,
|
|
||||||
})
|
|
||||||
.map(Some)
|
|
||||||
.context(
|
|
||||||
"An applied lint fix caused a syntax error. Please report this bug.",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_lint_fixes(
|
|
||||||
text_info: &SourceTextInfo,
|
|
||||||
diagnostics: &[LintDiagnostic],
|
|
||||||
) -> Option<String> {
|
|
||||||
if diagnostics.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_start = text_info.range().start;
|
|
||||||
let mut quick_fixes = diagnostics
|
|
||||||
.iter()
|
|
||||||
// use the first quick fix
|
|
||||||
.filter_map(|d| d.fixes.first())
|
|
||||||
.flat_map(|fix| fix.changes.iter())
|
|
||||||
.map(|change| deno_ast::TextChange {
|
|
||||||
range: change.range.as_byte_range(file_start),
|
|
||||||
new_text: change.new_text.to_string(),
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
if quick_fixes.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
// remove any overlapping text changes, we'll circle
|
|
||||||
// back for another pass to fix the remaining
|
|
||||||
quick_fixes.sort_by_key(|change| change.range.start);
|
|
||||||
for i in (1..quick_fixes.len()).rev() {
|
|
||||||
let cur = &quick_fixes[i];
|
|
||||||
let previous = &quick_fixes[i - 1];
|
|
||||||
let is_overlapping = cur.range.start < previous.range.end;
|
|
||||||
if is_overlapping {
|
|
||||||
quick_fixes.remove(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let new_text =
|
|
||||||
deno_ast::apply_text_changes(text_info.text_str(), quick_fixes);
|
|
||||||
Some(new_text)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Lint stdin and write result to stdout.
|
/// Lint stdin and write result to stdout.
|
||||||
/// Treats input as TypeScript.
|
/// Treats input as TypeScript.
|
||||||
/// Compatible with `--json` flag.
|
/// Compatible with `--json` flag.
|
||||||
fn lint_stdin(
|
fn lint_stdin(
|
||||||
file_path: &Path,
|
file_path: &Path,
|
||||||
lint_rules: Vec<&'static dyn LintRule>,
|
configured_rules: ConfiguredRules,
|
||||||
deno_lint_config: LintConfig,
|
deno_lint_config: LintConfig,
|
||||||
) -> Result<(ParsedSource, Vec<LintDiagnostic>), AnyError> {
|
) -> Result<(ParsedSource, Vec<LintDiagnostic>), AnyError> {
|
||||||
let mut source_code = String::new();
|
let mut source_code = String::new();
|
||||||
|
@ -629,15 +489,14 @@ fn lint_stdin(
|
||||||
return Err(generic_error("Failed to read from stdin"));
|
return Err(generic_error("Failed to read from stdin"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let linter = create_linter(lint_rules);
|
let linter = CliLinter::new(CliLinterOptions {
|
||||||
|
fix: false,
|
||||||
|
configured_rules,
|
||||||
|
deno_lint_config,
|
||||||
|
});
|
||||||
|
|
||||||
linter
|
linter
|
||||||
.lint_file(LintFileOptions {
|
.lint_file(file_path, deno_ast::strip_bom(source_code))
|
||||||
specifier: specifier_from_file_path(file_path)?,
|
|
||||||
source_code: deno_ast::strip_bom(source_code),
|
|
||||||
media_type: MediaType::TypeScript,
|
|
||||||
config: deno_lint_config,
|
|
||||||
})
|
|
||||||
.map_err(AnyError::from)
|
.map_err(AnyError::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -656,11 +515,18 @@ fn handle_lint_result(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_diagnostics.sort_by(|a, b| match a.specifier.cmp(&b.specifier) {
|
file_diagnostics.sort_by(|a, b| match a.specifier.cmp(&b.specifier) {
|
||||||
std::cmp::Ordering::Equal => a.range.start.cmp(&b.range.start),
|
std::cmp::Ordering::Equal => {
|
||||||
|
let a_start = a.range.as_ref().map(|r| r.range.start);
|
||||||
|
let b_start = b.range.as_ref().map(|r| r.range.start);
|
||||||
|
match a_start.cmp(&b_start) {
|
||||||
|
std::cmp::Ordering::Equal => a.details.code.cmp(&b.details.code),
|
||||||
|
other => other,
|
||||||
|
}
|
||||||
|
}
|
||||||
file_order => file_order,
|
file_order => file_order,
|
||||||
});
|
});
|
||||||
for d in &file_diagnostics {
|
for d in &file_diagnostics {
|
||||||
reporter.visit_diagnostic(LintOrCliDiagnostic::Lint(d));
|
reporter.visit_diagnostic(d);
|
||||||
}
|
}
|
||||||
file_diagnostics.is_empty()
|
file_diagnostics.is_empty()
|
||||||
}
|
}
|
||||||
|
@ -671,99 +537,8 @@ fn handle_lint_result(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub enum LintOrCliDiagnostic<'a> {
|
|
||||||
Lint(&'a LintDiagnostic),
|
|
||||||
FastCheck(&'a FastCheckDiagnostic),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> LintOrCliDiagnostic<'a> {
|
|
||||||
pub fn specifier(&self) -> &ModuleSpecifier {
|
|
||||||
match self {
|
|
||||||
LintOrCliDiagnostic::Lint(d) => &d.specifier,
|
|
||||||
LintOrCliDiagnostic::FastCheck(d) => d.specifier(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn range(&self) -> Option<(&SourceTextInfo, SourceRange)> {
|
|
||||||
match self {
|
|
||||||
LintOrCliDiagnostic::Lint(d) => Some((&d.text_info, d.range)),
|
|
||||||
LintOrCliDiagnostic::FastCheck(d) => {
|
|
||||||
d.range().map(|r| (&r.text_info, r.range))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> deno_ast::diagnostics::Diagnostic for LintOrCliDiagnostic<'a> {
|
|
||||||
fn level(&self) -> deno_ast::diagnostics::DiagnosticLevel {
|
|
||||||
match self {
|
|
||||||
LintOrCliDiagnostic::Lint(d) => d.level(),
|
|
||||||
LintOrCliDiagnostic::FastCheck(d) => d.level(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn code(&self) -> Cow<'_, str> {
|
|
||||||
match self {
|
|
||||||
LintOrCliDiagnostic::Lint(d) => d.code(),
|
|
||||||
LintOrCliDiagnostic::FastCheck(_) => Cow::Borrowed("no-slow-types"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn message(&self) -> Cow<'_, str> {
|
|
||||||
match self {
|
|
||||||
LintOrCliDiagnostic::Lint(d) => d.message(),
|
|
||||||
LintOrCliDiagnostic::FastCheck(d) => d.message(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn location(&self) -> deno_ast::diagnostics::DiagnosticLocation {
|
|
||||||
match self {
|
|
||||||
LintOrCliDiagnostic::Lint(d) => d.location(),
|
|
||||||
LintOrCliDiagnostic::FastCheck(d) => d.location(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn snippet(&self) -> Option<deno_ast::diagnostics::DiagnosticSnippet<'_>> {
|
|
||||||
match self {
|
|
||||||
LintOrCliDiagnostic::Lint(d) => d.snippet(),
|
|
||||||
LintOrCliDiagnostic::FastCheck(d) => d.snippet(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hint(&self) -> Option<Cow<'_, str>> {
|
|
||||||
match self {
|
|
||||||
LintOrCliDiagnostic::Lint(d) => d.hint(),
|
|
||||||
LintOrCliDiagnostic::FastCheck(d) => d.hint(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn snippet_fixed(
|
|
||||||
&self,
|
|
||||||
) -> Option<deno_ast::diagnostics::DiagnosticSnippet<'_>> {
|
|
||||||
match self {
|
|
||||||
LintOrCliDiagnostic::Lint(d) => d.snippet_fixed(),
|
|
||||||
LintOrCliDiagnostic::FastCheck(d) => d.snippet_fixed(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn info(&self) -> Cow<'_, [Cow<'_, str>]> {
|
|
||||||
match self {
|
|
||||||
LintOrCliDiagnostic::Lint(d) => d.info(),
|
|
||||||
LintOrCliDiagnostic::FastCheck(d) => d.info(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn docs_url(&self) -> Option<Cow<'_, str>> {
|
|
||||||
match self {
|
|
||||||
LintOrCliDiagnostic::Lint(d) => d.docs_url(),
|
|
||||||
LintOrCliDiagnostic::FastCheck(d) => d.docs_url(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait LintReporter {
|
trait LintReporter {
|
||||||
fn visit_diagnostic(&mut self, d: LintOrCliDiagnostic);
|
fn visit_diagnostic(&mut self, d: &LintDiagnostic);
|
||||||
fn visit_error(&mut self, file_path: &str, err: &AnyError);
|
fn visit_error(&mut self, file_path: &str, err: &AnyError);
|
||||||
fn close(&mut self, check_count: usize);
|
fn close(&mut self, check_count: usize);
|
||||||
}
|
}
|
||||||
|
@ -789,12 +564,10 @@ impl PrettyLintReporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LintReporter for PrettyLintReporter {
|
impl LintReporter for PrettyLintReporter {
|
||||||
fn visit_diagnostic(&mut self, d: LintOrCliDiagnostic) {
|
fn visit_diagnostic(&mut self, d: &LintDiagnostic) {
|
||||||
self.lint_count += 1;
|
self.lint_count += 1;
|
||||||
if let LintOrCliDiagnostic::Lint(d) = d {
|
if !d.details.fixes.is_empty() {
|
||||||
if !d.fixes.is_empty() {
|
self.fixable_diagnostics += 1;
|
||||||
self.fixable_diagnostics += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log::error!("{}\n", d.display());
|
log::error!("{}\n", d.display());
|
||||||
|
@ -838,15 +611,17 @@ impl CompactLintReporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LintReporter for CompactLintReporter {
|
impl LintReporter for CompactLintReporter {
|
||||||
fn visit_diagnostic(&mut self, d: LintOrCliDiagnostic) {
|
fn visit_diagnostic(&mut self, d: &LintDiagnostic) {
|
||||||
self.lint_count += 1;
|
self.lint_count += 1;
|
||||||
|
|
||||||
match d.range() {
|
match &d.range {
|
||||||
Some((text_info, range)) => {
|
Some(range) => {
|
||||||
|
let text_info = &range.text_info;
|
||||||
|
let range = &range.range;
|
||||||
let line_and_column = text_info.line_and_column_display(range.start);
|
let line_and_column = text_info.line_and_column_display(range.start);
|
||||||
log::error!(
|
log::error!(
|
||||||
"{}: line {}, col {} - {} ({})",
|
"{}: line {}, col {} - {} ({})",
|
||||||
d.specifier(),
|
d.specifier,
|
||||||
line_and_column.line_number,
|
line_and_column.line_number,
|
||||||
line_and_column.column_number,
|
line_and_column.column_number,
|
||||||
d.message(),
|
d.message(),
|
||||||
|
@ -854,7 +629,7 @@ impl LintReporter for CompactLintReporter {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
log::error!("{}: {} ({})", d.specifier(), d.message(), d.code())
|
log::error!("{}: {} ({})", d.specifier, d.message(), d.code())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -932,18 +707,22 @@ impl JsonLintReporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LintReporter for JsonLintReporter {
|
impl LintReporter for JsonLintReporter {
|
||||||
fn visit_diagnostic(&mut self, d: LintOrCliDiagnostic) {
|
fn visit_diagnostic(&mut self, d: &LintDiagnostic) {
|
||||||
self.diagnostics.push(JsonLintDiagnostic {
|
self.diagnostics.push(JsonLintDiagnostic {
|
||||||
filename: d.specifier().to_string(),
|
filename: d.specifier.to_string(),
|
||||||
range: d.range().map(|(text_info, range)| JsonLintDiagnosticRange {
|
range: d.range.as_ref().map(|range| {
|
||||||
start: JsonDiagnosticLintPosition::new(
|
let text_info = &range.text_info;
|
||||||
range.start.as_byte_index(text_info.range().start),
|
let range = range.range;
|
||||||
text_info.line_and_column_index(range.start),
|
JsonLintDiagnosticRange {
|
||||||
),
|
start: JsonDiagnosticLintPosition::new(
|
||||||
end: JsonDiagnosticLintPosition::new(
|
range.start.as_byte_index(text_info.range().start),
|
||||||
range.end.as_byte_index(text_info.range().start),
|
text_info.line_and_column_index(range.start),
|
||||||
text_info.line_and_column_index(range.end),
|
),
|
||||||
),
|
end: JsonDiagnosticLintPosition::new(
|
||||||
|
range.end.as_byte_index(text_info.range().start),
|
||||||
|
text_info.line_and_column_index(range.end),
|
||||||
|
),
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
message: d.message().to_string(),
|
message: d.message().to_string(),
|
||||||
code: d.code().to_string(),
|
code: d.code().to_string(),
|
||||||
|
@ -994,116 +773,3 @@ fn sort_diagnostics(diagnostics: &mut [JsonLintDiagnostic]) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_config_rules_err_empty(
|
|
||||||
rules: LintRulesConfig,
|
|
||||||
maybe_config_file: Option<&ConfigFile>,
|
|
||||||
) -> Result<ConfiguredRules, AnyError> {
|
|
||||||
let lint_rules = get_configured_rules(rules, maybe_config_file);
|
|
||||||
if lint_rules.rules.is_empty() {
|
|
||||||
bail!("No rules have been configured")
|
|
||||||
}
|
|
||||||
Ok(lint_rules)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ConfiguredRules {
|
|
||||||
pub rules: Vec<&'static dyn LintRule>,
|
|
||||||
// cli specific rules
|
|
||||||
pub no_slow_types: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ConfiguredRules {
|
|
||||||
fn default() -> Self {
|
|
||||||
get_configured_rules(Default::default(), None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConfiguredRules {
|
|
||||||
fn incremental_cache_state(&self) -> Vec<&str> {
|
|
||||||
// use a hash of the rule names in order to bust the cache
|
|
||||||
let mut names = self.rules.iter().map(|r| r.code()).collect::<Vec<_>>();
|
|
||||||
// ensure this is stable by sorting it
|
|
||||||
names.sort_unstable();
|
|
||||||
if self.no_slow_types {
|
|
||||||
names.push("no-slow-types");
|
|
||||||
}
|
|
||||||
names
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_configured_rules(
|
|
||||||
rules: LintRulesConfig,
|
|
||||||
maybe_config_file: Option<&ConfigFile>,
|
|
||||||
) -> ConfiguredRules {
|
|
||||||
const NO_SLOW_TYPES_NAME: &str = "no-slow-types";
|
|
||||||
let implicit_no_slow_types =
|
|
||||||
maybe_config_file.map(|c| c.is_package()).unwrap_or(false);
|
|
||||||
let no_slow_types = implicit_no_slow_types
|
|
||||||
&& !rules
|
|
||||||
.exclude
|
|
||||||
.as_ref()
|
|
||||||
.map(|exclude| exclude.iter().any(|i| i == NO_SLOW_TYPES_NAME))
|
|
||||||
.unwrap_or(false);
|
|
||||||
let rules = rules::get_filtered_rules(
|
|
||||||
rules
|
|
||||||
.tags
|
|
||||||
.or_else(|| Some(get_default_tags(maybe_config_file))),
|
|
||||||
rules.exclude.map(|exclude| {
|
|
||||||
exclude
|
|
||||||
.into_iter()
|
|
||||||
.filter(|c| c != NO_SLOW_TYPES_NAME)
|
|
||||||
.collect()
|
|
||||||
}),
|
|
||||||
rules.include.map(|include| {
|
|
||||||
include
|
|
||||||
.into_iter()
|
|
||||||
.filter(|c| c != NO_SLOW_TYPES_NAME)
|
|
||||||
.collect()
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
ConfiguredRules {
|
|
||||||
rules,
|
|
||||||
no_slow_types,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_default_tags(maybe_config_file: Option<&ConfigFile>) -> Vec<String> {
|
|
||||||
let mut tags = Vec::with_capacity(2);
|
|
||||||
tags.push("recommended".to_string());
|
|
||||||
if maybe_config_file.map(|c| c.is_package()).unwrap_or(false) {
|
|
||||||
tags.push("jsr".to_string());
|
|
||||||
}
|
|
||||||
tags
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use deno_lint::rules::get_recommended_rules;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::args::LintRulesConfig;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn recommended_rules_when_no_tags_in_config() {
|
|
||||||
let rules_config = LintRulesConfig {
|
|
||||||
exclude: Some(vec!["no-debugger".to_string()]),
|
|
||||||
include: None,
|
|
||||||
tags: None,
|
|
||||||
};
|
|
||||||
let rules = get_configured_rules(rules_config, None);
|
|
||||||
let mut rule_names = rules
|
|
||||||
.rules
|
|
||||||
.into_iter()
|
|
||||||
.map(|r| r.code().to_string())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
rule_names.sort();
|
|
||||||
let mut recommended_rule_names = get_recommended_rules()
|
|
||||||
.into_iter()
|
|
||||||
.map(|r| r.code().to_string())
|
|
||||||
.filter(|n| n != "no-debugger")
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
recommended_rule_names.sort();
|
|
||||||
assert_eq!(rule_names, recommended_rule_names);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
|
||||||
|
|
||||||
use deno_ast::diagnostics::Diagnostic;
|
|
||||||
use deno_ast::ModuleSpecifier;
|
|
||||||
use deno_graph::FastCheckDiagnostic;
|
|
||||||
use deno_graph::ModuleGraph;
|
|
||||||
|
|
||||||
/// Collects diagnostics from the module graph for the
|
|
||||||
/// given package's export URLs.
|
|
||||||
pub fn collect_no_slow_type_diagnostics(
|
|
||||||
package_export_urls: &[ModuleSpecifier],
|
|
||||||
graph: &ModuleGraph,
|
|
||||||
) -> Vec<FastCheckDiagnostic> {
|
|
||||||
let mut js_exports = package_export_urls
|
|
||||||
.iter()
|
|
||||||
.filter_map(|url| graph.get(url).and_then(|m| m.js()));
|
|
||||||
// fast check puts the same diagnostics in each entrypoint for the
|
|
||||||
// package (since it's all or nothing), so we only need to check
|
|
||||||
// the first one JS entrypoint
|
|
||||||
let Some(module) = js_exports.next() else {
|
|
||||||
// could happen if all the exports are JSON
|
|
||||||
return vec![];
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(diagnostics) = module.fast_check_diagnostics() {
|
|
||||||
let mut diagnostics = diagnostics.clone();
|
|
||||||
diagnostics.sort_by_cached_key(|d| {
|
|
||||||
(
|
|
||||||
d.specifier().clone(),
|
|
||||||
d.range().map(|r| r.range),
|
|
||||||
d.code().to_string(),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
diagnostics
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
}
|
|
296
cli/tools/lint/rules/mod.rs
Normal file
296
cli/tools/lint/rules/mod.rs
Normal file
|
@ -0,0 +1,296 @@
|
||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use deno_ast::ModuleSpecifier;
|
||||||
|
use deno_config::deno_json::ConfigFile;
|
||||||
|
use deno_config::deno_json::LintRulesConfig;
|
||||||
|
use deno_config::workspace::WorkspaceResolver;
|
||||||
|
use deno_core::anyhow::bail;
|
||||||
|
use deno_core::error::AnyError;
|
||||||
|
use deno_graph::ModuleGraph;
|
||||||
|
use deno_lint::diagnostic::LintDiagnostic;
|
||||||
|
use deno_lint::rules::LintRule;
|
||||||
|
|
||||||
|
use crate::resolver::SloppyImportsResolver;
|
||||||
|
|
||||||
|
mod no_sloppy_imports;
|
||||||
|
mod no_slow_types;
|
||||||
|
|
||||||
|
// used for publishing
|
||||||
|
pub use no_slow_types::collect_no_slow_type_diagnostics;
|
||||||
|
|
||||||
|
pub trait PackageLintRule: std::fmt::Debug + Send + Sync {
|
||||||
|
fn code(&self) -> &'static str;
|
||||||
|
|
||||||
|
fn tags(&self) -> &'static [&'static str] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn docs(&self) -> &'static str;
|
||||||
|
|
||||||
|
fn help_docs_url(&self) -> Cow<'static, str>;
|
||||||
|
|
||||||
|
fn lint_package(
|
||||||
|
&self,
|
||||||
|
graph: &ModuleGraph,
|
||||||
|
entrypoints: &[ModuleSpecifier],
|
||||||
|
) -> Vec<LintDiagnostic>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) trait ExtendedLintRule: LintRule {
|
||||||
|
/// If the rule supports the incremental cache.
|
||||||
|
fn supports_incremental_cache(&self) -> bool;
|
||||||
|
|
||||||
|
fn help_docs_url(&self) -> Cow<'static, str>;
|
||||||
|
|
||||||
|
fn into_base(self: Box<Self>) -> Box<dyn LintRule>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum FileOrPackageLintRule {
|
||||||
|
File(Box<dyn LintRule>),
|
||||||
|
Package(Box<dyn PackageLintRule>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum CliLintRuleKind {
|
||||||
|
DenoLint(Box<dyn LintRule>),
|
||||||
|
Extended(Box<dyn ExtendedLintRule>),
|
||||||
|
Package(Box<dyn PackageLintRule>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CliLintRule(CliLintRuleKind);
|
||||||
|
|
||||||
|
impl CliLintRule {
|
||||||
|
pub fn code(&self) -> &'static str {
|
||||||
|
use CliLintRuleKind::*;
|
||||||
|
match &self.0 {
|
||||||
|
DenoLint(rule) => rule.code(),
|
||||||
|
Extended(rule) => rule.code(),
|
||||||
|
Package(rule) => rule.code(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tags(&self) -> &'static [&'static str] {
|
||||||
|
use CliLintRuleKind::*;
|
||||||
|
match &self.0 {
|
||||||
|
DenoLint(rule) => rule.tags(),
|
||||||
|
Extended(rule) => rule.tags(),
|
||||||
|
Package(rule) => rule.tags(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn docs(&self) -> &'static str {
|
||||||
|
use CliLintRuleKind::*;
|
||||||
|
match &self.0 {
|
||||||
|
DenoLint(rule) => rule.docs(),
|
||||||
|
Extended(rule) => rule.docs(),
|
||||||
|
Package(rule) => rule.docs(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn help_docs_url(&self) -> Cow<'static, str> {
|
||||||
|
use CliLintRuleKind::*;
|
||||||
|
match &self.0 {
|
||||||
|
DenoLint(rule) => {
|
||||||
|
Cow::Owned(format!("https://lint.deno.land/rules/{}", rule.code()))
|
||||||
|
}
|
||||||
|
Extended(rule) => rule.help_docs_url(),
|
||||||
|
Package(rule) => rule.help_docs_url(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn supports_incremental_cache(&self) -> bool {
|
||||||
|
use CliLintRuleKind::*;
|
||||||
|
match &self.0 {
|
||||||
|
DenoLint(_) => true,
|
||||||
|
Extended(rule) => rule.supports_incremental_cache(),
|
||||||
|
// graph rules don't go through the incremental cache, so allow it
|
||||||
|
Package(_) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_file_or_pkg_rule(self) -> FileOrPackageLintRule {
|
||||||
|
use CliLintRuleKind::*;
|
||||||
|
match self.0 {
|
||||||
|
DenoLint(rule) => FileOrPackageLintRule::File(rule),
|
||||||
|
Extended(rule) => FileOrPackageLintRule::File(rule.into_base()),
|
||||||
|
Package(rule) => FileOrPackageLintRule::Package(rule),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ConfiguredRules {
|
||||||
|
pub all_rule_codes: HashSet<&'static str>,
|
||||||
|
pub rules: Vec<CliLintRule>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfiguredRules {
|
||||||
|
pub fn incremental_cache_state(&self) -> Option<impl std::hash::Hash> {
|
||||||
|
if self.rules.iter().any(|r| !r.supports_incremental_cache()) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// use a hash of the rule names in order to bust the cache
|
||||||
|
let mut codes = self.rules.iter().map(|r| r.code()).collect::<Vec<_>>();
|
||||||
|
// ensure this is stable by sorting it
|
||||||
|
codes.sort_unstable();
|
||||||
|
Some(codes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LintRuleProvider {
|
||||||
|
sloppy_imports_resolver: Option<Arc<SloppyImportsResolver>>,
|
||||||
|
workspace_resolver: Option<Arc<WorkspaceResolver>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LintRuleProvider {
|
||||||
|
pub fn new(
|
||||||
|
sloppy_imports_resolver: Option<Arc<SloppyImportsResolver>>,
|
||||||
|
workspace_resolver: Option<Arc<WorkspaceResolver>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
sloppy_imports_resolver,
|
||||||
|
workspace_resolver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_lint_rules_err_empty(
|
||||||
|
&self,
|
||||||
|
rules: LintRulesConfig,
|
||||||
|
maybe_config_file: Option<&ConfigFile>,
|
||||||
|
) -> Result<ConfiguredRules, AnyError> {
|
||||||
|
let lint_rules = self.resolve_lint_rules(rules, maybe_config_file);
|
||||||
|
if lint_rules.rules.is_empty() {
|
||||||
|
bail!("No rules have been configured")
|
||||||
|
}
|
||||||
|
Ok(lint_rules)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_lint_rules(
|
||||||
|
&self,
|
||||||
|
rules: LintRulesConfig,
|
||||||
|
maybe_config_file: Option<&ConfigFile>,
|
||||||
|
) -> ConfiguredRules {
|
||||||
|
let deno_lint_rules = deno_lint::rules::get_all_rules();
|
||||||
|
let cli_lint_rules = vec![CliLintRule(CliLintRuleKind::Extended(
|
||||||
|
Box::new(no_sloppy_imports::NoSloppyImportsRule::new(
|
||||||
|
self.sloppy_imports_resolver.clone(),
|
||||||
|
self.workspace_resolver.clone(),
|
||||||
|
)),
|
||||||
|
))];
|
||||||
|
let cli_graph_rules = vec![CliLintRule(CliLintRuleKind::Package(
|
||||||
|
Box::new(no_slow_types::NoSlowTypesRule),
|
||||||
|
))];
|
||||||
|
let mut all_rule_names = HashSet::with_capacity(
|
||||||
|
deno_lint_rules.len() + cli_lint_rules.len() + cli_graph_rules.len(),
|
||||||
|
);
|
||||||
|
let all_rules = deno_lint_rules
|
||||||
|
.into_iter()
|
||||||
|
.map(|rule| CliLintRule(CliLintRuleKind::DenoLint(rule)))
|
||||||
|
.chain(cli_lint_rules)
|
||||||
|
.chain(cli_graph_rules)
|
||||||
|
.inspect(|rule| {
|
||||||
|
all_rule_names.insert(rule.code());
|
||||||
|
});
|
||||||
|
let rules = filtered_rules(
|
||||||
|
all_rules,
|
||||||
|
rules
|
||||||
|
.tags
|
||||||
|
.or_else(|| Some(get_default_tags(maybe_config_file))),
|
||||||
|
rules.exclude,
|
||||||
|
rules.include,
|
||||||
|
);
|
||||||
|
ConfiguredRules {
|
||||||
|
rules,
|
||||||
|
all_rule_codes: all_rule_names,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_default_tags(maybe_config_file: Option<&ConfigFile>) -> Vec<String> {
|
||||||
|
let mut tags = Vec::with_capacity(2);
|
||||||
|
tags.push("recommended".to_string());
|
||||||
|
if maybe_config_file.map(|c| c.is_package()).unwrap_or(false) {
|
||||||
|
tags.push("jsr".to_string());
|
||||||
|
}
|
||||||
|
tags
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filtered_rules(
|
||||||
|
all_rules: impl Iterator<Item = CliLintRule>,
|
||||||
|
maybe_tags: Option<Vec<String>>,
|
||||||
|
maybe_exclude: Option<Vec<String>>,
|
||||||
|
maybe_include: Option<Vec<String>>,
|
||||||
|
) -> Vec<CliLintRule> {
|
||||||
|
let tags_set =
|
||||||
|
maybe_tags.map(|tags| tags.into_iter().collect::<HashSet<_>>());
|
||||||
|
|
||||||
|
let mut rules = all_rules
|
||||||
|
.filter(|rule| {
|
||||||
|
let mut passes = if let Some(tags_set) = &tags_set {
|
||||||
|
rule
|
||||||
|
.tags()
|
||||||
|
.iter()
|
||||||
|
.any(|t| tags_set.contains(&t.to_string()))
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(includes) = &maybe_include {
|
||||||
|
if includes.contains(&rule.code().to_owned()) {
|
||||||
|
passes |= true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(excludes) = &maybe_exclude {
|
||||||
|
if excludes.contains(&rule.code().to_owned()) {
|
||||||
|
passes &= false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
passes
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
rules.sort_by_key(|r| r.code());
|
||||||
|
|
||||||
|
rules
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::args::LintRulesConfig;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn recommended_rules_when_no_tags_in_config() {
|
||||||
|
let rules_config = LintRulesConfig {
|
||||||
|
exclude: Some(vec!["no-debugger".to_string()]),
|
||||||
|
include: None,
|
||||||
|
tags: None,
|
||||||
|
};
|
||||||
|
let rules_provider = LintRuleProvider::new(None, None);
|
||||||
|
let rules = rules_provider.resolve_lint_rules(rules_config, None);
|
||||||
|
let mut rule_names = rules
|
||||||
|
.rules
|
||||||
|
.into_iter()
|
||||||
|
.map(|r| r.code().to_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
rule_names.sort();
|
||||||
|
let mut recommended_rule_names = rules_provider
|
||||||
|
.resolve_lint_rules(Default::default(), None)
|
||||||
|
.rules
|
||||||
|
.into_iter()
|
||||||
|
.filter(|r| r.tags().iter().any(|t| *t == "recommended"))
|
||||||
|
.map(|r| r.code().to_string())
|
||||||
|
.filter(|n| n != "no-debugger")
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
recommended_rule_names.sort();
|
||||||
|
assert_eq!(rule_names, recommended_rule_names);
|
||||||
|
}
|
||||||
|
}
|
20
cli/tools/lint/rules/no_sloppy_imports.md
Normal file
20
cli/tools/lint/rules/no_sloppy_imports.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
Enforces specifying explicit references to paths in module specifiers.
|
||||||
|
|
||||||
|
Non-explicit specifiers are ambiguous and require probing for the correct file
|
||||||
|
path on every run, which has a performance overhead.
|
||||||
|
|
||||||
|
Note: This lint rule is only active when using `--unstable-sloppy-imports`.
|
||||||
|
|
||||||
|
### Invalid:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { add } from "./math/add";
|
||||||
|
import { ConsoleLogger } from "./loggers";
|
||||||
|
```
|
||||||
|
|
||||||
|
### Valid:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { add } from "./math/add.ts";
|
||||||
|
import { ConsoleLogger } from "./loggers/index.ts";
|
||||||
|
```
|
214
cli/tools/lint/rules/no_sloppy_imports.rs
Normal file
214
cli/tools/lint/rules/no_sloppy_imports.rs
Normal file
|
@ -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!("")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
cli/tools/lint/rules/no_slow_types.md
Normal file
3
cli/tools/lint/rules/no_slow_types.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Enforces using types that are explicit or can be simply inferred.
|
||||||
|
|
||||||
|
Read more: https://jsr.io/docs/about-slow-types
|
98
cli/tools/lint/rules/no_slow_types.rs
Normal file
98
cli/tools/lint/rules/no_slow_types.rs
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use deno_ast::diagnostics::Diagnostic;
|
||||||
|
use deno_ast::ModuleSpecifier;
|
||||||
|
use deno_graph::FastCheckDiagnostic;
|
||||||
|
use deno_graph::ModuleGraph;
|
||||||
|
use deno_lint::diagnostic::LintDiagnostic;
|
||||||
|
use deno_lint::diagnostic::LintDiagnosticDetails;
|
||||||
|
use deno_lint::diagnostic::LintDiagnosticRange;
|
||||||
|
|
||||||
|
use super::PackageLintRule;
|
||||||
|
|
||||||
|
const CODE: &str = "no-slow-types";
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NoSlowTypesRule;
|
||||||
|
|
||||||
|
impl PackageLintRule for NoSlowTypesRule {
|
||||||
|
fn code(&self) -> &'static str {
|
||||||
|
CODE
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tags(&self) -> &'static [&'static str] {
|
||||||
|
&["jsr"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn docs(&self) -> &'static str {
|
||||||
|
include_str!("no_slow_types.md")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn help_docs_url(&self) -> Cow<'static, str> {
|
||||||
|
Cow::Borrowed("https://jsr.io/docs/about-slow-types")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lint_package(
|
||||||
|
&self,
|
||||||
|
graph: &ModuleGraph,
|
||||||
|
entrypoints: &[ModuleSpecifier],
|
||||||
|
) -> Vec<LintDiagnostic> {
|
||||||
|
collect_no_slow_type_diagnostics(graph, entrypoints)
|
||||||
|
.into_iter()
|
||||||
|
.map(|d| LintDiagnostic {
|
||||||
|
specifier: d.specifier().clone(),
|
||||||
|
range: d.range().map(|range| LintDiagnosticRange {
|
||||||
|
text_info: range.text_info.clone(),
|
||||||
|
range: range.range,
|
||||||
|
description: d.range_description().map(|r| r.to_string()),
|
||||||
|
}),
|
||||||
|
details: LintDiagnosticDetails {
|
||||||
|
message: d.message().to_string(),
|
||||||
|
code: CODE.to_string(),
|
||||||
|
hint: d.hint().map(|h| h.to_string()),
|
||||||
|
info: d
|
||||||
|
.info()
|
||||||
|
.iter()
|
||||||
|
.map(|info| Cow::Owned(info.to_string()))
|
||||||
|
.collect(),
|
||||||
|
fixes: vec![],
|
||||||
|
custom_docs_url: d.docs_url().map(|u| u.into_owned()),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collects diagnostics from the module graph for the
|
||||||
|
/// given package's export URLs.
|
||||||
|
pub fn collect_no_slow_type_diagnostics(
|
||||||
|
graph: &ModuleGraph,
|
||||||
|
package_export_urls: &[ModuleSpecifier],
|
||||||
|
) -> Vec<FastCheckDiagnostic> {
|
||||||
|
let mut js_exports = package_export_urls
|
||||||
|
.iter()
|
||||||
|
.filter_map(|url| graph.get(url).and_then(|m| m.js()));
|
||||||
|
// fast check puts the same diagnostics in each entrypoint for the
|
||||||
|
// package (since it's all or nothing), so we only need to check
|
||||||
|
// the first one JS entrypoint
|
||||||
|
let Some(module) = js_exports.next() else {
|
||||||
|
// could happen if all the exports are JSON
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(diagnostics) = module.fast_check_diagnostics() {
|
||||||
|
let mut diagnostics = diagnostics.clone();
|
||||||
|
diagnostics.sort_by_cached_key(|d| {
|
||||||
|
(
|
||||||
|
d.specifier().clone(),
|
||||||
|
d.range().map(|r| r.range),
|
||||||
|
d.code().to_string(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
diagnostics
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,7 +45,7 @@ use crate::graph_util::ModuleGraphCreator;
|
||||||
use crate::http_util::HttpClient;
|
use crate::http_util::HttpClient;
|
||||||
use crate::resolver::SloppyImportsResolver;
|
use crate::resolver::SloppyImportsResolver;
|
||||||
use crate::tools::check::CheckOptions;
|
use crate::tools::check::CheckOptions;
|
||||||
use crate::tools::lint::no_slow_types;
|
use crate::tools::lint::collect_no_slow_type_diagnostics;
|
||||||
use crate::tools::registry::diagnostics::PublishDiagnostic;
|
use crate::tools::registry::diagnostics::PublishDiagnostic;
|
||||||
use crate::tools::registry::diagnostics::PublishDiagnosticsCollector;
|
use crate::tools::registry::diagnostics::PublishDiagnosticsCollector;
|
||||||
use crate::util::display::human_size;
|
use crate::util::display::human_size;
|
||||||
|
@ -341,7 +341,7 @@ impl PublishPreparer {
|
||||||
for package in package_configs {
|
for package in package_configs {
|
||||||
let export_urls = package.config_file.resolve_export_value_urls()?;
|
let export_urls = package.config_file.resolve_export_value_urls()?;
|
||||||
let diagnostics =
|
let diagnostics =
|
||||||
no_slow_types::collect_no_slow_type_diagnostics(&export_urls, &graph);
|
collect_no_slow_type_diagnostics(&graph, &export_urls);
|
||||||
if !diagnostics.is_empty() {
|
if !diagnostics.is_empty() {
|
||||||
any_pkg_had_diagnostics = true;
|
any_pkg_had_diagnostics = true;
|
||||||
for diagnostic in diagnostics {
|
for diagnostic in diagnostics {
|
||||||
|
|
|
@ -177,8 +177,8 @@ impl SpecifierUnfurler {
|
||||||
if let Some(sloppy_imports_resolver) = &self.sloppy_imports_resolver {
|
if let Some(sloppy_imports_resolver) = &self.sloppy_imports_resolver {
|
||||||
sloppy_imports_resolver
|
sloppy_imports_resolver
|
||||||
.resolve(&resolved, deno_graph::source::ResolutionMode::Execution)
|
.resolve(&resolved, deno_graph::source::ResolutionMode::Execution)
|
||||||
.as_specifier()
|
.map(|res| res.into_specifier())
|
||||||
.clone()
|
.unwrap_or(resolved)
|
||||||
} else {
|
} else {
|
||||||
resolved
|
resolved
|
||||||
};
|
};
|
||||||
|
|
|
@ -45,13 +45,6 @@ To grant permissions, set them before the script argument. For example:
|
||||||
let deno_dir = factory.deno_dir()?;
|
let deno_dir = factory.deno_dir()?;
|
||||||
let http_client = factory.http_client_provider();
|
let http_client = factory.http_client_provider();
|
||||||
|
|
||||||
if cli_options.unstable_sloppy_imports() {
|
|
||||||
log::warn!(
|
|
||||||
"{} Sloppy imports are not recommended and have a negative impact on performance.",
|
|
||||||
crate::colors::yellow("Warning"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run a background task that checks for available upgrades or output
|
// Run a background task that checks for available upgrades or output
|
||||||
// if an earlier run of this background task found a new version of Deno.
|
// if an earlier run of this background task found a new version of Deno.
|
||||||
#[cfg(feature = "upgrade")]
|
#[cfg(feature = "upgrade")]
|
||||||
|
|
|
@ -392,105 +392,3 @@ fn npm_module_check_then_error() {
|
||||||
.assert_matches_text("Check [WILDCARD]main.ts\nerror: TS2305[WILDCARD]has no exported member 'oldName'[WILDCARD]")
|
.assert_matches_text("Check [WILDCARD]main.ts\nerror: TS2305[WILDCARD]has no exported member 'oldName'[WILDCARD]")
|
||||||
.assert_exit_code(1);
|
.assert_exit_code(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_unstable_sloppy_imports_dts_files() {
|
|
||||||
let context = TestContextBuilder::new().use_temp_cwd().build();
|
|
||||||
let temp_dir = context.temp_dir();
|
|
||||||
temp_dir.write("a.ts", "export class A {}"); // resolves this
|
|
||||||
temp_dir.write("a.d.ts", "export class A2 {}");
|
|
||||||
|
|
||||||
temp_dir.write("b.js", "export class B {}");
|
|
||||||
temp_dir.write("b.d.ts", "export class B2 {}"); // this
|
|
||||||
|
|
||||||
temp_dir.write("c.mts", "export class C {}"); // this
|
|
||||||
temp_dir.write("c.d.mts", "export class C2 {}");
|
|
||||||
|
|
||||||
temp_dir.write("d.mjs", "export class D {}");
|
|
||||||
temp_dir.write("d.d.mts", "export class D2 {}"); // this
|
|
||||||
|
|
||||||
let temp_dir = temp_dir.path();
|
|
||||||
|
|
||||||
let dir = temp_dir.join("dir_ts");
|
|
||||||
dir.create_dir_all();
|
|
||||||
dir.join("index.ts").write("export class Dir {}"); // this
|
|
||||||
dir.join("index.d.ts").write("export class Dir2 {}");
|
|
||||||
|
|
||||||
let dir = temp_dir.join("dir_js");
|
|
||||||
dir.create_dir_all();
|
|
||||||
dir.join("index.js").write("export class Dir {}");
|
|
||||||
dir.join("index.d.ts").write("export class Dir2 {}"); // this
|
|
||||||
|
|
||||||
let dir = temp_dir.join("dir_mts");
|
|
||||||
dir.create_dir_all();
|
|
||||||
dir.join("index.mts").write("export class Dir {}"); // this
|
|
||||||
dir.join("index.d.ts").write("export class Dir2 {}");
|
|
||||||
|
|
||||||
let dir = temp_dir.join("dir_mjs");
|
|
||||||
dir.create_dir_all();
|
|
||||||
dir.join("index.mjs").write("export class Dir {}");
|
|
||||||
dir.join("index.d.ts").write("export class Dir2 {}"); // this
|
|
||||||
|
|
||||||
temp_dir.join("main.ts").write(
|
|
||||||
r#"import * as a from "./a.js";
|
|
||||||
import * as b from "./b.js";
|
|
||||||
import * as c from "./c.mjs";
|
|
||||||
import * as d from "./d.mjs";
|
|
||||||
|
|
||||||
console.log(a.A);
|
|
||||||
console.log(b.B2);
|
|
||||||
console.log(c.C);
|
|
||||||
console.log(d.D2);
|
|
||||||
|
|
||||||
import * as a2 from "./a";
|
|
||||||
import * as b2 from "./b";
|
|
||||||
import * as c2 from "./c";
|
|
||||||
import * as d2 from "./d";
|
|
||||||
|
|
||||||
console.log(a2.A);
|
|
||||||
console.log(b2.B2);
|
|
||||||
console.log(c2.C);
|
|
||||||
console.log(d2.D2);
|
|
||||||
|
|
||||||
import * as dirTs from "./dir_ts";
|
|
||||||
import * as dirJs from "./dir_js";
|
|
||||||
import * as dirMts from "./dir_mts";
|
|
||||||
import * as dirMjs from "./dir_mjs";
|
|
||||||
|
|
||||||
console.log(dirTs.Dir);
|
|
||||||
console.log(dirJs.Dir2);
|
|
||||||
console.log(dirMts.Dir);
|
|
||||||
console.log(dirMjs.Dir2);
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
context
|
|
||||||
.new_command()
|
|
||||||
.args("check --unstable-sloppy-imports main.ts")
|
|
||||||
.run()
|
|
||||||
.assert_matches_text(
|
|
||||||
r#"Warning Sloppy module resolution (hint: update .js extension to .ts)
|
|
||||||
at file:///[WILDCARD]/main.ts:1:20
|
|
||||||
Warning Sloppy module resolution (hint: update .mjs extension to .mts)
|
|
||||||
at file:///[WILDCARD]/main.ts:3:20
|
|
||||||
Warning Sloppy module resolution (hint: add .ts extension)
|
|
||||||
at file:///[WILDCARD]/main.ts:11:21
|
|
||||||
Warning Sloppy module resolution (hint: add .js extension)
|
|
||||||
at file:///[WILDCARD]/main.ts:12:21
|
|
||||||
Warning Sloppy module resolution (hint: add .mts extension)
|
|
||||||
at file:///[WILDCARD]/main.ts:13:21
|
|
||||||
Warning Sloppy module resolution (hint: add .mjs extension)
|
|
||||||
at file:///[WILDCARD]/main.ts:14:21
|
|
||||||
Warning Sloppy module resolution (hint: specify path to index.ts file in directory instead)
|
|
||||||
at file:///[WILDCARD]/main.ts:21:24
|
|
||||||
Warning Sloppy module resolution (hint: specify path to index.js file in directory instead)
|
|
||||||
at file:///[WILDCARD]/main.ts:22:24
|
|
||||||
Warning Sloppy module resolution (hint: specify path to index.mts file in directory instead)
|
|
||||||
at file:///[WILDCARD]/main.ts:23:25
|
|
||||||
Warning Sloppy module resolution (hint: specify path to index.mjs file in directory instead)
|
|
||||||
at file:///[WILDCARD]/main.ts:24:25
|
|
||||||
Check [WILDCARD]main.ts
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.assert_exit_code(0);
|
|
||||||
}
|
|
||||||
|
|
|
@ -14459,7 +14459,67 @@ fn lsp_sloppy_imports() {
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
assert_eq!(json!(diagnostics.all()), json!([]));
|
assert_eq!(
|
||||||
|
json!(diagnostics.all()),
|
||||||
|
json!([{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 0, "character": 19 },
|
||||||
|
"end": { "line": 0, "character": 24 }
|
||||||
|
},
|
||||||
|
"severity": 2,
|
||||||
|
"code": "no-sloppy-imports",
|
||||||
|
"source": "deno-lint",
|
||||||
|
"message": "Sloppy imports are not allowed.",
|
||||||
|
"data": [{
|
||||||
|
"description": "Add a '.ts' extension.",
|
||||||
|
"changes": [{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 0, "character": 19 },
|
||||||
|
"end": { "line": 0, "character": 24 },
|
||||||
|
},
|
||||||
|
"new_text": "'./a.ts'"
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 1, "character": 19 },
|
||||||
|
"end": { "line": 1, "character": 27 }
|
||||||
|
},
|
||||||
|
"severity": 2,
|
||||||
|
"code": "no-sloppy-imports",
|
||||||
|
"source": "deno-lint",
|
||||||
|
"message": "Sloppy imports are not allowed.",
|
||||||
|
"data": [{
|
||||||
|
"description": "Change the extension to '.ts'.",
|
||||||
|
"changes": [{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 1, "character": 19 },
|
||||||
|
"end": { "line": 1, "character": 27 },
|
||||||
|
},
|
||||||
|
"new_text": "'./b.ts'"
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 2, "character": 19 },
|
||||||
|
"end": { "line": 2, "character": 27 }
|
||||||
|
},
|
||||||
|
"severity": 2,
|
||||||
|
"code": "no-sloppy-imports",
|
||||||
|
"source": "deno-lint",
|
||||||
|
"message": "Sloppy imports are not allowed.",
|
||||||
|
"data": [{
|
||||||
|
"description": "Change the extension to '.d.ts'.",
|
||||||
|
"changes": [{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 2, "character": 19 },
|
||||||
|
"end": { "line": 2, "character": 27 },
|
||||||
|
},
|
||||||
|
"new_text": "'./c.d.ts'"
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}])
|
||||||
|
);
|
||||||
|
|
||||||
client.shutdown();
|
client.shutdown();
|
||||||
}
|
}
|
||||||
|
@ -14488,11 +14548,33 @@ fn lsp_sloppy_imports_prefers_dts() {
|
||||||
"import { foo } from './a.js';\nconsole.log(foo);",
|
"import { foo } from './a.js';\nconsole.log(foo);",
|
||||||
);
|
);
|
||||||
let diagnostics = client.did_open_file(&file);
|
let diagnostics = client.did_open_file(&file);
|
||||||
// no warnings because "a.js" exists
|
// no other warnings because "a.js" exists
|
||||||
assert_eq!(diagnostics.all().len(), 0);
|
assert_eq!(
|
||||||
|
json!(diagnostics.all()),
|
||||||
|
json!([{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 0, "character": 20 },
|
||||||
|
"end": { "line": 0, "character": 28 }
|
||||||
|
},
|
||||||
|
"severity": 2,
|
||||||
|
"code": "no-sloppy-imports",
|
||||||
|
"source": "deno-lint",
|
||||||
|
"message": "Sloppy imports are not allowed.",
|
||||||
|
"data": [{
|
||||||
|
"description": "Change the extension to '.d.ts'.",
|
||||||
|
"changes": [{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 0, "character": 20 },
|
||||||
|
"end": { "line": 0, "character": 28 },
|
||||||
|
},
|
||||||
|
"new_text": "'./a.d.ts'"
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}])
|
||||||
|
);
|
||||||
|
|
||||||
let diagnostics = client.did_open_file(&a_dts);
|
let diagnostics = client.did_open_file(&a_dts);
|
||||||
assert_eq!(diagnostics.all().len(), 0, "Got {:#?}", diagnostics.all());
|
assert_eq!(json!(diagnostics.for_file(&a_dts.uri())), json!([]));
|
||||||
|
|
||||||
let response = client.write_request(
|
let response = client.write_request(
|
||||||
"textDocument/references",
|
"textDocument/references",
|
||||||
|
|
|
@ -4851,86 +4851,6 @@ itest!(unsafe_proto_flag {
|
||||||
exit_code: 0,
|
exit_code: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_unstable_sloppy_imports() {
|
|
||||||
let context = TestContextBuilder::new().use_temp_cwd().build();
|
|
||||||
let temp_dir = context.temp_dir();
|
|
||||||
temp_dir.write("a.ts", "export class A {}");
|
|
||||||
temp_dir.write("b.js", "export class B {}");
|
|
||||||
temp_dir.write("c.mts", "export class C {}");
|
|
||||||
temp_dir.write("d.mjs", "export class D {}");
|
|
||||||
temp_dir.write("e.tsx", "export class E {}");
|
|
||||||
temp_dir.write("f.jsx", "export class F {}");
|
|
||||||
let dir = temp_dir.path().join("dir");
|
|
||||||
dir.create_dir_all();
|
|
||||||
dir.join("index.tsx").write("export class G {}");
|
|
||||||
temp_dir.write(
|
|
||||||
"main.ts",
|
|
||||||
r#"import * as a from "./a.js";
|
|
||||||
import * as b from "./b";
|
|
||||||
import * as c from "./c";
|
|
||||||
import * as d from "./d";
|
|
||||||
import * as e from "./e";
|
|
||||||
import * as e2 from "./e.js";
|
|
||||||
import * as f from "./f";
|
|
||||||
import * as g from "./dir";
|
|
||||||
console.log(a.A);
|
|
||||||
console.log(b.B);
|
|
||||||
console.log(c.C);
|
|
||||||
console.log(d.D);
|
|
||||||
console.log(e.E);
|
|
||||||
console.log(e2.E);
|
|
||||||
console.log(f.F);
|
|
||||||
console.log(g.G);
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
// run without sloppy imports
|
|
||||||
context
|
|
||||||
.new_command()
|
|
||||||
.args("run main.ts")
|
|
||||||
.run()
|
|
||||||
.assert_matches_text(r#"error: Module not found "file:///[WILDCARD]/a.js". Maybe change the extension to '.ts' or run with --unstable-sloppy-imports
|
|
||||||
at file:///[WILDCARD]/main.ts:1:20
|
|
||||||
"#)
|
|
||||||
.assert_exit_code(1);
|
|
||||||
|
|
||||||
// now run with sloppy imports
|
|
||||||
temp_dir.write("deno.json", r#"{ "unstable": ["sloppy-imports"] }"#);
|
|
||||||
context
|
|
||||||
.new_command()
|
|
||||||
.args("run main.ts")
|
|
||||||
.run()
|
|
||||||
.assert_matches_text(
|
|
||||||
"Warning Sloppy imports are not recommended and have a negative impact on performance.
|
|
||||||
Warning Sloppy module resolution (hint: update .js extension to .ts)
|
|
||||||
at file:///[WILDCARD]/main.ts:1:20
|
|
||||||
Warning Sloppy module resolution (hint: add .js extension)
|
|
||||||
at file:///[WILDCARD]/main.ts:2:20
|
|
||||||
Warning Sloppy module resolution (hint: add .mts extension)
|
|
||||||
at file:///[WILDCARD]/main.ts:3:20
|
|
||||||
Warning Sloppy module resolution (hint: add .mjs extension)
|
|
||||||
at file:///[WILDCARD]/main.ts:4:20
|
|
||||||
Warning Sloppy module resolution (hint: add .tsx extension)
|
|
||||||
at file:///[WILDCARD]/main.ts:5:20
|
|
||||||
Warning Sloppy module resolution (hint: update .js extension to .tsx)
|
|
||||||
at file:///[WILDCARD]/main.ts:6:21
|
|
||||||
Warning Sloppy module resolution (hint: add .jsx extension)
|
|
||||||
at file:///[WILDCARD]/main.ts:7:20
|
|
||||||
Warning Sloppy module resolution (hint: specify path to index.tsx file in directory instead)
|
|
||||||
at file:///[WILDCARD]/main.ts:8:20
|
|
||||||
[class A]
|
|
||||||
[class B]
|
|
||||||
[class C]
|
|
||||||
[class D]
|
|
||||||
[class E]
|
|
||||||
[class E]
|
|
||||||
[class F]
|
|
||||||
[class G]
|
|
||||||
",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
itest!(unstable_temporal_api {
|
itest!(unstable_temporal_api {
|
||||||
args: "run --no-config --unstable-temporal --check run/unstable_temporal_api/main.ts",
|
args: "run --no-config --unstable-temporal --check run/unstable_temporal_api/main.ts",
|
||||||
output: "run/unstable_temporal_api/main.out",
|
output: "run/unstable_temporal_api/main.out",
|
||||||
|
|
34
tests/specs/lint/sloppy_imports_dts/__test__.jsonc
Normal file
34
tests/specs/lint/sloppy_imports_dts/__test__.jsonc
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"tests": {
|
||||||
|
"check": {
|
||||||
|
"args": "check --unstable-sloppy-imports main.ts",
|
||||||
|
"output": "check.out"
|
||||||
|
},
|
||||||
|
"run": {
|
||||||
|
"args": "run --unstable-sloppy-imports main.ts",
|
||||||
|
"output": "run.out"
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"args": "lint --unstable-sloppy-imports",
|
||||||
|
"output": "lint.out",
|
||||||
|
"exitCode": 1
|
||||||
|
},
|
||||||
|
// try fixing the lint issues and then ensure deno check and run still work
|
||||||
|
"lint_fix": {
|
||||||
|
"tempDir": true,
|
||||||
|
"steps": [{
|
||||||
|
"args": "lint --unstable-sloppy-imports --fix",
|
||||||
|
"output": "Checked 17 files\n"
|
||||||
|
}, {
|
||||||
|
"args": "lint --unstable-sloppy-imports",
|
||||||
|
"output": "Checked 17 files\n"
|
||||||
|
}, {
|
||||||
|
"args": "check --unstable-sloppy-imports main.ts",
|
||||||
|
"output": "check.out"
|
||||||
|
}, {
|
||||||
|
"args": "run --unstable-sloppy-imports main.ts",
|
||||||
|
"output": "run.out"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
tests/specs/lint/sloppy_imports_dts/a.d.ts
vendored
Normal file
1
tests/specs/lint/sloppy_imports_dts/a.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class A2 {}
|
1
tests/specs/lint/sloppy_imports_dts/a.ts
Normal file
1
tests/specs/lint/sloppy_imports_dts/a.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class A {}
|
1
tests/specs/lint/sloppy_imports_dts/b.d.ts
vendored
Normal file
1
tests/specs/lint/sloppy_imports_dts/b.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class B2 {}
|
1
tests/specs/lint/sloppy_imports_dts/b.js
Normal file
1
tests/specs/lint/sloppy_imports_dts/b.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class B {}
|
1
tests/specs/lint/sloppy_imports_dts/c.d.mts
Normal file
1
tests/specs/lint/sloppy_imports_dts/c.d.mts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class C2 {}
|
1
tests/specs/lint/sloppy_imports_dts/c.mts
Normal file
1
tests/specs/lint/sloppy_imports_dts/c.mts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class C {}
|
1
tests/specs/lint/sloppy_imports_dts/check.out
Normal file
1
tests/specs/lint/sloppy_imports_dts/check.out
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Check file:///[WILDLINE]/main.ts
|
1
tests/specs/lint/sloppy_imports_dts/d.d.mts
Normal file
1
tests/specs/lint/sloppy_imports_dts/d.d.mts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class D2 {}
|
1
tests/specs/lint/sloppy_imports_dts/d.mjs
Normal file
1
tests/specs/lint/sloppy_imports_dts/d.mjs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class D {}
|
1
tests/specs/lint/sloppy_imports_dts/dir_js/index.d.ts
vendored
Normal file
1
tests/specs/lint/sloppy_imports_dts/dir_js/index.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class Dir2 {}
|
1
tests/specs/lint/sloppy_imports_dts/dir_js/index.js
Normal file
1
tests/specs/lint/sloppy_imports_dts/dir_js/index.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class Dir {}
|
1
tests/specs/lint/sloppy_imports_dts/dir_mjs/index.d.ts
vendored
Normal file
1
tests/specs/lint/sloppy_imports_dts/dir_mjs/index.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class Dir2 {}
|
1
tests/specs/lint/sloppy_imports_dts/dir_mjs/index.mjs
Normal file
1
tests/specs/lint/sloppy_imports_dts/dir_mjs/index.mjs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class Dir {}
|
1
tests/specs/lint/sloppy_imports_dts/dir_mts/index.d.ts
vendored
Normal file
1
tests/specs/lint/sloppy_imports_dts/dir_mts/index.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class Dir2 {}
|
1
tests/specs/lint/sloppy_imports_dts/dir_mts/index.mts
Normal file
1
tests/specs/lint/sloppy_imports_dts/dir_mts/index.mts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class Dir {}
|
1
tests/specs/lint/sloppy_imports_dts/dir_ts/index.d.ts
vendored
Normal file
1
tests/specs/lint/sloppy_imports_dts/dir_ts/index.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class Dir2 {}
|
1
tests/specs/lint/sloppy_imports_dts/dir_ts/index.ts
Normal file
1
tests/specs/lint/sloppy_imports_dts/dir_ts/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class Dir {}
|
110
tests/specs/lint/sloppy_imports_dts/lint.out
Normal file
110
tests/specs/lint/sloppy_imports_dts/lint.out
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
error[no-sloppy-imports]: Sloppy imports are not allowed.
|
||||||
|
--> [WILDLINE]main.ts:1:20
|
||||||
|
|
|
||||||
|
1 | import * as a from "./a.js";
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
||||||
|
docs: https://docs.deno.com/runtime/manual/tools/unstable_flags/#--unstable-sloppy-imports
|
||||||
|
|
||||||
|
|
||||||
|
error[no-sloppy-imports]: Sloppy imports are not allowed.
|
||||||
|
--> [WILDLINE]main.ts:2:20
|
||||||
|
|
|
||||||
|
2 | import * as b from "./b.js";
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
||||||
|
docs: https://docs.deno.com/runtime/manual/tools/unstable_flags/#--unstable-sloppy-imports
|
||||||
|
|
||||||
|
|
||||||
|
error[no-sloppy-imports]: Sloppy imports are not allowed.
|
||||||
|
--> [WILDLINE]main.ts:3:20
|
||||||
|
|
|
||||||
|
3 | import * as c from "./c.mjs";
|
||||||
|
| ^^^^^^^^^
|
||||||
|
|
||||||
|
docs: https://docs.deno.com/runtime/manual/tools/unstable_flags/#--unstable-sloppy-imports
|
||||||
|
|
||||||
|
|
||||||
|
error[no-sloppy-imports]: Sloppy imports are not allowed.
|
||||||
|
--> [WILDLINE]main.ts:4:20
|
||||||
|
|
|
||||||
|
4 | import * as d from "./d.mjs";
|
||||||
|
| ^^^^^^^^^
|
||||||
|
|
||||||
|
docs: https://docs.deno.com/runtime/manual/tools/unstable_flags/#--unstable-sloppy-imports
|
||||||
|
|
||||||
|
|
||||||
|
error[no-sloppy-imports]: Sloppy imports are not allowed.
|
||||||
|
--> [WILDLINE]main.ts:11:21
|
||||||
|
|
|
||||||
|
11 | import * as a2 from "./a";
|
||||||
|
| ^^^^^
|
||||||
|
|
||||||
|
docs: https://docs.deno.com/runtime/manual/tools/unstable_flags/#--unstable-sloppy-imports
|
||||||
|
|
||||||
|
|
||||||
|
error[no-sloppy-imports]: Sloppy imports are not allowed.
|
||||||
|
--> [WILDLINE]main.ts:12:21
|
||||||
|
|
|
||||||
|
12 | import * as b2 from "./b";
|
||||||
|
| ^^^^^
|
||||||
|
|
||||||
|
docs: https://docs.deno.com/runtime/manual/tools/unstable_flags/#--unstable-sloppy-imports
|
||||||
|
|
||||||
|
|
||||||
|
error[no-sloppy-imports]: Sloppy imports are not allowed.
|
||||||
|
--> [WILDLINE]main.ts:13:21
|
||||||
|
|
|
||||||
|
13 | import * as c2 from "./c";
|
||||||
|
| ^^^^^
|
||||||
|
|
||||||
|
docs: https://docs.deno.com/runtime/manual/tools/unstable_flags/#--unstable-sloppy-imports
|
||||||
|
|
||||||
|
|
||||||
|
error[no-sloppy-imports]: Sloppy imports are not allowed.
|
||||||
|
--> [WILDLINE]main.ts:14:21
|
||||||
|
|
|
||||||
|
14 | import * as d2 from "./d";
|
||||||
|
| ^^^^^
|
||||||
|
|
||||||
|
docs: https://docs.deno.com/runtime/manual/tools/unstable_flags/#--unstable-sloppy-imports
|
||||||
|
|
||||||
|
|
||||||
|
error[no-sloppy-imports]: Sloppy imports are not allowed.
|
||||||
|
--> [WILDLINE]main.ts:21:24
|
||||||
|
|
|
||||||
|
21 | import * as dirTs from "./dir_ts";
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
|
||||||
|
docs: https://docs.deno.com/runtime/manual/tools/unstable_flags/#--unstable-sloppy-imports
|
||||||
|
|
||||||
|
|
||||||
|
error[no-sloppy-imports]: Sloppy imports are not allowed.
|
||||||
|
--> [WILDLINE]main.ts:22:24
|
||||||
|
|
|
||||||
|
22 | import * as dirJs from "./dir_js";
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
|
||||||
|
docs: https://docs.deno.com/runtime/manual/tools/unstable_flags/#--unstable-sloppy-imports
|
||||||
|
|
||||||
|
|
||||||
|
error[no-sloppy-imports]: Sloppy imports are not allowed.
|
||||||
|
--> [WILDLINE]main.ts:23:25
|
||||||
|
|
|
||||||
|
23 | import * as dirMts from "./dir_mts";
|
||||||
|
| ^^^^^^^^^^^
|
||||||
|
|
||||||
|
docs: https://docs.deno.com/runtime/manual/tools/unstable_flags/#--unstable-sloppy-imports
|
||||||
|
|
||||||
|
|
||||||
|
error[no-sloppy-imports]: Sloppy imports are not allowed.
|
||||||
|
--> [WILDLINE]main.ts:24:25
|
||||||
|
|
|
||||||
|
24 | import * as dirMjs from "./dir_mjs";
|
||||||
|
| ^^^^^^^^^^^
|
||||||
|
|
||||||
|
docs: https://docs.deno.com/runtime/manual/tools/unstable_flags/#--unstable-sloppy-imports
|
||||||
|
|
||||||
|
|
||||||
|
Found 12 problems (12 fixable via --fix)
|
||||||
|
Checked 17 files
|
29
tests/specs/lint/sloppy_imports_dts/main.ts
Normal file
29
tests/specs/lint/sloppy_imports_dts/main.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import * as a from "./a.js";
|
||||||
|
import * as b from "./b.js";
|
||||||
|
import * as c from "./c.mjs";
|
||||||
|
import * as d from "./d.mjs";
|
||||||
|
|
||||||
|
console.log(a.A);
|
||||||
|
console.log(b.B2);
|
||||||
|
console.log(c.C);
|
||||||
|
console.log(d.D2);
|
||||||
|
|
||||||
|
import * as a2 from "./a";
|
||||||
|
import * as b2 from "./b";
|
||||||
|
import * as c2 from "./c";
|
||||||
|
import * as d2 from "./d";
|
||||||
|
|
||||||
|
console.log(a2.A);
|
||||||
|
console.log(b2.B2);
|
||||||
|
console.log(c2.C);
|
||||||
|
console.log(d2.D2);
|
||||||
|
|
||||||
|
import * as dirTs from "./dir_ts";
|
||||||
|
import * as dirJs from "./dir_js";
|
||||||
|
import * as dirMts from "./dir_mts";
|
||||||
|
import * as dirMjs from "./dir_mjs";
|
||||||
|
|
||||||
|
console.log(dirTs.Dir);
|
||||||
|
console.log(dirJs.Dir2);
|
||||||
|
console.log(dirMts.Dir);
|
||||||
|
console.log(dirMjs.Dir2);
|
12
tests/specs/lint/sloppy_imports_dts/run.out
Normal file
12
tests/specs/lint/sloppy_imports_dts/run.out
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[class A]
|
||||||
|
undefined
|
||||||
|
[class C]
|
||||||
|
undefined
|
||||||
|
[class A]
|
||||||
|
undefined
|
||||||
|
[class C]
|
||||||
|
undefined
|
||||||
|
[class Dir]
|
||||||
|
undefined
|
||||||
|
[class Dir]
|
||||||
|
undefined
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"tempDir": true,
|
||||||
|
"steps": [{
|
||||||
|
"args": "lint main.ts",
|
||||||
|
"output": "Checked 1 file\n"
|
||||||
|
}, {
|
||||||
|
"args": "lint --unstable-sloppy-imports main.ts",
|
||||||
|
"output": "Checked 1 file\n"
|
||||||
|
}, {
|
||||||
|
"args": [
|
||||||
|
"eval",
|
||||||
|
"Deno.renameSync('file.js', 'file.ts')"
|
||||||
|
],
|
||||||
|
"output": ""
|
||||||
|
}, {
|
||||||
|
"args": "lint --unstable-sloppy-imports main.ts",
|
||||||
|
"output": "fail_js_to_ts.out",
|
||||||
|
"exitCode": 1
|
||||||
|
}]
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
error[no-sloppy-imports]: Sloppy imports are not allowed.
|
||||||
|
--> [WILDLINE]main.ts:1:23
|
||||||
|
|
|
||||||
|
1 | import * as file from "./file.js";
|
||||||
|
| ^^^^^^^^^^^
|
||||||
|
|
||||||
|
docs: https://docs.deno.com/runtime/manual/tools/unstable_flags/#--unstable-sloppy-imports
|
||||||
|
|
||||||
|
|
||||||
|
Found 1 problem (1 fixable via --fix)
|
||||||
|
Checked 1 file
|
|
@ -0,0 +1 @@
|
||||||
|
export class File {}
|
|
@ -0,0 +1,3 @@
|
||||||
|
import * as file from "./file.js";
|
||||||
|
|
||||||
|
console.log(file);
|
|
@ -1,5 +1,3 @@
|
||||||
Warning Sloppy module resolution (hint: specify path to index.ts file in directory instead)
|
|
||||||
at file:///[WILDCARD]/mod.ts:1:20
|
|
||||||
Check file:///[WILDCARD]/mod.ts
|
Check file:///[WILDCARD]/mod.ts
|
||||||
Checking for slow types in the public API...
|
Checking for slow types in the public API...
|
||||||
Check file:///[WILDCARD]/mod.ts
|
Check file:///[WILDCARD]/mod.ts
|
||||||
|
|
10
tests/specs/run/sloppy_imports/__test__.jsonc
Normal file
10
tests/specs/run/sloppy_imports/__test__.jsonc
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"steps": [{
|
||||||
|
"args": "run main.ts",
|
||||||
|
"output": "no_sloppy.out",
|
||||||
|
"exitCode": 1
|
||||||
|
}, {
|
||||||
|
"args": "run --unstable-sloppy-imports main.ts",
|
||||||
|
"output": "sloppy.out"
|
||||||
|
}]
|
||||||
|
}
|
1
tests/specs/run/sloppy_imports/a.ts
Normal file
1
tests/specs/run/sloppy_imports/a.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class A {}
|
1
tests/specs/run/sloppy_imports/b.js
Normal file
1
tests/specs/run/sloppy_imports/b.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class B {}
|
1
tests/specs/run/sloppy_imports/c.mts
Normal file
1
tests/specs/run/sloppy_imports/c.mts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class C {}
|
1
tests/specs/run/sloppy_imports/d.mjs
Normal file
1
tests/specs/run/sloppy_imports/d.mjs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class D {}
|
1
tests/specs/run/sloppy_imports/dir/index.tsx
Normal file
1
tests/specs/run/sloppy_imports/dir/index.tsx
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class G {}
|
1
tests/specs/run/sloppy_imports/e.tsx
Normal file
1
tests/specs/run/sloppy_imports/e.tsx
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class E {}
|
1
tests/specs/run/sloppy_imports/f.jsx
Normal file
1
tests/specs/run/sloppy_imports/f.jsx
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export class F {}
|
16
tests/specs/run/sloppy_imports/main.ts
Normal file
16
tests/specs/run/sloppy_imports/main.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import * as a from "./a.js";
|
||||||
|
import * as b from "./b";
|
||||||
|
import * as c from "./c";
|
||||||
|
import * as d from "./d";
|
||||||
|
import * as e from "./e";
|
||||||
|
import * as e2 from "./e.js";
|
||||||
|
import * as f from "./f";
|
||||||
|
import * as g from "./dir";
|
||||||
|
console.log(a.A);
|
||||||
|
console.log(b.B);
|
||||||
|
console.log(c.C);
|
||||||
|
console.log(d.D);
|
||||||
|
console.log(e.E);
|
||||||
|
console.log(e2.E);
|
||||||
|
console.log(f.F);
|
||||||
|
console.log(g.G);
|
2
tests/specs/run/sloppy_imports/no_sloppy.out
Normal file
2
tests/specs/run/sloppy_imports/no_sloppy.out
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
error: Module not found "file:///[WILDCARD]/a.js". Maybe change the extension to '.ts' or run with --unstable-sloppy-imports
|
||||||
|
at file:///[WILDLINE]/main.ts:1:20
|
8
tests/specs/run/sloppy_imports/sloppy.out
Normal file
8
tests/specs/run/sloppy_imports/sloppy.out
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[class A]
|
||||||
|
[class B]
|
||||||
|
[class C]
|
||||||
|
[class D]
|
||||||
|
[class E]
|
||||||
|
[class E]
|
||||||
|
[class F]
|
||||||
|
[class G]
|
|
@ -1,5 +1,4 @@
|
||||||
Task start deno run index.js
|
Task start deno run index.js
|
||||||
Warning Sloppy imports are not recommended and have a negative impact on performance.
|
|
||||||
Warning 'abc' isn't a valid unstable feature
|
Warning 'abc' isn't a valid unstable feature
|
||||||
Warning 'cba' isn't a valid unstable feature
|
Warning 'cba' isn't a valid unstable feature
|
||||||
Hello unstable features
|
Hello unstable features
|
||||||
|
|
|
@ -1230,6 +1230,16 @@ impl CollectedDiagnostics {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn for_file(&self, specifier: &Url) -> Vec<lsp::Diagnostic> {
|
||||||
|
self
|
||||||
|
.all_messages()
|
||||||
|
.iter()
|
||||||
|
.filter(|p| p.uri == *specifier)
|
||||||
|
.flat_map(|p| p.diagnostics.iter())
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets the messages that the editor will see after all the publishes.
|
/// Gets the messages that the editor will see after all the publishes.
|
||||||
pub fn all_messages(&self) -> Vec<lsp::PublishDiagnosticsParams> {
|
pub fn all_messages(&self) -> Vec<lsp::PublishDiagnosticsParams> {
|
||||||
self.0.clone()
|
self.0.clone()
|
||||||
|
@ -1245,7 +1255,7 @@ impl CollectedDiagnostics {
|
||||||
.find(|p| {
|
.find(|p| {
|
||||||
p.diagnostics
|
p.diagnostics
|
||||||
.iter()
|
.iter()
|
||||||
.any(|d| d.source == Some(source.to_string()))
|
.any(|d| d.source.as_deref() == Some(source))
|
||||||
})
|
})
|
||||||
.map(ToOwned::to_owned)
|
.map(ToOwned::to_owned)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
Loading…
Reference in a new issue