mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -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]]
|
||||
name = "deno_lint"
|
||||
version = "0.60.1"
|
||||
version = "0.61.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "598de34cdfb2a8ed335d8f2e33a75249445a9f81c8092a069fc562c2d5cdb9b6"
|
||||
checksum = "d127c05c87cb0fa2a59ad9bc70084f06731a5117c14888253269b6e921cfaef1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"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_emit = "=0.43.1"
|
||||
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_npm = "=0.21.4"
|
||||
deno_package_json.workspace = true
|
||||
|
|
|
@ -45,6 +45,7 @@ use crate::resolver::SloppyImportsResolver;
|
|||
use crate::standalone::DenoCompileBinaryWriter;
|
||||
use crate::tools::check::TypeChecker;
|
||||
use crate::tools::coverage::CoverageCollector;
|
||||
use crate::tools::lint::LintRuleProvider;
|
||||
use crate::tools::run::hmr::HmrRunner;
|
||||
use crate::util::file_watcher::WatcherCommunicator;
|
||||
use crate::util::fs::canonicalize_path_maybe_not_exists;
|
||||
|
@ -179,6 +180,7 @@ struct CliFactoryServices {
|
|||
node_code_translator: Deferred<Arc<CliNodeCodeTranslator>>,
|
||||
node_resolver: Deferred<Arc<NodeResolver>>,
|
||||
npm_resolver: Deferred<Arc<dyn CliNpmResolver>>,
|
||||
sloppy_imports_resolver: Deferred<Option<Arc<SloppyImportsResolver>>>,
|
||||
text_only_progress_bar: Deferred<ProgressBar>,
|
||||
type_checker: Deferred<Arc<TypeChecker>>,
|
||||
cjs_resolutions: Deferred<Arc<CjsResolutionStore>>,
|
||||
|
@ -397,6 +399,23 @@ impl CliFactory {
|
|||
.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(
|
||||
&self,
|
||||
) -> Result<&Arc<WorkspaceResolver>, AnyError> {
|
||||
|
@ -440,11 +459,7 @@ impl CliFactory {
|
|||
async {
|
||||
let cli_options = self.cli_options()?;
|
||||
Ok(Arc::new(CliGraphResolver::new(CliGraphResolverOptions {
|
||||
sloppy_imports_resolver: if cli_options.unstable_sloppy_imports() {
|
||||
Some(SloppyImportsResolver::new(self.fs().clone()))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
sloppy_imports_resolver: self.sloppy_imports_resolver()?.cloned(),
|
||||
node_resolver: Some(self.cli_node_resolver().await?.clone()),
|
||||
npm_resolver: if cli_options.no_npm() {
|
||||
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> {
|
||||
self
|
||||
.services
|
||||
|
|
|
@ -750,8 +750,8 @@ fn enhanced_sloppy_imports_error_message(
|
|||
ModuleError::LoadingErr(specifier, _, ModuleLoadError::Loader(_)) // ex. "Is a directory" error
|
||||
| ModuleError::Missing(specifier, _) => {
|
||||
let additional_message = SloppyImportsResolver::new(fs.clone())
|
||||
.resolve(specifier, ResolutionMode::Execution)
|
||||
.as_suggestion_message()?;
|
||||
.resolve(specifier, ResolutionMode::Execution)?
|
||||
.as_suggestion_message();
|
||||
Some(format!(
|
||||
"{} {} or run with --unstable-sloppy-imports",
|
||||
error,
|
||||
|
|
|
@ -8,8 +8,8 @@ use super::resolver::LspResolver;
|
|||
use super::tsc;
|
||||
|
||||
use crate::args::jsr_url;
|
||||
use crate::tools::lint::create_linter;
|
||||
use deno_lint::linter::LintConfig;
|
||||
use crate::tools::lint::CliLinter;
|
||||
use deno_lint::diagnostic::LintDiagnosticRange;
|
||||
use deno_runtime::fs_util::specifier_to_file_path;
|
||||
|
||||
use deno_ast::SourceRange;
|
||||
|
@ -23,8 +23,6 @@ use deno_core::serde::Serialize;
|
|||
use deno_core::serde_json;
|
||||
use deno_core::serde_json::json;
|
||||
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::PathClean;
|
||||
use deno_semver::jsr::JsrPackageNvReference;
|
||||
|
@ -149,8 +147,10 @@ impl Reference {
|
|||
}
|
||||
}
|
||||
|
||||
fn as_lsp_range_from_diagnostic(diagnostic: &LintDiagnostic) -> Range {
|
||||
as_lsp_range(diagnostic.range, &diagnostic.text_info)
|
||||
fn as_lsp_range_from_lint_diagnostic(
|
||||
diagnostic_range: &LintDiagnosticRange,
|
||||
) -> Range {
|
||||
as_lsp_range(diagnostic_range.range, &diagnostic_range.text_info)
|
||||
}
|
||||
|
||||
fn as_lsp_range(
|
||||
|
@ -173,37 +173,39 @@ fn as_lsp_range(
|
|||
|
||||
pub fn get_lint_references(
|
||||
parsed_source: &deno_ast::ParsedSource,
|
||||
lint_rules: Vec<&'static dyn LintRule>,
|
||||
lint_config: LintConfig,
|
||||
linter: &CliLinter,
|
||||
) -> Result<Vec<Reference>, AnyError> {
|
||||
let linter = create_linter(lint_rules);
|
||||
let lint_diagnostics = linter.lint_with_ast(parsed_source, lint_config);
|
||||
let lint_diagnostics = linter.lint_with_ast(parsed_source);
|
||||
|
||||
Ok(
|
||||
lint_diagnostics
|
||||
.into_iter()
|
||||
.map(|d| Reference {
|
||||
range: as_lsp_range_from_diagnostic(&d),
|
||||
category: Category::Lint {
|
||||
message: d.message,
|
||||
code: d.code,
|
||||
hint: d.hint,
|
||||
quick_fixes: d
|
||||
.fixes
|
||||
.into_iter()
|
||||
.map(|f| DataQuickFix {
|
||||
description: f.description.to_string(),
|
||||
changes: f
|
||||
.changes
|
||||
.into_iter()
|
||||
.map(|change| DataQuickFixChange {
|
||||
range: as_lsp_range(change.range, &d.text_info),
|
||||
new_text: change.new_text.to_string(),
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
.filter_map(|d| {
|
||||
let range = d.range.as_ref()?;
|
||||
Some(Reference {
|
||||
range: as_lsp_range_from_lint_diagnostic(range),
|
||||
category: Category::Lint {
|
||||
message: d.details.message,
|
||||
code: d.details.code.to_string(),
|
||||
hint: d.details.hint,
|
||||
quick_fixes: d
|
||||
.details
|
||||
.fixes
|
||||
.into_iter()
|
||||
.map(|f| DataQuickFix {
|
||||
description: f.description.to_string(),
|
||||
changes: f
|
||||
.changes
|
||||
.into_iter()
|
||||
.map(|change| DataQuickFixChange {
|
||||
range: as_lsp_range(change.range, &range.text_info),
|
||||
new_text: change.new_text.to_string(),
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
|
|
|
@ -52,12 +52,16 @@ use crate::args::discover_npmrc_from_workspace;
|
|||
use crate::args::has_flag_env_var;
|
||||
use crate::args::CliLockfile;
|
||||
use crate::args::ConfigFile;
|
||||
use crate::args::LintFlags;
|
||||
use crate::args::LintOptions;
|
||||
use crate::args::DENO_FUTURE;
|
||||
use crate::cache::FastInsecureHasher;
|
||||
use crate::file_fetcher::FileFetcher;
|
||||
use crate::lsp::logging::lsp_warn;
|
||||
use crate::tools::lint::get_configured_rules;
|
||||
use crate::tools::lint::ConfiguredRules;
|
||||
use crate::resolver::SloppyImportsResolver;
|
||||
use crate::tools::lint::CliLinter;
|
||||
use crate::tools::lint::CliLinterOptions;
|
||||
use crate::tools::lint::LintRuleProvider;
|
||||
use crate::util::fs::canonicalize_path_maybe_not_exists;
|
||||
|
||||
pub const SETTINGS_SECTION: &str = "deno";
|
||||
|
@ -1116,8 +1120,7 @@ pub struct ConfigData {
|
|||
pub lint_config: Arc<LintConfig>,
|
||||
pub test_config: Arc<TestConfig>,
|
||||
pub exclude_files: Arc<PathOrPatternSet>,
|
||||
pub deno_lint_config: DenoLintConfig,
|
||||
pub lint_rules: Arc<ConfiguredRules>,
|
||||
pub linter: Arc<CliLinter>,
|
||||
pub ts_config: Arc<LspTsConfig>,
|
||||
pub byonm: bool,
|
||||
pub node_modules_dir: Option<PathBuf>,
|
||||
|
@ -1125,6 +1128,7 @@ pub struct ConfigData {
|
|||
pub lockfile: Option<Arc<CliLockfile>>,
|
||||
pub npmrc: Option<Arc<ResolvedNpmRc>>,
|
||||
pub resolver: Arc<WorkspaceResolver>,
|
||||
pub sloppy_imports_resolver: Option<Arc<SloppyImportsResolver>>,
|
||||
pub import_map_from_settings: Option<ModuleSpecifier>,
|
||||
watched_files: HashMap<ModuleSpecifier, ConfigWatchedFileType>,
|
||||
}
|
||||
|
@ -1310,10 +1314,7 @@ impl ConfigData {
|
|||
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(
|
||||
member_dir
|
||||
.to_test_config(FilePatterns::new_with_base(member_dir.dir_path()))
|
||||
|
@ -1532,16 +1533,38 @@ impl ConfigData {
|
|||
.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 {
|
||||
scope,
|
||||
member_dir,
|
||||
resolver: Arc::new(resolver),
|
||||
resolver,
|
||||
sloppy_imports_resolver,
|
||||
fmt_config,
|
||||
lint_config,
|
||||
test_config,
|
||||
deno_lint_config,
|
||||
lint_rules,
|
||||
linter,
|
||||
exclude_files,
|
||||
ts_config: Arc::new(ts_config),
|
||||
byonm,
|
||||
|
|
|
@ -20,6 +20,9 @@ use crate::graph_util::enhanced_resolution_error_message;
|
|||
use crate::lsp::lsp_custom::DiagnosticBatchNotificationParams;
|
||||
use crate::resolver::SloppyImportsResolution;
|
||||
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 deno_ast::MediaType;
|
||||
|
@ -40,8 +43,6 @@ use deno_graph::source::ResolveError;
|
|||
use deno_graph::Resolution;
|
||||
use deno_graph::ResolutionError;
|
||||
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_node;
|
||||
use deno_runtime::tokio_util::create_basic_runtime;
|
||||
|
@ -817,25 +818,25 @@ fn generate_lint_diagnostics(
|
|||
continue;
|
||||
}
|
||||
let version = document.maybe_lsp_version();
|
||||
let (lint_config, deno_lint_config, lint_rules) = config
|
||||
let (lint_config, linter) = config
|
||||
.tree
|
||||
.scope_for_specifier(specifier)
|
||||
.and_then(|s| config_data_by_scope.get(s))
|
||||
.map(|d| {
|
||||
(
|
||||
d.lint_config.clone(),
|
||||
d.deno_lint_config.clone(),
|
||||
d.lint_rules.clone(),
|
||||
)
|
||||
})
|
||||
.map(|d| (d.lint_config.clone(), d.linter.clone()))
|
||||
.unwrap_or_else(|| {
|
||||
(
|
||||
Arc::new(LintConfig::new_with_base(PathBuf::from("/"))),
|
||||
DenoLintConfig {
|
||||
default_jsx_factory: None,
|
||||
default_jsx_fragment_factory: None,
|
||||
},
|
||||
Arc::default(),
|
||||
Arc::new(CliLinter::new(CliLinterOptions {
|
||||
configured_rules: {
|
||||
let lint_rule_provider = LintRuleProvider::new(None, None);
|
||||
lint_rule_provider.resolve_lint_rules(Default::default(), None)
|
||||
},
|
||||
fix: false,
|
||||
deno_lint_config: deno_lint::linter::LintConfig {
|
||||
default_jsx_factory: None,
|
||||
default_jsx_fragment_factory: None,
|
||||
},
|
||||
})),
|
||||
)
|
||||
});
|
||||
diagnostics_vec.push(DiagnosticRecord {
|
||||
|
@ -845,8 +846,7 @@ fn generate_lint_diagnostics(
|
|||
diagnostics: generate_document_lint_diagnostics(
|
||||
&document,
|
||||
&lint_config,
|
||||
deno_lint_config,
|
||||
lint_rules.rules.clone(),
|
||||
&linter,
|
||||
),
|
||||
},
|
||||
});
|
||||
|
@ -857,19 +857,16 @@ fn generate_lint_diagnostics(
|
|||
fn generate_document_lint_diagnostics(
|
||||
document: &Document,
|
||||
lint_config: &LintConfig,
|
||||
deno_lint_config: DenoLintConfig,
|
||||
lint_rules: Vec<&'static dyn LintRule>,
|
||||
linter: &CliLinter,
|
||||
) -> Vec<lsp::Diagnostic> {
|
||||
if !lint_config.files.matches_specifier(document.specifier()) {
|
||||
return Vec::new();
|
||||
}
|
||||
match document.maybe_parsed_source() {
|
||||
Some(Ok(parsed_source)) => {
|
||||
if let Ok(references) = analysis::get_lint_references(
|
||||
parsed_source,
|
||||
lint_rules,
|
||||
deno_lint_config,
|
||||
) {
|
||||
if let Ok(references) =
|
||||
analysis::get_lint_references(parsed_source, linter)
|
||||
{
|
||||
references
|
||||
.into_iter()
|
||||
.map(|r| r.to_diagnostic())
|
||||
|
@ -1237,16 +1234,14 @@ impl DenoDiagnostic {
|
|||
pub fn to_lsp_diagnostic(&self, range: &lsp::Range) -> lsp::Diagnostic {
|
||||
fn no_local_message(
|
||||
specifier: &ModuleSpecifier,
|
||||
sloppy_resolution: SloppyImportsResolution,
|
||||
maybe_sloppy_resolution: Option<&SloppyImportsResolution>,
|
||||
) -> String {
|
||||
let mut message = format!(
|
||||
"Unable to load a local module: {}\n",
|
||||
to_percent_decoded_str(specifier.as_ref())
|
||||
);
|
||||
if let Some(additional_message) =
|
||||
sloppy_resolution.as_suggestion_message()
|
||||
{
|
||||
message.push_str(&additional_message);
|
||||
if let Some(res) = maybe_sloppy_resolution {
|
||||
message.push_str(&res.as_suggestion_message());
|
||||
message.push('.');
|
||||
} else {
|
||||
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::NoCacheNpm(pkg_req, specifier) => (lsp::DiagnosticSeverity::ERROR, format!("Uncached or missing npm package: {}", pkg_req), Some(json!({ "specifier": specifier }))),
|
||||
Self::NoLocal(specifier) => {
|
||||
let 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 maybe_sloppy_resolution = SloppyImportsResolver::new(Arc::new(deno_fs::RealFs)).resolve(specifier, ResolutionMode::Execution);
|
||||
let data = maybe_sloppy_resolution.as_ref().map(|res| {
|
||||
json!({
|
||||
"specifier": specifier,
|
||||
"to": sloppy_resolution.as_specifier(),
|
||||
"message": message,
|
||||
"to": res.as_specifier(),
|
||||
"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::ResolutionError(err) => {
|
||||
|
|
|
@ -21,7 +21,6 @@ use crate::resolver::CjsResolutionStore;
|
|||
use crate::resolver::CliGraphResolver;
|
||||
use crate::resolver::CliGraphResolverOptions;
|
||||
use crate::resolver::CliNodeResolver;
|
||||
use crate::resolver::SloppyImportsResolver;
|
||||
use crate::resolver::WorkerCliNpmGraphResolver;
|
||||
use crate::util::progress_bar::ProgressBar;
|
||||
use crate::util::progress_bar::ProgressBarStyle;
|
||||
|
@ -514,8 +513,6 @@ fn create_graph_resolver(
|
|||
node_resolver: Option<&Arc<CliNodeResolver>>,
|
||||
) -> Arc<CliGraphResolver> {
|
||||
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 {
|
||||
node_resolver: node_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()),
|
||||
bare_node_builtins_enabled: workspace
|
||||
.is_some_and(|workspace| workspace.has_unstable("bare-node-builtins")),
|
||||
sloppy_imports_resolver: unstable_sloppy_imports.then(|| {
|
||||
SloppyImportsResolver::new_without_stat_cache(Arc::new(deno_fs::RealFs))
|
||||
}),
|
||||
sloppy_imports_resolver: config_data
|
||||
.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::DENO_DISABLE_PEDANTIC_NODE_WARNINGS;
|
||||
use crate::colors;
|
||||
use crate::node::CliNodeCodeTranslator;
|
||||
use crate::npm::CliNpmResolver;
|
||||
use crate::npm::InnerCliNpmResolverRef;
|
||||
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 code: ModuleSourceCode,
|
||||
pub found_url: ModuleSpecifier,
|
||||
|
@ -268,8 +258,8 @@ impl CliNodeResolver {
|
|||
|
||||
pub fn handle_if_in_node_modules(
|
||||
&self,
|
||||
specifier: ModuleSpecifier,
|
||||
) -> Result<ModuleSpecifier, AnyError> {
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> Result<Option<ModuleSpecifier>, AnyError> {
|
||||
// skip canonicalizing if we definitely know it's unnecessary
|
||||
if specifier.scheme() == "file"
|
||||
&& 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
|
||||
// resolution.
|
||||
let specifier =
|
||||
crate::node::resolve_specifier_into_node_modules(&specifier);
|
||||
crate::node::resolve_specifier_into_node_modules(specifier);
|
||||
if self.in_npm_package(&specifier) {
|
||||
let resolution =
|
||||
self.node_resolver.url_to_node_resolution(specifier)?;
|
||||
if let NodeResolution::CommonJs(specifier) = &resolution {
|
||||
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(
|
||||
|
@ -436,7 +426,7 @@ impl CjsResolutionStore {
|
|||
pub struct CliGraphResolver {
|
||||
node_resolver: Option<Arc<CliNodeResolver>>,
|
||||
npm_resolver: Option<Arc<dyn CliNpmResolver>>,
|
||||
sloppy_imports_resolver: Option<SloppyImportsResolver>,
|
||||
sloppy_imports_resolver: Option<Arc<SloppyImportsResolver>>,
|
||||
workspace_resolver: Arc<WorkspaceResolver>,
|
||||
maybe_default_jsx_import_source: Option<String>,
|
||||
maybe_default_jsx_import_source_types: Option<String>,
|
||||
|
@ -449,7 +439,7 @@ pub struct CliGraphResolver {
|
|||
pub struct CliGraphResolverOptions<'a> {
|
||||
pub node_resolver: Option<Arc<CliNodeResolver>>,
|
||||
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 bare_node_builtins_enabled: bool,
|
||||
pub maybe_jsx_import_source_config: Option<JsxImportSourceConfig>,
|
||||
|
@ -552,12 +542,12 @@ impl Resolver for CliGraphResolver {
|
|||
| MappedResolution::ImportMap(specifier) => {
|
||||
// do sloppy imports resolution if enabled
|
||||
if let Some(sloppy_imports_resolver) = &self.sloppy_imports_resolver {
|
||||
Ok(sloppy_imports_resolve(
|
||||
sloppy_imports_resolver,
|
||||
specifier,
|
||||
referrer_range,
|
||||
mode,
|
||||
))
|
||||
Ok(
|
||||
sloppy_imports_resolver
|
||||
.resolve(&specifier, mode)
|
||||
.map(|s| s.into_specifier())
|
||||
.unwrap_or(specifier),
|
||||
)
|
||||
} else {
|
||||
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) => {
|
||||
// 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)]
|
||||
pub struct WorkerCliNpmGraphResolver<'a> {
|
||||
npm_resolver: Option<&'a Arc<dyn CliNpmResolver>>,
|
||||
|
@ -902,10 +836,8 @@ impl SloppyImportsFsEntry {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum SloppyImportsResolution<'a> {
|
||||
/// No sloppy resolution was found.
|
||||
None(&'a ModuleSpecifier),
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SloppyImportsResolution {
|
||||
/// Ex. `./file.js` to `./file.ts`
|
||||
JsToTs(ModuleSpecifier),
|
||||
/// Ex. `./file` to `./file.ts`
|
||||
|
@ -914,55 +846,46 @@ pub enum SloppyImportsResolution<'a> {
|
|||
Directory(ModuleSpecifier),
|
||||
}
|
||||
|
||||
impl<'a> SloppyImportsResolution<'a> {
|
||||
impl SloppyImportsResolution {
|
||||
pub fn as_specifier(&self) -> &ModuleSpecifier {
|
||||
match self {
|
||||
Self::None(specifier) => specifier,
|
||||
Self::JsToTs(specifier) => specifier,
|
||||
Self::NoExtension(specifier) => specifier,
|
||||
Self::Directory(specifier) => specifier,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_specifier(self) -> Cow<'a, ModuleSpecifier> {
|
||||
pub fn into_specifier(self) -> ModuleSpecifier {
|
||||
match self {
|
||||
Self::None(specifier) => Cow::Borrowed(specifier),
|
||||
Self::JsToTs(specifier) => Cow::Owned(specifier),
|
||||
Self::NoExtension(specifier) => Cow::Owned(specifier),
|
||||
Self::Directory(specifier) => Cow::Owned(specifier),
|
||||
Self::JsToTs(specifier) => specifier,
|
||||
Self::NoExtension(specifier) => specifier,
|
||||
Self::Directory(specifier) => specifier,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_suggestion_message(&self) -> Option<String> {
|
||||
Some(format!("Maybe {}", self.as_base_message()?))
|
||||
pub fn as_suggestion_message(&self) -> String {
|
||||
format!("Maybe {}", self.as_base_message())
|
||||
}
|
||||
|
||||
pub fn as_lsp_quick_fix_message(&self) -> Option<String> {
|
||||
let message = self.as_base_message()?;
|
||||
pub fn as_quick_fix_message(&self) -> String {
|
||||
let message = self.as_base_message();
|
||||
let mut chars = message.chars();
|
||||
Some(format!(
|
||||
format!(
|
||||
"{}{}.",
|
||||
chars.next().unwrap().to_uppercase(),
|
||||
chars.as_str()
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
fn as_base_message(&self) -> Option<String> {
|
||||
fn as_base_message(&self) -> String {
|
||||
match self {
|
||||
SloppyImportsResolution::None(_) => None,
|
||||
SloppyImportsResolution::JsToTs(specifier) => {
|
||||
let media_type = MediaType::from_specifier(specifier);
|
||||
Some(format!(
|
||||
"change the extension to '{}'",
|
||||
media_type.as_ts_extension()
|
||||
))
|
||||
format!("change the extension to '{}'", media_type.as_ts_extension())
|
||||
}
|
||||
SloppyImportsResolution::NoExtension(specifier) => {
|
||||
let media_type = MediaType::from_specifier(specifier);
|
||||
Some(format!(
|
||||
"add a '{}' extension",
|
||||
media_type.as_ts_extension()
|
||||
))
|
||||
format!("add a '{}' extension", media_type.as_ts_extension())
|
||||
}
|
||||
SloppyImportsResolution::Directory(specifier) => {
|
||||
let file_name = specifier
|
||||
|
@ -970,10 +893,7 @@ impl<'a> SloppyImportsResolution<'a> {
|
|||
.rsplit_once('/')
|
||||
.map(|(_, file_name)| file_name)
|
||||
.unwrap_or(specifier.path());
|
||||
Some(format!(
|
||||
"specify path to '{}' file in directory instead",
|
||||
file_name
|
||||
))
|
||||
format!("specify path to '{}' file in directory instead", file_name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -997,11 +917,11 @@ impl SloppyImportsResolver {
|
|||
Self { fs, cache: None }
|
||||
}
|
||||
|
||||
pub fn resolve<'a>(
|
||||
pub fn resolve(
|
||||
&self,
|
||||
specifier: &'a ModuleSpecifier,
|
||||
specifier: &ModuleSpecifier,
|
||||
mode: ResolutionMode,
|
||||
) -> SloppyImportsResolution<'a> {
|
||||
) -> Option<SloppyImportsResolution> {
|
||||
fn path_without_ext(
|
||||
path: &Path,
|
||||
media_type: MediaType,
|
||||
|
@ -1017,11 +937,13 @@ impl SloppyImportsResolver {
|
|||
|
||||
fn media_types_to_paths(
|
||||
path_no_ext: &str,
|
||||
original_media_type: MediaType,
|
||||
probe_media_type_types: Vec<MediaType>,
|
||||
reason: SloppyImportsResolutionReason,
|
||||
) -> Vec<(PathBuf, SloppyImportsResolutionReason)> {
|
||||
probe_media_type_types
|
||||
.into_iter()
|
||||
.filter(|media_type| *media_type != original_media_type)
|
||||
.map(|media_type| {
|
||||
(
|
||||
PathBuf::from(format!(
|
||||
|
@ -1036,12 +958,10 @@ impl SloppyImportsResolver {
|
|||
}
|
||||
|
||||
if specifier.scheme() != "file" {
|
||||
return SloppyImportsResolution::None(specifier);
|
||||
return None;
|
||||
}
|
||||
|
||||
let Ok(path) = specifier_to_file_path(specifier) else {
|
||||
return SloppyImportsResolution::None(specifier);
|
||||
};
|
||||
let path = specifier_to_file_path(specifier).ok()?;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum SloppyImportsResolutionReason {
|
||||
|
@ -1066,18 +986,17 @@ impl SloppyImportsResolver {
|
|||
MediaType::Cjs => {
|
||||
vec![MediaType::Dcts, MediaType::Dts, MediaType::Cjs]
|
||||
}
|
||||
_ => return SloppyImportsResolution::None(specifier),
|
||||
};
|
||||
let Some(path_no_ext) = path_without_ext(&path, media_type) else {
|
||||
return SloppyImportsResolution::None(specifier);
|
||||
_ => return None,
|
||||
};
|
||||
let path_no_ext = path_without_ext(&path, media_type)?;
|
||||
media_types_to_paths(
|
||||
&path_no_ext,
|
||||
media_type,
|
||||
probe_media_type_types,
|
||||
SloppyImportsResolutionReason::JsToTs,
|
||||
)
|
||||
} else {
|
||||
return SloppyImportsResolution::None(specifier);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
entry @ None | entry @ Some(SloppyImportsFsEntry::Dir) => {
|
||||
|
@ -1121,7 +1040,7 @@ impl SloppyImportsResolver {
|
|||
| MediaType::Wasm
|
||||
| MediaType::TsBuildInfo
|
||||
| MediaType::SourceMap => {
|
||||
return SloppyImportsResolution::None(specifier)
|
||||
return None;
|
||||
}
|
||||
MediaType::Unknown => (
|
||||
if mode.is_types() {
|
||||
|
@ -1152,6 +1071,7 @@ impl SloppyImportsResolver {
|
|||
let mut probe_paths = match path_without_ext(&path, media_type) {
|
||||
Some(path_no_ext) => media_types_to_paths(
|
||||
&path_no_ext,
|
||||
media_type,
|
||||
probe_media_type_types.0,
|
||||
probe_media_type_types.1,
|
||||
),
|
||||
|
@ -1222,7 +1142,7 @@ impl SloppyImportsResolver {
|
|||
}
|
||||
}
|
||||
if probe_paths.is_empty() {
|
||||
return SloppyImportsResolution::None(specifier);
|
||||
return None;
|
||||
}
|
||||
probe_paths
|
||||
}
|
||||
|
@ -1233,20 +1153,20 @@ impl SloppyImportsResolver {
|
|||
if let Ok(specifier) = ModuleSpecifier::from_file_path(probe_path) {
|
||||
match reason {
|
||||
SloppyImportsResolutionReason::JsToTs => {
|
||||
return SloppyImportsResolution::JsToTs(specifier)
|
||||
return Some(SloppyImportsResolution::JsToTs(specifier));
|
||||
}
|
||||
SloppyImportsResolutionReason::NoExtension => {
|
||||
return SloppyImportsResolution::NoExtension(specifier)
|
||||
return Some(SloppyImportsResolution::NoExtension(specifier));
|
||||
}
|
||||
SloppyImportsResolutionReason::Directory => {
|
||||
return SloppyImportsResolution::Directory(specifier)
|
||||
return Some(SloppyImportsResolution::Directory(specifier));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SloppyImportsResolution::None(specifier)
|
||||
None
|
||||
}
|
||||
|
||||
fn stat_sync(&self, path: &Path) -> Option<SloppyImportsFsEntry> {
|
||||
|
@ -1276,9 +1196,22 @@ mod test {
|
|||
|
||||
#[test]
|
||||
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))
|
||||
.resolve(specifier, ResolutionMode::Execution)
|
||||
.resolve(specifier, mode)
|
||||
}
|
||||
|
||||
let context = TestContext::default();
|
||||
|
@ -1288,11 +1221,7 @@ mod test {
|
|||
for (ext_from, ext_to) in [("js", "ts"), ("js", "tsx"), ("mjs", "mts")] {
|
||||
let ts_file = temp_dir.join(format!("file.{}", ext_to));
|
||||
ts_file.write("");
|
||||
let ts_file_uri = ts_file.uri_file();
|
||||
assert_eq!(
|
||||
resolve(&ts_file.uri_file()),
|
||||
SloppyImportsResolution::None(&ts_file_uri),
|
||||
);
|
||||
assert_eq!(resolve(&ts_file.uri_file()), None);
|
||||
assert_eq!(
|
||||
resolve(
|
||||
&temp_dir
|
||||
|
@ -1300,7 +1229,7 @@ mod test {
|
|||
.join(&format!("file.{}", ext_from))
|
||||
.unwrap()
|
||||
),
|
||||
SloppyImportsResolution::JsToTs(ts_file.uri_file()),
|
||||
Some(SloppyImportsResolution::JsToTs(ts_file.uri_file())),
|
||||
);
|
||||
ts_file.remove_file();
|
||||
}
|
||||
|
@ -1316,7 +1245,7 @@ mod test {
|
|||
.join("file") // no ext
|
||||
.unwrap()
|
||||
),
|
||||
SloppyImportsResolution::NoExtension(file.uri_file()),
|
||||
Some(SloppyImportsResolution::NoExtension(file.uri_file()))
|
||||
);
|
||||
file.remove_file();
|
||||
}
|
||||
|
@ -1327,11 +1256,15 @@ mod test {
|
|||
ts_file.write("");
|
||||
let js_file = temp_dir.join("file.js");
|
||||
js_file.write("");
|
||||
let js_file_uri = js_file.uri_file();
|
||||
assert_eq!(
|
||||
resolve(&js_file.uri_file()),
|
||||
SloppyImportsResolution::None(&js_file_uri),
|
||||
);
|
||||
assert_eq!(resolve(&js_file.uri_file()), None);
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -1342,7 +1275,7 @@ mod test {
|
|||
index_file.write("");
|
||||
assert_eq!(
|
||||
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("");
|
||||
assert_eq!(
|
||||
resolve(&api_dir.uri_file()),
|
||||
SloppyImportsResolution::NoExtension(api_file.uri_file()),
|
||||
Some(SloppyImportsResolution::NoExtension(api_file.uri_file())),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
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
|
||||
assert_eq!(
|
||||
SloppyImportsResolution::Directory(
|
||||
ModuleSpecifier::parse("file:///dir/index.js").unwrap()
|
||||
)
|
||||
.as_suggestion_message()
|
||||
.unwrap(),
|
||||
.as_suggestion_message(),
|
||||
"Maybe specify path to 'index.js' file in directory instead"
|
||||
);
|
||||
// no ext
|
||||
|
@ -1383,8 +1309,7 @@ mod test {
|
|||
SloppyImportsResolution::NoExtension(
|
||||
ModuleSpecifier::parse("file:///dir/index.mjs").unwrap()
|
||||
)
|
||||
.as_suggestion_message()
|
||||
.unwrap(),
|
||||
.as_suggestion_message(),
|
||||
"Maybe add a '.mjs' extension"
|
||||
);
|
||||
// js to ts
|
||||
|
@ -1392,8 +1317,7 @@ mod test {
|
|||
SloppyImportsResolution::JsToTs(
|
||||
ModuleSpecifier::parse("file:///dir/index.mts").unwrap()
|
||||
)
|
||||
.as_suggestion_message()
|
||||
.unwrap(),
|
||||
.as_suggestion_message(),
|
||||
"Maybe change the extension to '.mts'"
|
||||
);
|
||||
}
|
||||
|
|
|
@ -241,10 +241,13 @@ impl ModuleLoader for EmbeddedModuleLoader {
|
|||
}
|
||||
}
|
||||
|
||||
self
|
||||
.shared
|
||||
.node_resolver
|
||||
.handle_if_in_node_modules(specifier)
|
||||
Ok(
|
||||
self
|
||||
.shared
|
||||
.node_resolver
|
||||
.handle_if_in_node_modules(&specifier)?
|
||||
.unwrap_or(specifier),
|
||||
)
|
||||
}
|
||||
Err(err)
|
||||
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
|
||||
//! [`deno_lint`](https://github.com/denoland/deno_lint).
|
||||
use deno_ast::diagnostics::Diagnostic;
|
||||
use deno_ast::MediaType;
|
||||
use deno_ast::ModuleSpecifier;
|
||||
use deno_ast::ParsedSource;
|
||||
use deno_ast::SourceRange;
|
||||
use deno_ast::SourceTextInfo;
|
||||
use deno_config::deno_json::ConfigFile;
|
||||
use deno_config::deno_json::LintRulesConfig;
|
||||
use deno_config::glob::FileCollector;
|
||||
use deno_config::glob::FilePatterns;
|
||||
use deno_config::workspace::WorkspaceDirectory;
|
||||
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::AnyError;
|
||||
use deno_core::futures::future::LocalBoxFuture;
|
||||
|
@ -23,19 +18,12 @@ use deno_core::parking_lot::Mutex;
|
|||
use deno_core::serde_json;
|
||||
use deno_core::unsync::future::LocalFutureExt;
|
||||
use deno_core::unsync::future::SharedLocal;
|
||||
use deno_graph::FastCheckDiagnostic;
|
||||
use deno_graph::ModuleGraph;
|
||||
use deno_lint::diagnostic::LintDiagnostic;
|
||||
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::info;
|
||||
use serde::Serialize;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
use std::io::stdin;
|
||||
|
@ -50,7 +38,6 @@ use crate::args::Flags;
|
|||
use crate::args::LintFlags;
|
||||
use crate::args::LintOptions;
|
||||
use crate::args::LintReporterKind;
|
||||
use crate::args::LintRulesConfig;
|
||||
use crate::args::WorkspaceLintOptions;
|
||||
use crate::cache::Caches;
|
||||
use crate::cache::IncrementalCache;
|
||||
|
@ -60,11 +47,17 @@ use crate::graph_util::ModuleGraphCreator;
|
|||
use crate::tools::fmt::run_parallelized;
|
||||
use crate::util::file_watcher;
|
||||
use crate::util::fs::canonicalize_path;
|
||||
use crate::util::fs::specifier_from_file_path;
|
||||
use crate::util::path::is_script_ext;
|
||||
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";
|
||||
|
||||
|
@ -120,6 +113,7 @@ pub async fn lint(
|
|||
|
||||
let mut linter = WorkspaceLinter::new(
|
||||
factory.caches()?.clone(),
|
||||
factory.lint_rule_provider().await?,
|
||||
factory.module_graph_creator().await?.clone(),
|
||||
cli_options.start_dir.clone(),
|
||||
&cli_options.resolve_workspace_lint_options(&lint_flags)?,
|
||||
|
@ -157,12 +151,15 @@ pub async fn lint(
|
|||
let lint_config = start_dir
|
||||
.to_lint_config(FilePatterns::new_with_base(start_dir.dir_path()))?;
|
||||
let lint_options = LintOptions::resolve(lint_config, &lint_flags);
|
||||
let lint_rules = get_config_rules_err_empty(
|
||||
lint_options.rules,
|
||||
start_dir.maybe_deno_json().map(|c| c.as_ref()),
|
||||
)?;
|
||||
let lint_rules = factory
|
||||
.lint_rule_provider()
|
||||
.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 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(
|
||||
&file_path.to_string_lossy(),
|
||||
r,
|
||||
|
@ -173,6 +170,7 @@ pub async fn lint(
|
|||
} else {
|
||||
let mut linter = WorkspaceLinter::new(
|
||||
factory.caches()?.clone(),
|
||||
factory.lint_rule_provider().await?,
|
||||
factory.module_graph_creator().await?.clone(),
|
||||
cli_options.start_dir.clone(),
|
||||
&workspace_lint_options,
|
||||
|
@ -234,6 +232,7 @@ type WorkspaceModuleGraphFuture =
|
|||
|
||||
struct WorkspaceLinter {
|
||||
caches: Arc<Caches>,
|
||||
lint_rule_provider: LintRuleProvider,
|
||||
module_graph_creator: Arc<ModuleGraphCreator>,
|
||||
workspace_dir: Arc<WorkspaceDirectory>,
|
||||
reporter_lock: Arc<Mutex<Box<dyn LintReporter + Send>>>,
|
||||
|
@ -245,6 +244,7 @@ struct WorkspaceLinter {
|
|||
impl WorkspaceLinter {
|
||||
pub fn new(
|
||||
caches: Arc<Caches>,
|
||||
lint_rule_provider: LintRuleProvider,
|
||||
module_graph_creator: Arc<ModuleGraphCreator>,
|
||||
workspace_dir: Arc<WorkspaceDirectory>,
|
||||
workspace_options: &WorkspaceLintOptions,
|
||||
|
@ -253,6 +253,7 @@ impl WorkspaceLinter {
|
|||
Arc::new(Mutex::new(create_reporter(workspace_options.reporter_kind)));
|
||||
Self {
|
||||
caches,
|
||||
lint_rule_provider,
|
||||
module_graph_creator,
|
||||
workspace_dir,
|
||||
reporter_lock,
|
||||
|
@ -271,18 +272,27 @@ impl WorkspaceLinter {
|
|||
) -> Result<(), AnyError> {
|
||||
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,
|
||||
member_dir.maybe_deno_json().map(|c| c.as_ref()),
|
||||
)?;
|
||||
let incremental_cache = Arc::new(IncrementalCache::new(
|
||||
self.caches.lint_incremental_cache_db(),
|
||||
&lint_rules.incremental_cache_state(),
|
||||
&paths,
|
||||
));
|
||||
let maybe_incremental_cache =
|
||||
lint_rules.incremental_cache_state().map(|state| {
|
||||
Arc::new(IncrementalCache::new(
|
||||
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);
|
||||
if lint_rules.no_slow_types {
|
||||
if linter.has_package_rules() {
|
||||
if self.workspace_module_graph.is_none() {
|
||||
let module_graph_creator = self.module_graph_creator.clone();
|
||||
let packages = self.workspace_dir.jsr_packages_for_publish();
|
||||
|
@ -304,6 +314,7 @@ impl WorkspaceLinter {
|
|||
if let Some(publish_config) = publish_config {
|
||||
let has_error = self.has_error.clone();
|
||||
let reporter_lock = self.reporter_lock.clone();
|
||||
let linter = linter.clone();
|
||||
let path_urls = paths
|
||||
.iter()
|
||||
.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)) {
|
||||
return Ok(()); // entrypoint is not specified, so skip
|
||||
}
|
||||
let diagnostics = no_slow_types::collect_no_slow_type_diagnostics(
|
||||
&export_urls,
|
||||
&graph,
|
||||
);
|
||||
let diagnostics = linter.lint_package(&graph, &export_urls);
|
||||
if !diagnostics.is_empty() {
|
||||
has_error.raise();
|
||||
let mut reporter = reporter_lock.lock();
|
||||
for diagnostic in &diagnostics {
|
||||
reporter
|
||||
.visit_diagnostic(LintOrCliDiagnostic::FastCheck(diagnostic));
|
||||
reporter.visit_diagnostic(diagnostic);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -339,11 +346,9 @@ impl WorkspaceLinter {
|
|||
|
||||
futures.push({
|
||||
let has_error = self.has_error.clone();
|
||||
let linter = create_linter(lint_rules.rules);
|
||||
let reporter_lock = self.reporter_lock.clone();
|
||||
let incremental_cache = incremental_cache.clone();
|
||||
let lint_config = lint_config.clone();
|
||||
let fix = lint_options.fix;
|
||||
let maybe_incremental_cache = maybe_incremental_cache.clone();
|
||||
let linter = linter.clone();
|
||||
async move {
|
||||
run_parallelized(paths, {
|
||||
move |file_path| {
|
||||
|
@ -351,19 +356,23 @@ impl WorkspaceLinter {
|
|||
deno_ast::strip_bom(fs::read_to_string(&file_path)?);
|
||||
|
||||
// don't bother rechecking this file if it didn't have any diagnostics before
|
||||
if incremental_cache.is_file_same(&file_path, &file_text) {
|
||||
return Ok(());
|
||||
if let Some(incremental_cache) = &maybe_incremental_cache {
|
||||
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 file_diagnostics.is_empty() {
|
||||
// update the incremental cache if there were no diagnostics
|
||||
incremental_cache.update_file(
|
||||
&file_path,
|
||||
// ensure the returned text is used here as it may have been modified via --fix
|
||||
file_source.text(),
|
||||
)
|
||||
if let Some(incremental_cache) = &maybe_incremental_cache {
|
||||
if file_diagnostics.is_empty() {
|
||||
// update the incremental cache if there were no diagnostics
|
||||
incremental_cache.update_file(
|
||||
&file_path,
|
||||
// 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()
|
||||
});
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
|
@ -410,11 +431,17 @@ fn collect_lint_files(
|
|||
|
||||
#[allow(clippy::print_stdout)]
|
||||
pub fn print_rules_list(json: bool, maybe_rules_tags: Option<Vec<String>>) {
|
||||
let lint_rules = if maybe_rules_tags.is_none() {
|
||||
rules::get_all_rules()
|
||||
} else {
|
||||
rules::get_filtered_rules(maybe_rules_tags, None, None)
|
||||
};
|
||||
let rule_provider = LintRuleProvider::new(None, None);
|
||||
let lint_rules = rule_provider
|
||||
.resolve_lint_rules(
|
||||
LintRulesConfig {
|
||||
tags: maybe_rules_tags.clone(),
|
||||
include: None,
|
||||
exclude: None,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.rules;
|
||||
|
||||
if json {
|
||||
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!(
|
||||
"{}",
|
||||
colors::gray(format!(
|
||||
" help: https://lint.deno.land/#{}",
|
||||
rule.code()
|
||||
))
|
||||
colors::gray(format!(" help: {}", rule.help_docs_url()))
|
||||
);
|
||||
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.
|
||||
/// Treats input as TypeScript.
|
||||
/// Compatible with `--json` flag.
|
||||
fn lint_stdin(
|
||||
file_path: &Path,
|
||||
lint_rules: Vec<&'static dyn LintRule>,
|
||||
configured_rules: ConfiguredRules,
|
||||
deno_lint_config: LintConfig,
|
||||
) -> Result<(ParsedSource, Vec<LintDiagnostic>), AnyError> {
|
||||
let mut source_code = String::new();
|
||||
|
@ -629,15 +489,14 @@ fn lint_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
|
||||
.lint_file(LintFileOptions {
|
||||
specifier: specifier_from_file_path(file_path)?,
|
||||
source_code: deno_ast::strip_bom(source_code),
|
||||
media_type: MediaType::TypeScript,
|
||||
config: deno_lint_config,
|
||||
})
|
||||
.lint_file(file_path, deno_ast::strip_bom(source_code))
|
||||
.map_err(AnyError::from)
|
||||
}
|
||||
|
||||
|
@ -656,11 +515,18 @@ fn handle_lint_result(
|
|||
}
|
||||
}
|
||||
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,
|
||||
});
|
||||
for d in &file_diagnostics {
|
||||
reporter.visit_diagnostic(LintOrCliDiagnostic::Lint(d));
|
||||
reporter.visit_diagnostic(d);
|
||||
}
|
||||
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 {
|
||||
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 close(&mut self, check_count: usize);
|
||||
}
|
||||
|
@ -789,12 +564,10 @@ impl PrettyLintReporter {
|
|||
}
|
||||
|
||||
impl LintReporter for PrettyLintReporter {
|
||||
fn visit_diagnostic(&mut self, d: LintOrCliDiagnostic) {
|
||||
fn visit_diagnostic(&mut self, d: &LintDiagnostic) {
|
||||
self.lint_count += 1;
|
||||
if let LintOrCliDiagnostic::Lint(d) = d {
|
||||
if !d.fixes.is_empty() {
|
||||
self.fixable_diagnostics += 1;
|
||||
}
|
||||
if !d.details.fixes.is_empty() {
|
||||
self.fixable_diagnostics += 1;
|
||||
}
|
||||
|
||||
log::error!("{}\n", d.display());
|
||||
|
@ -838,15 +611,17 @@ impl CompactLintReporter {
|
|||
}
|
||||
|
||||
impl LintReporter for CompactLintReporter {
|
||||
fn visit_diagnostic(&mut self, d: LintOrCliDiagnostic) {
|
||||
fn visit_diagnostic(&mut self, d: &LintDiagnostic) {
|
||||
self.lint_count += 1;
|
||||
|
||||
match d.range() {
|
||||
Some((text_info, range)) => {
|
||||
match &d.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);
|
||||
log::error!(
|
||||
"{}: line {}, col {} - {} ({})",
|
||||
d.specifier(),
|
||||
d.specifier,
|
||||
line_and_column.line_number,
|
||||
line_and_column.column_number,
|
||||
d.message(),
|
||||
|
@ -854,7 +629,7 @@ impl LintReporter for CompactLintReporter {
|
|||
)
|
||||
}
|
||||
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 {
|
||||
fn visit_diagnostic(&mut self, d: LintOrCliDiagnostic) {
|
||||
fn visit_diagnostic(&mut self, d: &LintDiagnostic) {
|
||||
self.diagnostics.push(JsonLintDiagnostic {
|
||||
filename: d.specifier().to_string(),
|
||||
range: d.range().map(|(text_info, range)| JsonLintDiagnosticRange {
|
||||
start: JsonDiagnosticLintPosition::new(
|
||||
range.start.as_byte_index(text_info.range().start),
|
||||
text_info.line_and_column_index(range.start),
|
||||
),
|
||||
end: JsonDiagnosticLintPosition::new(
|
||||
range.end.as_byte_index(text_info.range().start),
|
||||
text_info.line_and_column_index(range.end),
|
||||
),
|
||||
filename: d.specifier.to_string(),
|
||||
range: d.range.as_ref().map(|range| {
|
||||
let text_info = &range.text_info;
|
||||
let range = range.range;
|
||||
JsonLintDiagnosticRange {
|
||||
start: JsonDiagnosticLintPosition::new(
|
||||
range.start.as_byte_index(text_info.range().start),
|
||||
text_info.line_and_column_index(range.start),
|
||||
),
|
||||
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(),
|
||||
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::resolver::SloppyImportsResolver;
|
||||
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::PublishDiagnosticsCollector;
|
||||
use crate::util::display::human_size;
|
||||
|
@ -341,7 +341,7 @@ impl PublishPreparer {
|
|||
for package in package_configs {
|
||||
let export_urls = package.config_file.resolve_export_value_urls()?;
|
||||
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() {
|
||||
any_pkg_had_diagnostics = true;
|
||||
for diagnostic in diagnostics {
|
||||
|
|
|
@ -177,8 +177,8 @@ impl SpecifierUnfurler {
|
|||
if let Some(sloppy_imports_resolver) = &self.sloppy_imports_resolver {
|
||||
sloppy_imports_resolver
|
||||
.resolve(&resolved, deno_graph::source::ResolutionMode::Execution)
|
||||
.as_specifier()
|
||||
.clone()
|
||||
.map(|res| res.into_specifier())
|
||||
.unwrap_or(resolved)
|
||||
} else {
|
||||
resolved
|
||||
};
|
||||
|
|
|
@ -45,13 +45,6 @@ To grant permissions, set them before the script argument. For example:
|
|||
let deno_dir = factory.deno_dir()?;
|
||||
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
|
||||
// if an earlier run of this background task found a new version of Deno.
|
||||
#[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_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();
|
||||
}
|
||||
|
@ -14488,11 +14548,33 @@ fn lsp_sloppy_imports_prefers_dts() {
|
|||
"import { foo } from './a.js';\nconsole.log(foo);",
|
||||
);
|
||||
let diagnostics = client.did_open_file(&file);
|
||||
// no warnings because "a.js" exists
|
||||
assert_eq!(diagnostics.all().len(), 0);
|
||||
// no other warnings because "a.js" exists
|
||||
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);
|
||||
assert_eq!(diagnostics.all().len(), 0, "Got {:#?}", diagnostics.all());
|
||||
assert_eq!(json!(diagnostics.for_file(&a_dts.uri())), json!([]));
|
||||
|
||||
let response = client.write_request(
|
||||
"textDocument/references",
|
||||
|
|
|
@ -4851,86 +4851,6 @@ itest!(unsafe_proto_flag {
|
|||
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 {
|
||||
args: "run --no-config --unstable-temporal --check run/unstable_temporal_api/main.ts",
|
||||
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
|
||||
Checking for slow types in the public API...
|
||||
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
|
||||
Warning Sloppy imports are not recommended and have a negative impact on performance.
|
||||
Warning 'abc' isn't a valid unstable feature
|
||||
Warning 'cba' isn't a valid unstable feature
|
||||
Hello unstable features
|
||||
|
|
|
@ -1230,6 +1230,16 @@ impl CollectedDiagnostics {
|
|||
.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.
|
||||
pub fn all_messages(&self) -> Vec<lsp::PublishDiagnosticsParams> {
|
||||
self.0.clone()
|
||||
|
@ -1245,7 +1255,7 @@ impl CollectedDiagnostics {
|
|||
.find(|p| {
|
||||
p.diagnostics
|
||||
.iter()
|
||||
.any(|d| d.source == Some(source.to_string()))
|
||||
.any(|d| d.source.as_deref() == Some(source))
|
||||
})
|
||||
.map(ToOwned::to_owned)
|
||||
.unwrap()
|
||||
|
|
Loading…
Reference in a new issue