2025-01-01 04:12:39 +09:00
|
|
|
// Copyright 2018-2025 the Deno authors. MIT license.
|
2024-07-25 09:07:59 -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
|
|
|
use std::borrow::Cow;
|
2024-08-15 13:43:04 -07:00
|
|
|
use std::collections::HashSet;
|
2024-07-25 09:07:59 -04:00
|
|
|
use std::path::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
|
|
|
use std::path::PathBuf;
|
|
|
|
use std::sync::Arc;
|
2024-07-25 09:07:59 -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
|
|
|
use ::tokio_util::sync::CancellationToken;
|
2024-07-25 09:07:59 -04:00
|
|
|
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;
|
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_core::futures::FutureExt as _;
|
|
|
|
use deno_core::parking_lot::Mutex;
|
2024-07-25 09:07:59 -04:00
|
|
|
use deno_graph::ModuleGraph;
|
|
|
|
use deno_lint::diagnostic::LintDiagnostic;
|
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_lint::linter::ExternalLinterCb;
|
|
|
|
use deno_lint::linter::ExternalLinterResult;
|
2024-07-25 09:07:59 -04:00
|
|
|
use deno_lint::linter::LintConfig as DenoLintConfig;
|
|
|
|
use deno_lint::linter::LintFileOptions;
|
|
|
|
use deno_lint::linter::Linter as DenoLintLinter;
|
|
|
|
use deno_lint::linter::LinterOptions;
|
2024-12-30 12:38:20 -05:00
|
|
|
use deno_path_util::fs::atomic_write_file_with_retries;
|
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_runtime::tokio_util;
|
2024-07-25 09:07:59 -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
|
|
|
use super::plugins;
|
|
|
|
use super::plugins::PluginHostProxy;
|
2024-07-25 09:07:59 -04:00
|
|
|
use super::rules::FileOrPackageLintRule;
|
|
|
|
use super::rules::PackageLintRule;
|
|
|
|
use super::ConfiguredRules;
|
2024-12-31 12:13:39 -05:00
|
|
|
use crate::sys::CliSys;
|
|
|
|
use crate::util::fs::specifier_from_file_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
|
|
|
use crate::util::text_encoding::Utf16Map;
|
2024-07-25 09:07:59 -04:00
|
|
|
|
|
|
|
pub struct CliLinterOptions {
|
|
|
|
pub configured_rules: ConfiguredRules,
|
|
|
|
pub fix: bool,
|
|
|
|
pub deno_lint_config: DenoLintConfig,
|
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 maybe_plugin_runner: Option<Arc<PluginHostProxy>>,
|
2024-07-25 09:07:59 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct CliLinter {
|
|
|
|
fix: bool,
|
|
|
|
package_rules: Vec<Box<dyn PackageLintRule>>,
|
|
|
|
linter: DenoLintLinter,
|
|
|
|
deno_lint_config: DenoLintConfig,
|
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: Option<Arc<PluginHostProxy>>,
|
2024-07-25 09:07:59 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
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: options.maybe_plugin_runner,
|
2024-07-25 09:07:59 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
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
|
|
|
token: CancellationToken,
|
|
|
|
) -> Result<Vec<LintDiagnostic>, AnyError> {
|
|
|
|
let external_linter_container = ExternalLinterContainer::new(
|
|
|
|
self.maybe_plugin_runner.clone(),
|
|
|
|
Some(token),
|
|
|
|
);
|
|
|
|
|
|
|
|
let d = self.linter.lint_with_ast(
|
|
|
|
parsed_source,
|
|
|
|
self.deno_lint_config.clone(),
|
|
|
|
external_linter_container.get_callback(),
|
|
|
|
);
|
|
|
|
if let Some(err) = external_linter_container.take_error() {
|
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
Ok(d)
|
2024-07-25 09:07:59 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn lint_file(
|
|
|
|
&self,
|
|
|
|
file_path: &Path,
|
|
|
|
source_code: String,
|
2024-09-18 12:15:13 -07:00
|
|
|
ext: Option<&str>,
|
2024-07-25 09:07:59 -04:00
|
|
|
) -> Result<(ParsedSource, Vec<LintDiagnostic>), AnyError> {
|
|
|
|
let specifier = specifier_from_file_path(file_path)?;
|
2024-09-18 12:15:13 -07:00
|
|
|
let media_type = if let Some(ext) = ext {
|
|
|
|
MediaType::from_str(&format!("placeholder.{ext}"))
|
|
|
|
} else if file_path.extension().is_none() {
|
|
|
|
MediaType::TypeScript
|
|
|
|
} else {
|
|
|
|
MediaType::from_specifier(&specifier)
|
|
|
|
};
|
2024-07-25 09:07:59 -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 external_linter_container =
|
|
|
|
ExternalLinterContainer::new(self.maybe_plugin_runner.clone(), None);
|
|
|
|
|
2024-07-25 09:07:59 -04:00
|
|
|
if self.fix {
|
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
|
|
|
self.lint_file_and_fix(
|
|
|
|
&specifier,
|
|
|
|
media_type,
|
|
|
|
source_code,
|
|
|
|
file_path,
|
|
|
|
external_linter_container,
|
|
|
|
)
|
2024-07-25 09:07:59 -04:00
|
|
|
} else {
|
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 (source, diagnostics) = self
|
2024-07-25 09:07:59 -04:00
|
|
|
.linter
|
|
|
|
.lint_file(LintFileOptions {
|
|
|
|
specifier,
|
|
|
|
media_type,
|
|
|
|
source_code,
|
|
|
|
config: self.deno_lint_config.clone(),
|
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
|
|
|
external_linter: external_linter_container.get_callback(),
|
2024-07-25 09:07:59 -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
|
|
|
.map_err(AnyError::from)?;
|
|
|
|
|
|
|
|
if let Some(err) = external_linter_container.take_error() {
|
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok((source, diagnostics))
|
2024-07-25 09:07:59 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn lint_file_and_fix(
|
|
|
|
&self,
|
|
|
|
specifier: &ModuleSpecifier,
|
|
|
|
media_type: MediaType,
|
|
|
|
source_code: String,
|
|
|
|
file_path: &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
|
|
|
external_linter_container: ExternalLinterContainer,
|
2024-07-25 09:07:59 -04:00
|
|
|
) -> 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(),
|
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
|
|
|
external_linter: external_linter_container.get_callback(),
|
2024-07-25 09:07:59 -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
|
|
|
if let Some(err) = external_linter_container.take_error() {
|
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
|
2024-07-25 09:07:59 -04:00
|
|
|
// 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(),
|
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
|
|
|
&source,
|
2024-07-25 09:07:59 -04:00
|
|
|
&diagnostics,
|
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
|
|
|
&external_linter_container,
|
2024-07-25 09:07:59 -04:00
|
|
|
)?;
|
|
|
|
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 ",
|
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
|
|
|
"probably a bug in the lint rule. Please fix this file manually.",
|
2024-07-25 09:07:59 -04:00
|
|
|
),
|
|
|
|
specifier,
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if fix_iterations > 0 {
|
|
|
|
// everything looks good and the file still parses, so write it out
|
|
|
|
atomic_write_file_with_retries(
|
2024-12-31 11:29:07 -05:00
|
|
|
&CliSys::default(),
|
2024-07-25 09:07:59 -04:00
|
|
|
file_path,
|
2024-12-30 12:38:20 -05:00
|
|
|
source.text().as_bytes(),
|
2024-07-25 09:07:59 -04:00
|
|
|
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,
|
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
|
|
|
original_source: &ParsedSource,
|
2024-07-25 09:07:59 -04:00
|
|
|
diagnostics: &[LintDiagnostic],
|
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
|
|
|
external_linter_container: &ExternalLinterContainer,
|
2024-07-25 09:07:59 -04:00
|
|
|
) -> Result<Option<(ParsedSource, Vec<LintDiagnostic>)>, AnyError> {
|
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 text_info = original_source.text_info_lazy();
|
2024-07-25 09:07:59 -04:00
|
|
|
let Some(new_text) = apply_lint_fixes(text_info, diagnostics) else {
|
|
|
|
return Ok(None);
|
|
|
|
};
|
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_with_text = |new_text: String| {
|
|
|
|
let (source, diagnostics) = linter.lint_file(LintFileOptions {
|
2024-07-25 09:07:59 -04:00
|
|
|
specifier: specifier.clone(),
|
|
|
|
source_code: new_text,
|
|
|
|
media_type,
|
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
|
|
|
config: config.clone(),
|
|
|
|
external_linter: external_linter_container.get_callback(),
|
|
|
|
})?;
|
|
|
|
let mut new_diagnostics = source.diagnostics().clone();
|
|
|
|
new_diagnostics.retain(|d| !original_source.diagnostics().contains(d));
|
|
|
|
if let Some(diagnostic) = new_diagnostics.pop() {
|
|
|
|
return Err(AnyError::from(diagnostic));
|
|
|
|
}
|
|
|
|
Ok((source, diagnostics))
|
|
|
|
};
|
|
|
|
|
|
|
|
let (source, diagnostics) = match lint_with_text(new_text) {
|
|
|
|
Ok(result) => result,
|
|
|
|
Err(err) => {
|
|
|
|
let utf16_map = Utf16Map::new(text_info.text_str());
|
|
|
|
// figure out which diagnostic caused a syntax error
|
|
|
|
let mut diagnostics = diagnostics.to_vec();
|
|
|
|
while let Some(last_diagnostic) = diagnostics.pop() {
|
|
|
|
let Some(lint_fix) = last_diagnostic.details.fixes.first() else {
|
|
|
|
continue;
|
|
|
|
};
|
|
|
|
let success = match apply_lint_fixes(text_info, &diagnostics) {
|
|
|
|
Some(new_text) => lint_with_text(new_text).is_ok(),
|
|
|
|
None => true,
|
|
|
|
};
|
|
|
|
if success {
|
|
|
|
let mut changes_text = String::new();
|
|
|
|
for change in &lint_fix.changes {
|
|
|
|
let utf8_start =
|
|
|
|
(change.range.start - text_info.range().start) as u32;
|
|
|
|
let utf8_end = (change.range.end - text_info.range().start) as u32;
|
|
|
|
let utf16_start = utf16_map
|
|
|
|
.utf8_to_utf16_offset(utf8_start.into())
|
|
|
|
.unwrap_or(utf8_start.into());
|
|
|
|
let utf16_end = utf16_map
|
|
|
|
.utf8_to_utf16_offset(utf8_end.into())
|
|
|
|
.unwrap_or(utf8_end.into());
|
|
|
|
changes_text.push_str(&format!(
|
|
|
|
"Range: [{}, {}]\n",
|
|
|
|
u32::from(utf16_start),
|
|
|
|
u32::from(utf16_end)
|
|
|
|
));
|
|
|
|
changes_text.push_str(&format!("Text: {:?}\n\n", &change.new_text));
|
|
|
|
}
|
|
|
|
return Err(err).context(format!(
|
|
|
|
"The '{}' rule caused a syntax error applying '{}'.\n\n{}",
|
|
|
|
last_diagnostic.details.code, lint_fix.description, changes_text
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Err(err).context(
|
|
|
|
"A lint fix caused a syntax error. This is a bug in a lint rule.",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(err) = external_linter_container.take_error() {
|
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Some((source, diagnostics)))
|
2024-07-25 09:07:59 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2024-08-15 13:43:04 -07:00
|
|
|
|
|
|
|
let mut import_fixes = HashSet::new();
|
2024-07-25 09:07:59 -04:00
|
|
|
// 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];
|
2024-08-15 13:43:04 -07:00
|
|
|
// hack: deduplicate import fixes to avoid creating errors
|
|
|
|
if previous.new_text.trim_start().starts_with("import ") {
|
|
|
|
import_fixes.insert(previous.new_text.trim().to_string());
|
|
|
|
}
|
|
|
|
let is_overlapping = cur.range.start <= previous.range.end;
|
|
|
|
if is_overlapping
|
|
|
|
|| (cur.new_text.trim_start().starts_with("import ")
|
|
|
|
&& import_fixes.contains(cur.new_text.trim()))
|
|
|
|
{
|
2024-07-25 09:07:59 -04:00
|
|
|
quick_fixes.remove(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let new_text =
|
|
|
|
deno_ast::apply_text_changes(text_info.text_str(), quick_fixes);
|
|
|
|
Some(new_text)
|
|
|
|
}
|
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
|
|
|
|
|
|
|
fn run_plugins(
|
|
|
|
plugin_runner: Arc<PluginHostProxy>,
|
|
|
|
parsed_source: ParsedSource,
|
|
|
|
file_path: PathBuf,
|
|
|
|
maybe_token: Option<CancellationToken>,
|
|
|
|
) -> Result<ExternalLinterResult, AnyError> {
|
|
|
|
let source_text_info = parsed_source.text_info_lazy().clone();
|
|
|
|
let plugin_info = plugin_runner
|
|
|
|
.get_plugin_rules()
|
|
|
|
.into_iter()
|
|
|
|
.map(Cow::from)
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
let fut = async move {
|
|
|
|
let utf16_map = Utf16Map::new(parsed_source.text().as_ref());
|
|
|
|
let serialized_ast =
|
|
|
|
plugin_runner.serialize_ast(&parsed_source, &utf16_map)?;
|
|
|
|
|
|
|
|
plugins::run_rules_for_ast(
|
|
|
|
&plugin_runner,
|
|
|
|
&file_path,
|
|
|
|
serialized_ast,
|
|
|
|
source_text_info,
|
|
|
|
utf16_map,
|
|
|
|
maybe_token,
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
.boxed_local();
|
|
|
|
|
|
|
|
let plugin_diagnostics = tokio_util::create_and_run_current_thread(fut)?;
|
|
|
|
|
|
|
|
Ok(ExternalLinterResult {
|
|
|
|
diagnostics: plugin_diagnostics,
|
|
|
|
rules: plugin_info,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
struct ExternalLinterContainer {
|
|
|
|
cb: Option<ExternalLinterCb>,
|
|
|
|
error: Option<Arc<Mutex<Option<AnyError>>>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ExternalLinterContainer {
|
|
|
|
pub fn new(
|
|
|
|
maybe_plugin_runner: Option<Arc<PluginHostProxy>>,
|
|
|
|
maybe_token: Option<CancellationToken>,
|
|
|
|
) -> Self {
|
|
|
|
let mut s = Self {
|
|
|
|
cb: None,
|
|
|
|
error: None,
|
|
|
|
};
|
|
|
|
if let Some(plugin_runner) = maybe_plugin_runner {
|
|
|
|
s.error = Some(Arc::new(Mutex::new(None)));
|
|
|
|
let error_ = s.error.clone();
|
|
|
|
let cb = Arc::new(move |parsed_source: ParsedSource| {
|
|
|
|
let token_ = maybe_token.clone();
|
|
|
|
let file_path =
|
|
|
|
match deno_path_util::url_to_file_path(parsed_source.specifier()) {
|
|
|
|
Ok(path) => path,
|
|
|
|
Err(err) => {
|
|
|
|
*error_.as_ref().unwrap().lock() = Some(err.into());
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let r =
|
|
|
|
run_plugins(plugin_runner.clone(), parsed_source, file_path, token_);
|
|
|
|
|
|
|
|
match r {
|
|
|
|
Ok(d) => Some(d),
|
|
|
|
Err(err) => {
|
|
|
|
*error_.as_ref().unwrap().lock() = Some(err);
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
s.cb = Some(cb);
|
|
|
|
}
|
|
|
|
s
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_callback(&self) -> Option<ExternalLinterCb> {
|
|
|
|
self.cb.clone()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn take_error(&self) -> Option<AnyError> {
|
|
|
|
self.error.as_ref().and_then(|e| e.lock().take())
|
|
|
|
}
|
|
|
|
}
|