Skip to content

Commit

Permalink
feat(lint): add JavaScript plugin support (#27203)
Browse files Browse the repository at this point in the history
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 <[email protected]>
Co-authored-by: David Sherret <[email protected]>
  • Loading branch information
3 people authored Feb 5, 2025
1 parent 8a07d38 commit f08ca64
Show file tree
Hide file tree
Showing 53 changed files with 3,521 additions and 1,796 deletions.
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ deno_doc = { version = "=0.164.0", features = ["rust", "comrak"] }
deno_error.workspace = true
deno_graph = { version = "=0.87.2" }
deno_lib.workspace = true
deno_lint = { version = "0.70.0" }
deno_lint = { version = "0.71.0" }
deno_lockfile.workspace = true
deno_media_type = { workspace = true, features = ["data_url", "decoding", "module_specifier"] }
deno_npm.workspace = true
Expand Down
1 change: 1 addition & 0 deletions cli/args/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,7 @@ impl DenoSubcommand {
| Self::Jupyter(_)
| Self::Repl(_)
| Self::Bench(_)
| Self::Lint(_)
| Self::Lsp
)
}
Expand Down
42 changes: 32 additions & 10 deletions cli/args/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ pub struct LintOptions {
pub rules: LintRulesConfig,
pub files: FilePatterns,
pub fix: bool,
pub plugins: Vec<Url>,
}

impl Default for LintOptions {
Expand All @@ -380,20 +381,41 @@ impl LintOptions {
rules: Default::default(),
files: FilePatterns::new_with_base(base),
fix: false,
plugins: vec![],
}
}

pub fn resolve(lint_config: LintConfig, lint_flags: &LintFlags) -> Self {
Self {
pub fn resolve(
dir_path: PathBuf,
lint_config: LintConfig,
lint_flags: &LintFlags,
) -> Result<Self, AnyError> {
let rules = resolve_lint_rules_options(
lint_config.options.rules,
lint_flags.maybe_rules_tags.clone(),
lint_flags.maybe_rules_include.clone(),
lint_flags.maybe_rules_exclude.clone(),
);

let plugins = {
let plugin_specifiers = lint_config.options.plugins;
let mut plugins = Vec::with_capacity(plugin_specifiers.len());
for plugin in &plugin_specifiers {
// TODO(bartlomieju): handle import-mapped specifiers
let url = resolve_url_or_path(plugin, &dir_path)?;
plugins.push(url);
}
// ensure stability for hasher
plugins.sort_unstable();
plugins
};

Ok(Self {
files: lint_config.files,
rules: resolve_lint_rules_options(
lint_config.options.rules,
lint_flags.maybe_rules_tags.clone(),
lint_flags.maybe_rules_include.clone(),
lint_flags.maybe_rules_exclude.clone(),
),
rules,
fix: lint_flags.fix,
}
plugins,
})
}
}

Expand Down Expand Up @@ -759,7 +781,7 @@ impl CliOptions {
.resolve_lint_config_for_members(&cli_arg_patterns)?;
let mut result = Vec::with_capacity(member_configs.len());
for (ctx, config) in member_configs {
let options = LintOptions::resolve(config, lint_flags);
let options = LintOptions::resolve(ctx.dir_path(), config, lint_flags)?;
result.push((ctx, options));
}
Ok(result)
Expand Down
Loading

0 comments on commit f08ca64

Please sign in to comment.