2025-01-01 04:12:39 +09:00
|
|
|
// Copyright 2018-2025 the Deno authors. MIT license.
|
2020-06-10 23:29:48 +02:00
|
|
|
|
2021-09-07 10:39:32 -04:00
|
|
|
//! This module provides file linting utilities using
|
2020-06-10 23:29:48 +02:00
|
|
|
//! [`deno_lint`](https://github.com/denoland/deno_lint).
|
2024-07-27 09:01:42 -04:00
|
|
|
|
2024-12-31 12:13:39 -05:00
|
|
|
use std::collections::HashSet;
|
|
|
|
use std::fs;
|
|
|
|
use std::io::stdin;
|
|
|
|
use std::io::Read;
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use std::rc::Rc;
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
2024-02-19 10:28:41 -05:00
|
|
|
use deno_ast::ModuleSpecifier;
|
2024-01-23 16:37:43 +01:00
|
|
|
use deno_ast::ParsedSource;
|
2024-07-25 09:07:59 -04:00
|
|
|
use deno_config::deno_json::LintRulesConfig;
|
2024-07-05 17:53:09 -04:00
|
|
|
use deno_config::glob::FileCollector;
|
2024-01-15 19:15:39 -05:00
|
|
|
use deno_config::glob::FilePatterns;
|
2024-07-19 15:56:07 -04:00
|
|
|
use deno_config::workspace::WorkspaceDirectory;
|
2024-07-03 20:54:33 -04:00
|
|
|
use deno_core::anyhow::anyhow;
|
2021-11-16 09:02:28 -05:00
|
|
|
use deno_core::error::AnyError;
|
2024-07-03 20:54:33 -04:00
|
|
|
use deno_core::futures::future::LocalBoxFuture;
|
|
|
|
use deno_core::futures::FutureExt;
|
2024-02-19 10:28:41 -05:00
|
|
|
use deno_core::parking_lot::Mutex;
|
2020-09-21 18:36:37 +02:00
|
|
|
use deno_core::serde_json;
|
2024-07-03 20:54:33 -04:00
|
|
|
use deno_core::unsync::future::LocalFutureExt;
|
|
|
|
use deno_core::unsync::future::SharedLocal;
|
|
|
|
use deno_graph::ModuleGraph;
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
use deno_lib::util::hash::FastInsecureHasher;
|
2020-06-10 23:29:48 +02:00
|
|
|
use deno_lint::diagnostic::LintDiagnostic;
|
2021-03-26 12:34:25 -04:00
|
|
|
use log::debug;
|
2024-07-27 09:01:42 -04:00
|
|
|
use reporters::create_reporter;
|
|
|
|
use reporters::LintReporter;
|
2020-08-13 10:30:46 -05:00
|
|
|
use serde::Serialize;
|
2020-06-10 23:29:48 +02:00
|
|
|
|
2025-01-28 10:49:58 -05:00
|
|
|
use crate::args::deno_json::TsConfigResolver;
|
2024-03-27 14:25:39 -04:00
|
|
|
use crate::args::CliOptions;
|
2024-02-19 10:28:41 -05:00
|
|
|
use crate::args::Flags;
|
|
|
|
use crate::args::LintFlags;
|
|
|
|
use crate::args::LintOptions;
|
2024-07-03 20:54:33 -04:00
|
|
|
use crate::args::WorkspaceLintOptions;
|
2025-01-06 23:56:36 +00:00
|
|
|
use crate::cache::CacheDBHash;
|
2024-07-03 20:54:33 -04:00
|
|
|
use crate::cache::Caches;
|
2022-07-12 18:58:39 -04:00
|
|
|
use crate::cache::IncrementalCache;
|
2024-02-19 10:28:41 -05:00
|
|
|
use crate::colors;
|
|
|
|
use crate::factory::CliFactory;
|
2024-07-03 20:54:33 -04:00
|
|
|
use crate::graph_util::ModuleGraphCreator;
|
2024-12-31 11:29:07 -05:00
|
|
|
use crate::sys::CliSys;
|
2024-02-19 10:28:41 -05:00
|
|
|
use crate::tools::fmt::run_parallelized;
|
2024-09-05 10:51:40 +02:00
|
|
|
use crate::util::display;
|
2024-02-19 10:28:41 -05:00
|
|
|
use crate::util::file_watcher;
|
2024-12-17 01:35:26 +00:00
|
|
|
use crate::util::file_watcher::WatcherCommunicator;
|
2024-02-19 10:28:41 -05:00
|
|
|
use crate::util::fs::canonicalize_path;
|
|
|
|
use crate::util::path::is_script_ext;
|
|
|
|
use crate::util::sync::AtomicFlag;
|
|
|
|
|
2024-12-21 00:58:03 +01:00
|
|
|
mod ast_buffer;
|
2024-07-25 09:07:59 -04:00
|
|
|
mod linter;
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
mod plugins;
|
2024-07-27 09:01:42 -04:00
|
|
|
mod reporters;
|
2024-07-25 09:07:59 -04:00
|
|
|
mod rules;
|
|
|
|
|
2024-12-21 00:58:03 +01:00
|
|
|
// TODO(bartlomieju): remove once we wire plugins through the CLI linter
|
|
|
|
pub use ast_buffer::serialize_ast_to_buffer;
|
2024-07-25 09:07:59 -04:00
|
|
|
pub use linter::CliLinter;
|
|
|
|
pub use linter::CliLinterOptions;
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
pub use plugins::create_runner_and_load_plugins;
|
|
|
|
pub use plugins::PluginLogger;
|
2024-07-25 09:07:59 -04:00
|
|
|
pub use rules::collect_no_slow_type_diagnostics;
|
|
|
|
pub use rules::ConfiguredRules;
|
|
|
|
pub use rules::LintRuleProvider;
|
2022-04-19 22:14:00 -04:00
|
|
|
|
2024-09-05 10:51:40 +02:00
|
|
|
const JSON_SCHEMA_VERSION: u8 = 1;
|
|
|
|
|
2024-11-13 10:10:09 -05:00
|
|
|
static STDIN_FILE_NAME: &str = "$deno$stdin.mts";
|
2021-09-08 07:08:33 +02:00
|
|
|
|
2024-07-23 19:00:48 -04:00
|
|
|
pub async fn lint(
|
|
|
|
flags: Arc<Flags>,
|
|
|
|
lint_flags: LintFlags,
|
|
|
|
) -> Result<(), AnyError> {
|
2024-12-17 01:35:26 +00:00
|
|
|
if lint_flags.watch.is_some() {
|
2023-06-14 18:29:19 -04:00
|
|
|
if lint_flags.is_stdin() {
|
2025-01-08 14:52:32 -08:00
|
|
|
return Err(anyhow!("Lint watch on standard input is not supported.",));
|
2021-10-06 05:07:38 +08:00
|
|
|
}
|
2024-07-03 20:54:33 -04:00
|
|
|
|
2024-12-17 01:35:26 +00:00
|
|
|
return lint_with_watch(flags, lint_flags).await;
|
|
|
|
}
|
2024-07-03 20:54:33 -04:00
|
|
|
|
2024-12-17 01:35:26 +00:00
|
|
|
let factory = CliFactory::from_flags(flags);
|
|
|
|
let cli_options = factory.cli_options()?;
|
|
|
|
let lint_rule_provider = factory.lint_rule_provider().await?;
|
|
|
|
let is_stdin = lint_flags.is_stdin();
|
2025-01-28 10:49:58 -05:00
|
|
|
let tsconfig_resolver = factory.tsconfig_resolver()?;
|
2024-12-17 01:35:26 +00:00
|
|
|
let workspace_lint_options =
|
|
|
|
cli_options.resolve_workspace_lint_options(&lint_flags)?;
|
|
|
|
let success = if is_stdin {
|
|
|
|
lint_stdin(
|
|
|
|
cli_options,
|
|
|
|
lint_rule_provider,
|
|
|
|
workspace_lint_options,
|
|
|
|
lint_flags,
|
2025-01-28 10:49:58 -05:00
|
|
|
tsconfig_resolver,
|
2024-12-17 01:35:26 +00:00
|
|
|
)?
|
|
|
|
} else {
|
|
|
|
let mut linter = WorkspaceLinter::new(
|
|
|
|
factory.caches()?.clone(),
|
|
|
|
lint_rule_provider,
|
|
|
|
factory.module_graph_creator().await?.clone(),
|
2025-01-28 10:49:58 -05:00
|
|
|
tsconfig_resolver.clone(),
|
2024-12-17 01:35:26 +00:00
|
|
|
cli_options.start_dir.clone(),
|
|
|
|
&workspace_lint_options,
|
|
|
|
);
|
|
|
|
let paths_with_options_batches =
|
|
|
|
resolve_paths_with_options_batches(cli_options, &lint_flags)?;
|
|
|
|
for paths_with_options in paths_with_options_batches {
|
|
|
|
linter
|
|
|
|
.lint_files(
|
|
|
|
cli_options,
|
|
|
|
paths_with_options.options,
|
|
|
|
paths_with_options.dir,
|
|
|
|
paths_with_options.paths,
|
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
}
|
|
|
|
linter.finish()
|
|
|
|
};
|
|
|
|
if !success {
|
|
|
|
deno_runtime::exit(1);
|
|
|
|
}
|
2024-07-03 20:54:33 -04:00
|
|
|
|
2024-12-17 01:35:26 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn lint_with_watch_inner(
|
|
|
|
flags: Arc<Flags>,
|
|
|
|
lint_flags: LintFlags,
|
|
|
|
watcher_communicator: Arc<WatcherCommunicator>,
|
|
|
|
changed_paths: Option<Vec<PathBuf>>,
|
|
|
|
) -> Result<(), AnyError> {
|
|
|
|
let factory = CliFactory::from_flags(flags);
|
|
|
|
let cli_options = factory.cli_options()?;
|
|
|
|
let mut paths_with_options_batches =
|
|
|
|
resolve_paths_with_options_batches(cli_options, &lint_flags)?;
|
|
|
|
for paths_with_options in &mut paths_with_options_batches {
|
|
|
|
_ = watcher_communicator.watch_paths(paths_with_options.paths.clone());
|
|
|
|
|
|
|
|
let files = std::mem::take(&mut paths_with_options.paths);
|
|
|
|
paths_with_options.paths = if let Some(paths) = &changed_paths {
|
|
|
|
// lint all files on any changed (https://github.com/denoland/deno/issues/12446)
|
|
|
|
files
|
|
|
|
.iter()
|
|
|
|
.any(|path| {
|
|
|
|
canonicalize_path(path)
|
|
|
|
.map(|p| paths.contains(&p))
|
|
|
|
.unwrap_or(false)
|
2023-06-14 18:29:19 -04:00
|
|
|
})
|
2024-12-17 01:35:26 +00:00
|
|
|
.then_some(files)
|
|
|
|
.unwrap_or_else(|| [].to_vec())
|
2021-09-08 07:08:33 +02:00
|
|
|
} else {
|
2024-12-17 01:35:26 +00:00
|
|
|
files
|
2021-09-08 07:08:33 +02:00
|
|
|
};
|
2020-06-10 23:29:48 +02:00
|
|
|
}
|
|
|
|
|
2024-12-17 01:35:26 +00:00
|
|
|
let mut linter = WorkspaceLinter::new(
|
|
|
|
factory.caches()?.clone(),
|
|
|
|
factory.lint_rule_provider().await?,
|
|
|
|
factory.module_graph_creator().await?.clone(),
|
2025-01-28 10:49:58 -05:00
|
|
|
factory.tsconfig_resolver()?.clone(),
|
2024-12-17 01:35:26 +00:00
|
|
|
cli_options.start_dir.clone(),
|
|
|
|
&cli_options.resolve_workspace_lint_options(&lint_flags)?,
|
|
|
|
);
|
|
|
|
for paths_with_options in paths_with_options_batches {
|
|
|
|
linter
|
|
|
|
.lint_files(
|
|
|
|
cli_options,
|
|
|
|
paths_with_options.options,
|
|
|
|
paths_with_options.dir,
|
|
|
|
paths_with_options.paths,
|
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
}
|
|
|
|
|
|
|
|
linter.finish();
|
|
|
|
|
2020-06-10 23:29:48 +02:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-12-17 01:35:26 +00:00
|
|
|
async fn lint_with_watch(
|
|
|
|
flags: Arc<Flags>,
|
|
|
|
lint_flags: LintFlags,
|
|
|
|
) -> Result<(), AnyError> {
|
|
|
|
let watch_flags = lint_flags.watch.as_ref().unwrap();
|
|
|
|
|
|
|
|
file_watcher::watch_func(
|
|
|
|
flags,
|
|
|
|
file_watcher::PrintConfig::new("Lint", !watch_flags.no_clear_screen),
|
|
|
|
move |flags, watcher_communicator, changed_paths| {
|
|
|
|
let lint_flags = lint_flags.clone();
|
|
|
|
watcher_communicator.show_path_changed(changed_paths.clone());
|
|
|
|
Ok(lint_with_watch_inner(
|
|
|
|
flags,
|
|
|
|
lint_flags,
|
|
|
|
watcher_communicator,
|
|
|
|
changed_paths,
|
|
|
|
))
|
|
|
|
},
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
|
2024-07-03 20:54:33 -04:00
|
|
|
struct PathsWithOptions {
|
2024-07-19 15:56:07 -04:00
|
|
|
dir: WorkspaceDirectory,
|
2023-06-14 18:29:19 -04:00
|
|
|
paths: Vec<PathBuf>,
|
2024-07-03 20:54:33 -04:00
|
|
|
options: LintOptions,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn resolve_paths_with_options_batches(
|
|
|
|
cli_options: &CliOptions,
|
|
|
|
lint_flags: &LintFlags,
|
|
|
|
) -> Result<Vec<PathsWithOptions>, AnyError> {
|
|
|
|
let members_lint_options =
|
|
|
|
cli_options.resolve_lint_options_for_members(lint_flags)?;
|
|
|
|
let mut paths_with_options_batches =
|
|
|
|
Vec::with_capacity(members_lint_options.len());
|
2024-07-19 15:56:07 -04:00
|
|
|
for (dir, lint_options) in members_lint_options {
|
2025-01-08 14:52:32 -08:00
|
|
|
let files = collect_lint_files(cli_options, lint_options.files.clone());
|
2024-07-03 20:54:33 -04:00
|
|
|
if !files.is_empty() {
|
|
|
|
paths_with_options_batches.push(PathsWithOptions {
|
2024-07-19 15:56:07 -04:00
|
|
|
dir,
|
2024-07-03 20:54:33 -04:00
|
|
|
paths: files,
|
|
|
|
options: lint_options,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if paths_with_options_batches.is_empty() {
|
2025-01-08 14:52:32 -08:00
|
|
|
return Err(anyhow!("No target files found."));
|
2024-07-03 20:54:33 -04:00
|
|
|
}
|
|
|
|
Ok(paths_with_options_batches)
|
|
|
|
}
|
|
|
|
|
|
|
|
type WorkspaceModuleGraphFuture =
|
|
|
|
SharedLocal<LocalBoxFuture<'static, Result<Rc<ModuleGraph>, Rc<AnyError>>>>;
|
|
|
|
|
|
|
|
struct WorkspaceLinter {
|
|
|
|
caches: Arc<Caches>,
|
2024-07-25 09:07:59 -04:00
|
|
|
lint_rule_provider: LintRuleProvider,
|
2024-07-03 20:54:33 -04:00
|
|
|
module_graph_creator: Arc<ModuleGraphCreator>,
|
2025-01-28 10:49:58 -05:00
|
|
|
tsconfig_resolver: Arc<TsConfigResolver>,
|
2024-07-19 15:56:07 -04:00
|
|
|
workspace_dir: Arc<WorkspaceDirectory>,
|
2024-07-03 20:54:33 -04:00
|
|
|
reporter_lock: Arc<Mutex<Box<dyn LintReporter + Send>>>,
|
|
|
|
workspace_module_graph: Option<WorkspaceModuleGraphFuture>,
|
|
|
|
has_error: Arc<AtomicFlag>,
|
|
|
|
file_count: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl WorkspaceLinter {
|
|
|
|
pub fn new(
|
|
|
|
caches: Arc<Caches>,
|
2024-07-25 09:07:59 -04:00
|
|
|
lint_rule_provider: LintRuleProvider,
|
2024-07-03 20:54:33 -04:00
|
|
|
module_graph_creator: Arc<ModuleGraphCreator>,
|
2025-01-28 10:49:58 -05:00
|
|
|
tsconfig_resolver: Arc<TsConfigResolver>,
|
2024-07-19 15:56:07 -04:00
|
|
|
workspace_dir: Arc<WorkspaceDirectory>,
|
2024-07-03 20:54:33 -04:00
|
|
|
workspace_options: &WorkspaceLintOptions,
|
|
|
|
) -> Self {
|
|
|
|
let reporter_lock =
|
|
|
|
Arc::new(Mutex::new(create_reporter(workspace_options.reporter_kind)));
|
|
|
|
Self {
|
|
|
|
caches,
|
2024-07-25 09:07:59 -04:00
|
|
|
lint_rule_provider,
|
2024-07-03 20:54:33 -04:00
|
|
|
module_graph_creator,
|
2025-01-28 10:49:58 -05:00
|
|
|
tsconfig_resolver,
|
2024-07-19 15:56:07 -04:00
|
|
|
workspace_dir,
|
2024-07-03 20:54:33 -04:00
|
|
|
reporter_lock,
|
|
|
|
workspace_module_graph: None,
|
|
|
|
has_error: Default::default(),
|
|
|
|
file_count: 0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn lint_files(
|
|
|
|
&mut self,
|
2024-09-18 12:15:13 -07:00
|
|
|
cli_options: &Arc<CliOptions>,
|
2024-07-03 20:54:33 -04:00
|
|
|
lint_options: LintOptions,
|
2024-07-19 15:56:07 -04:00
|
|
|
member_dir: WorkspaceDirectory,
|
2024-07-03 20:54:33 -04:00
|
|
|
paths: Vec<PathBuf>,
|
|
|
|
) -> Result<(), AnyError> {
|
|
|
|
self.file_count += paths.len();
|
|
|
|
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
let exclude = lint_options.rules.exclude.clone();
|
|
|
|
|
|
|
|
let plugin_specifiers = lint_options.plugins.clone();
|
2024-07-25 09:07:59 -04:00
|
|
|
let lint_rules = self.lint_rule_provider.resolve_lint_rules_err_empty(
|
2024-07-03 20:54:33 -04:00
|
|
|
lint_options.rules,
|
2024-07-19 15:56:07 -04:00
|
|
|
member_dir.maybe_deno_json().map(|c| c.as_ref()),
|
2024-07-03 20:54:33 -04:00
|
|
|
)?;
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
let mut maybe_incremental_cache = None;
|
|
|
|
|
|
|
|
// TODO(bartlomieju): how do we decide if plugins support incremental cache?
|
|
|
|
if lint_rules.supports_incremental_cache() {
|
|
|
|
let mut hasher = FastInsecureHasher::new_deno_versioned();
|
|
|
|
hasher.write_hashable(lint_rules.incremental_cache_state());
|
|
|
|
if !plugin_specifiers.is_empty() {
|
|
|
|
hasher.write_hashable(&plugin_specifiers);
|
|
|
|
}
|
|
|
|
let state_hash = hasher.finish();
|
|
|
|
|
|
|
|
maybe_incremental_cache = Some(Arc::new(IncrementalCache::new(
|
|
|
|
self.caches.lint_incremental_cache_db(),
|
|
|
|
CacheDBHash::new(state_hash),
|
|
|
|
&paths,
|
|
|
|
)));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(clippy::print_stdout)]
|
|
|
|
#[allow(clippy::print_stderr)]
|
|
|
|
fn logger_printer(msg: &str, is_err: bool) {
|
|
|
|
if is_err {
|
|
|
|
eprint!("{}", msg);
|
|
|
|
} else {
|
|
|
|
print!("{}", msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut plugin_runner = None;
|
|
|
|
if !plugin_specifiers.is_empty() {
|
|
|
|
let logger = plugins::PluginLogger::new(logger_printer);
|
|
|
|
let runner = plugins::create_runner_and_load_plugins(
|
|
|
|
plugin_specifiers,
|
|
|
|
logger,
|
|
|
|
exclude,
|
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
plugin_runner = Some(Arc::new(runner));
|
|
|
|
}
|
2024-07-25 09:07:59 -04:00
|
|
|
|
|
|
|
let linter = Arc::new(CliLinter::new(CliLinterOptions {
|
|
|
|
configured_rules: lint_rules,
|
|
|
|
fix: lint_options.fix,
|
2025-01-28 10:49:58 -05:00
|
|
|
deno_lint_config: self
|
|
|
|
.tsconfig_resolver
|
|
|
|
.deno_lint_config(member_dir.dir_url())?,
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
maybe_plugin_runner: plugin_runner,
|
2024-07-25 09:07:59 -04:00
|
|
|
}));
|
2024-07-03 20:54:33 -04:00
|
|
|
|
2024-12-17 01:35:26 +00:00
|
|
|
let has_error = self.has_error.clone();
|
|
|
|
let reporter_lock = self.reporter_lock.clone();
|
|
|
|
|
2024-07-03 20:54:33 -04:00
|
|
|
let mut futures = Vec::with_capacity(2);
|
2024-07-25 09:07:59 -04:00
|
|
|
if linter.has_package_rules() {
|
2024-12-17 01:35:26 +00:00
|
|
|
if let Some(fut) = self.run_package_rules(&linter, &member_dir, &paths) {
|
|
|
|
futures.push(fut);
|
2024-07-03 20:54:33 -04:00
|
|
|
}
|
|
|
|
}
|
2023-06-14 18:29:19 -04:00
|
|
|
|
2024-12-17 01:35:26 +00:00
|
|
|
let maybe_incremental_cache_ = maybe_incremental_cache.clone();
|
|
|
|
let linter = linter.clone();
|
|
|
|
let cli_options = cli_options.clone();
|
|
|
|
let fut = async move {
|
|
|
|
let operation = move |file_path: PathBuf| {
|
|
|
|
let file_text = 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 let Some(incremental_cache) = &maybe_incremental_cache_ {
|
|
|
|
if incremental_cache.is_file_same(&file_path, &file_text) {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
}
|
2023-06-14 18:29:19 -04:00
|
|
|
|
2024-12-17 01:35:26 +00:00
|
|
|
let r = linter.lint_file(
|
|
|
|
&file_path,
|
|
|
|
file_text,
|
|
|
|
cli_options.ext_flag().as_deref(),
|
|
|
|
);
|
|
|
|
if let Ok((file_source, file_diagnostics)) = &r {
|
|
|
|
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(),
|
|
|
|
)
|
2024-07-03 20:54:33 -04:00
|
|
|
}
|
2024-12-17 01:35:26 +00:00
|
|
|
}
|
|
|
|
}
|
2024-02-19 10:28:41 -05:00
|
|
|
|
2024-12-17 01:35:26 +00:00
|
|
|
let success = handle_lint_result(
|
|
|
|
&file_path.to_string_lossy(),
|
|
|
|
r,
|
|
|
|
reporter_lock.clone(),
|
|
|
|
);
|
|
|
|
if !success {
|
|
|
|
has_error.raise();
|
|
|
|
}
|
2023-06-14 18:29:19 -04:00
|
|
|
|
2024-12-17 01:35:26 +00:00
|
|
|
Ok(())
|
|
|
|
};
|
|
|
|
run_parallelized(paths, operation).await
|
|
|
|
}
|
|
|
|
.boxed_local();
|
|
|
|
futures.push(fut);
|
2023-06-14 18:29:19 -04:00
|
|
|
|
2024-07-25 09:07:59 -04:00
|
|
|
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;
|
|
|
|
}
|
2023-06-14 18:29:19 -04:00
|
|
|
|
2024-07-03 20:54:33 -04:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-12-17 01:35:26 +00:00
|
|
|
fn run_package_rules(
|
|
|
|
&mut self,
|
|
|
|
linter: &Arc<CliLinter>,
|
|
|
|
member_dir: &WorkspaceDirectory,
|
|
|
|
paths: &[PathBuf],
|
|
|
|
) -> Option<LocalBoxFuture<Result<(), AnyError>>> {
|
|
|
|
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();
|
|
|
|
self.workspace_module_graph = Some(
|
|
|
|
async move {
|
|
|
|
module_graph_creator
|
|
|
|
.create_and_validate_publish_graph(&packages, true)
|
|
|
|
.await
|
|
|
|
.map(Rc::new)
|
|
|
|
.map_err(Rc::new)
|
|
|
|
}
|
|
|
|
.boxed_local()
|
|
|
|
.shared_local(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let workspace_module_graph_future =
|
|
|
|
self.workspace_module_graph.as_ref().unwrap().clone();
|
|
|
|
let maybe_publish_config = member_dir.maybe_package_config();
|
|
|
|
let publish_config = maybe_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())
|
|
|
|
.collect::<HashSet<_>>();
|
|
|
|
let fut = async move {
|
|
|
|
let graph = workspace_module_graph_future
|
|
|
|
.await
|
|
|
|
.map_err(|err| anyhow!("{:#}", err))?;
|
|
|
|
let export_urls =
|
|
|
|
publish_config.config_file.resolve_export_value_urls()?;
|
|
|
|
if !export_urls.iter().any(|url| path_urls.contains(url)) {
|
|
|
|
return Ok(()); // entrypoint is not specified, so skip
|
|
|
|
}
|
|
|
|
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(diagnostic);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
.boxed_local();
|
|
|
|
Some(fut)
|
|
|
|
}
|
|
|
|
|
2024-07-03 20:54:33 -04:00
|
|
|
pub fn finish(self) -> bool {
|
|
|
|
debug!("Found {} files", self.file_count);
|
|
|
|
self.reporter_lock.lock().close(self.file_count);
|
|
|
|
!self.has_error.is_raised() // success
|
|
|
|
}
|
2023-06-14 18:29:19 -04:00
|
|
|
}
|
|
|
|
|
2024-03-27 14:25:39 -04:00
|
|
|
fn collect_lint_files(
|
|
|
|
cli_options: &CliOptions,
|
|
|
|
files: FilePatterns,
|
2025-01-08 14:52:32 -08:00
|
|
|
) -> Vec<PathBuf> {
|
2024-09-18 12:15:13 -07:00
|
|
|
FileCollector::new(|e| {
|
2024-09-19 02:25:48 -07:00
|
|
|
is_script_ext(e.path)
|
|
|
|
|| (e.path.extension().is_none() && cli_options.ext_flag().is_some())
|
2024-09-18 12:15:13 -07:00
|
|
|
})
|
|
|
|
.ignore_git_folder()
|
|
|
|
.ignore_node_modules()
|
2024-11-18 22:54:28 +00:00
|
|
|
.use_gitignore()
|
2024-09-18 12:15:13 -07:00
|
|
|
.set_vendor_folder(cli_options.vendor_dir_path().map(ToOwned::to_owned))
|
2024-12-31 11:29:07 -05:00
|
|
|
.collect_file_patterns(&CliSys::default(), files)
|
2022-12-07 13:10:10 -05:00
|
|
|
}
|
|
|
|
|
2024-05-08 22:45:06 -04:00
|
|
|
#[allow(clippy::print_stdout)]
|
2023-07-25 23:24:06 +02:00
|
|
|
pub fn print_rules_list(json: bool, maybe_rules_tags: Option<Vec<String>>) {
|
2024-07-25 09:07:59 -04:00
|
|
|
let rule_provider = LintRuleProvider::new(None, None);
|
2025-01-24 13:08:36 +01:00
|
|
|
let mut all_rules = rule_provider.all_rules();
|
2025-01-22 22:43:00 +00:00
|
|
|
let configured_rules = rule_provider.resolve_lint_rules(
|
|
|
|
LintRulesConfig {
|
|
|
|
tags: maybe_rules_tags.clone(),
|
|
|
|
include: None,
|
|
|
|
exclude: None,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
2025-01-24 13:08:36 +01:00
|
|
|
all_rules.sort_by_cached_key(|rule| rule.code().to_string());
|
2020-06-12 16:42:12 +02:00
|
|
|
|
2020-11-14 20:51:30 +01:00
|
|
|
if json {
|
2024-09-05 10:51:40 +02:00
|
|
|
let json_output = serde_json::json!({
|
|
|
|
"version": JSON_SCHEMA_VERSION,
|
2025-01-22 22:43:00 +00:00
|
|
|
"rules": all_rules
|
2024-09-05 10:51:40 +02:00
|
|
|
.iter()
|
|
|
|
.map(|rule| {
|
2025-01-22 22:43:00 +00:00
|
|
|
// TODO(bartlomieju): print if rule enabled
|
2024-09-05 10:51:40 +02:00
|
|
|
serde_json::json!({
|
|
|
|
"code": rule.code(),
|
2025-01-24 13:08:36 +01:00
|
|
|
"tags": rule.tags().iter().map(|t| t.display()).collect::<Vec<_>>(),
|
2025-01-22 22:43:00 +00:00
|
|
|
"docs": rule.help_docs_url(),
|
2024-09-05 10:51:40 +02:00
|
|
|
})
|
2021-09-08 07:08:33 +02:00
|
|
|
})
|
2024-09-05 10:51:40 +02:00
|
|
|
.collect::<Vec<serde_json::Value>>(),
|
|
|
|
});
|
|
|
|
display::write_json_to_stdout(&json_output).unwrap();
|
2020-11-14 20:51:30 +01:00
|
|
|
} else {
|
|
|
|
// The rules should still be printed even if `--quiet` option is enabled,
|
|
|
|
// so use `println!` here instead of `info!`.
|
|
|
|
println!("Available rules:");
|
2025-01-22 22:43:00 +00:00
|
|
|
for rule in all_rules.iter() {
|
|
|
|
// TODO(bartlomieju): this is O(n) search, fix before landing
|
|
|
|
let enabled = if configured_rules.rules.contains(rule) {
|
|
|
|
"✓"
|
2023-08-27 10:17:41 +01:00
|
|
|
} else {
|
2025-01-24 13:08:36 +01:00
|
|
|
""
|
2025-01-22 22:43:00 +00:00
|
|
|
};
|
|
|
|
println!("- {} {}", rule.code(), colors::green(enabled),);
|
2023-08-27 10:17:41 +01:00
|
|
|
println!(
|
|
|
|
"{}",
|
2025-01-22 22:43:00 +00:00
|
|
|
colors::gray(format!(" help: {}", rule.help_docs_url()))
|
2023-08-27 10:17:41 +01:00
|
|
|
);
|
2025-01-22 22:43:00 +00:00
|
|
|
if rule.tags().is_empty() {
|
|
|
|
println!(" {}", colors::gray("tags:"));
|
|
|
|
} else {
|
|
|
|
println!(
|
|
|
|
" {}",
|
2025-01-24 13:08:36 +01:00
|
|
|
colors::gray(format!(
|
|
|
|
"tags: {}",
|
|
|
|
rule
|
|
|
|
.tags()
|
|
|
|
.iter()
|
|
|
|
.map(|t| t.display())
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.join(", ")
|
|
|
|
))
|
2025-01-22 22:43:00 +00:00
|
|
|
);
|
|
|
|
}
|
2021-08-12 19:15:31 +02:00
|
|
|
println!();
|
2020-11-14 20:51:30 +01:00
|
|
|
}
|
2020-06-12 16:42:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-31 20:53:42 +09:00
|
|
|
/// Lint stdin and write result to stdout.
|
|
|
|
/// Treats input as TypeScript.
|
|
|
|
/// Compatible with `--json` flag.
|
2021-09-03 17:01:58 +02:00
|
|
|
fn lint_stdin(
|
2024-12-17 01:35:26 +00:00
|
|
|
cli_options: &Arc<CliOptions>,
|
|
|
|
lint_rule_provider: LintRuleProvider,
|
|
|
|
workspace_lint_options: WorkspaceLintOptions,
|
|
|
|
lint_flags: LintFlags,
|
2025-01-28 10:49:58 -05:00
|
|
|
tsconfig_resolver: &TsConfigResolver,
|
2024-12-17 01:35:26 +00:00
|
|
|
) -> Result<bool, AnyError> {
|
|
|
|
let start_dir = &cli_options.start_dir;
|
|
|
|
let reporter_lock = Arc::new(Mutex::new(create_reporter(
|
|
|
|
workspace_lint_options.reporter_kind,
|
|
|
|
)));
|
|
|
|
let lint_config = start_dir
|
|
|
|
.to_lint_config(FilePatterns::new_with_base(start_dir.dir_path()))?;
|
2025-01-28 10:49:58 -05:00
|
|
|
let deno_lint_config =
|
|
|
|
tsconfig_resolver.deno_lint_config(start_dir.dir_url())?;
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
let lint_options =
|
|
|
|
LintOptions::resolve(start_dir.dir_path(), lint_config, &lint_flags)?;
|
2024-12-17 01:35:26 +00:00
|
|
|
let configured_rules = lint_rule_provider.resolve_lint_rules_err_empty(
|
|
|
|
lint_options.rules,
|
|
|
|
start_dir.maybe_deno_json().map(|c| c.as_ref()),
|
|
|
|
)?;
|
|
|
|
let mut file_path = cli_options.initial_cwd().join(STDIN_FILE_NAME);
|
|
|
|
if let Some(ext) = cli_options.ext_flag() {
|
|
|
|
file_path.set_extension(ext);
|
|
|
|
}
|
2021-09-08 07:08:33 +02:00
|
|
|
let mut source_code = String::new();
|
|
|
|
if stdin().read_to_string(&mut source_code).is_err() {
|
2025-01-08 14:52:32 -08:00
|
|
|
return Err(anyhow!("Failed to read from stdin"));
|
2020-08-31 20:53:42 +09:00
|
|
|
}
|
|
|
|
|
2024-07-25 09:07:59 -04:00
|
|
|
let linter = CliLinter::new(CliLinterOptions {
|
|
|
|
fix: false,
|
|
|
|
configured_rules,
|
|
|
|
deno_lint_config,
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
maybe_plugin_runner: None,
|
2024-07-25 09:07:59 -04:00
|
|
|
});
|
2021-09-08 07:08:33 +02:00
|
|
|
|
2024-12-17 01:35:26 +00:00
|
|
|
let r = linter
|
|
|
|
.lint_file(&file_path, deno_ast::strip_bom(source_code), None)
|
|
|
|
.map_err(AnyError::from);
|
|
|
|
|
|
|
|
let success =
|
|
|
|
handle_lint_result(&file_path.to_string_lossy(), r, reporter_lock.clone());
|
|
|
|
reporter_lock.lock().close(1);
|
|
|
|
Ok(success)
|
2021-09-08 07:08:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_lint_result(
|
|
|
|
file_path: &str,
|
2024-03-21 14:18:59 -07:00
|
|
|
result: Result<(ParsedSource, Vec<LintDiagnostic>), AnyError>,
|
2021-09-08 07:08:33 +02:00
|
|
|
reporter_lock: Arc<Mutex<Box<dyn LintReporter + Send>>>,
|
2023-06-14 18:29:19 -04:00
|
|
|
) -> bool {
|
2024-02-19 10:28:41 -05:00
|
|
|
let mut reporter = reporter_lock.lock();
|
2021-09-08 07:08:33 +02:00
|
|
|
|
|
|
|
match result {
|
2024-07-24 00:17:17 +03:00
|
|
|
Ok((source, mut file_diagnostics)) => {
|
|
|
|
if !source.diagnostics().is_empty() {
|
|
|
|
for parse_diagnostic in source.diagnostics() {
|
|
|
|
log::warn!("{}: {}", colors::yellow("warn"), parse_diagnostic);
|
|
|
|
}
|
|
|
|
}
|
2024-02-08 20:40:26 -05:00
|
|
|
file_diagnostics.sort_by(|a, b| match a.specifier.cmp(&b.specifier) {
|
2024-07-25 09:07:59 -04:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
2024-02-08 20:40:26 -05:00
|
|
|
file_order => file_order,
|
|
|
|
});
|
2024-02-19 10:28:41 -05:00
|
|
|
for d in &file_diagnostics {
|
2024-07-25 09:07:59 -04:00
|
|
|
reporter.visit_diagnostic(d);
|
2020-08-31 20:53:42 +09:00
|
|
|
}
|
2023-06-14 18:29:19 -04:00
|
|
|
file_diagnostics.is_empty()
|
2020-08-31 20:53:42 +09:00
|
|
|
}
|
|
|
|
Err(err) => {
|
2021-09-08 07:08:33 +02:00
|
|
|
reporter.visit_error(file_path, &err);
|
2023-06-14 18:29:19 -04:00
|
|
|
false
|
2020-08-31 20:53:42 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-13 10:30:46 -05:00
|
|
|
#[derive(Serialize)]
|
|
|
|
struct LintError {
|
|
|
|
file_path: String,
|
|
|
|
message: String,
|
|
|
|
}
|
2024-12-11 01:06:04 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use pretty_assertions::assert_eq;
|
|
|
|
use serde::Deserialize;
|
|
|
|
use test_util as util;
|
|
|
|
|
2024-12-31 12:13:39 -05:00
|
|
|
use super::*;
|
|
|
|
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
struct RulesPattern {
|
|
|
|
r#type: String,
|
|
|
|
pattern: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
struct RulesEnum {
|
|
|
|
r#enum: Vec<String>,
|
|
|
|
}
|
|
|
|
|
2024-12-11 01:06:04 +00:00
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
struct RulesSchema {
|
|
|
|
#[serde(rename = "$schema")]
|
|
|
|
schema: String,
|
|
|
|
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
#[serde(rename = "oneOf")]
|
|
|
|
one_of: (RulesPattern, RulesEnum),
|
2024-12-11 01:06:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn get_all_rules() -> Vec<String> {
|
|
|
|
let rule_provider = LintRuleProvider::new(None, None);
|
|
|
|
let configured_rules =
|
|
|
|
rule_provider.resolve_lint_rules(Default::default(), None);
|
|
|
|
let mut all_rules = configured_rules
|
|
|
|
.all_rule_codes
|
|
|
|
.into_iter()
|
|
|
|
.map(|s| s.to_string())
|
|
|
|
.collect::<Vec<String>>();
|
|
|
|
all_rules.sort();
|
|
|
|
|
|
|
|
all_rules
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(bartlomieju): do the same for tags, once https://github.com/denoland/deno/pull/27162 lands
|
|
|
|
#[test]
|
|
|
|
fn all_lint_rules_are_listed_in_schema_file() {
|
|
|
|
let all_rules = get_all_rules();
|
|
|
|
|
|
|
|
let rules_schema_path =
|
|
|
|
util::root_path().join("cli/schemas/lint-rules.v1.json");
|
|
|
|
let rules_schema_file =
|
|
|
|
std::fs::read_to_string(&rules_schema_path).unwrap();
|
|
|
|
|
|
|
|
let schema: RulesSchema = serde_json::from_str(&rules_schema_file).unwrap();
|
|
|
|
|
|
|
|
const UPDATE_ENV_VAR_NAME: &str = "UPDATE_EXPECTED";
|
|
|
|
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
let rules_list = schema.one_of.1.r#enum;
|
|
|
|
|
2024-12-11 01:06:04 +00:00
|
|
|
if std::env::var(UPDATE_ENV_VAR_NAME).ok().is_none() {
|
|
|
|
assert_eq!(
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
rules_list, all_rules,
|
2024-12-11 01:06:04 +00:00
|
|
|
"Lint rules schema file not up to date. Run again with {}=1 to update the expected output",
|
|
|
|
UPDATE_ENV_VAR_NAME
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
let new_schema = RulesSchema {
|
|
|
|
schema: schema.schema,
|
|
|
|
one_of: (schema.one_of.0, RulesEnum { r#enum: all_rules }),
|
|
|
|
};
|
|
|
|
|
2024-12-11 01:06:04 +00:00
|
|
|
std::fs::write(
|
|
|
|
&rules_schema_path,
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
format!("{}\n", serde_json::to_string_pretty(&new_schema).unwrap(),),
|
2024-12-11 01:06:04 +00:00
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
}
|