From adc25f85a15e3e22342da5917c68c822e954809a Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Fri, 3 Jan 2025 11:03:09 +0100 Subject: [PATCH] feat: support excluding rules from lint plugins --- cli/js/40_lint.js | 16 +- cli/js/40_lint_types.d.ts | 2 + cli/lsp/config.rs | 1 + cli/schemas/lint-rules.v1.json | 224 +++++++++--------- cli/tools/lint/mod.rs | 11 +- cli/tools/lint/plugins.rs | 27 ++- tests/specs/lint/lint_plugin/__test__.jsonc | 5 +- .../specs/lint/lint_plugin/deno_exclude.json | 10 + tests/specs/lint/lint_plugin/lint_exclude.out | 1 + 9 files changed, 178 insertions(+), 119 deletions(-) create mode 100644 tests/specs/lint/lint_plugin/deno_exclude.json create mode 100644 tests/specs/lint/lint_plugin/lint_exclude.out diff --git a/cli/js/40_lint.js b/cli/js/40_lint.js index 091cd7a998..44a5b06c09 100644 --- a/cli/js/40_lint.js +++ b/cli/js/40_lint.js @@ -63,6 +63,7 @@ const PropFlags = { const state = { plugins: [], installedPlugins: new Set(), + ignoredRules: new Set(), }; /** @@ -197,8 +198,9 @@ export class Context { /** * @param {LintPlugin} plugin + * @param {string[]} exclude */ -export function installPlugin(plugin) { +export function installPlugin(plugin, exclude) { if (typeof plugin !== "object") { throw new Error("Linter plugin must be an object"); } @@ -214,6 +216,12 @@ export function installPlugin(plugin) { state.plugins.push(plugin); state.installedPlugins.add(plugin.name); + // TODO(@marvinhagemeister): This should be done once instead of + // for every plugin + for (let i = 0; i < exclude.length; i++) { + state.ignoredRules.add(exclude[i]); + } + return { name: plugin.name, ruleNames: Object.keys(plugin.rules), @@ -921,6 +929,12 @@ export function runPluginsForFile(fileName, serializedAst) { for (const name of Object.keys(plugin.rules)) { const rule = plugin.rules[name]; const id = `${plugin.name}/${name}`; + + // Check if this rule is excluded + if (state.ignoredRules.has(id)) { + continue; + } + const ctx = new Context(id, fileName); const visitor = rule.create(ctx); diff --git a/cli/js/40_lint_types.d.ts b/cli/js/40_lint_types.d.ts index 37aa95ec95..2106562f92 100644 --- a/cli/js/40_lint_types.d.ts +++ b/cli/js/40_lint_types.d.ts @@ -70,6 +70,8 @@ export interface LintPlugin { export interface LintState { plugins: LintPlugin[]; installedPlugins: Set; + /** format: `/` */ + ignoredRules: Set; } export type VisitorFn = (node: unknown) => void; diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index 40151a1332..fc77eb2468 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -1643,6 +1643,7 @@ impl ConfigData { crate::tools::lint::create_runner_and_load_plugins( plugin_specifiers, logger, + lint_options.rules.exclude.clone(), ) .await; // eprintln!("plugin load result {:#?}", plugin_load_result); diff --git a/cli/schemas/lint-rules.v1.json b/cli/schemas/lint-rules.v1.json index 71d1784958..48885bcd0b 100644 --- a/cli/schemas/lint-rules.v1.json +++ b/cli/schemas/lint-rules.v1.json @@ -1,112 +1,120 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "enum": [ - "adjacent-overload-signatures", - "ban-ts-comment", - "ban-types", - "ban-unknown-rule-code", - "ban-untagged-ignore", - "ban-untagged-todo", - "ban-unused-ignore", - "camelcase", - "constructor-super", - "default-param-last", - "eqeqeq", - "explicit-function-return-type", - "explicit-module-boundary-types", - "for-direction", - "fresh-handler-export", - "fresh-server-event-handlers", - "getter-return", - "guard-for-in", - "no-array-constructor", - "no-async-promise-executor", - "no-await-in-loop", - "no-await-in-sync-fn", - "no-boolean-literal-for-arguments", - "no-case-declarations", - "no-class-assign", - "no-compare-neg-zero", - "no-cond-assign", - "no-console", - "no-const-assign", - "no-constant-condition", - "no-control-regex", - "no-debugger", - "no-delete-var", - "no-deprecated-deno-api", - "no-dupe-args", - "no-dupe-class-members", - "no-dupe-else-if", - "no-dupe-keys", - "no-duplicate-case", - "no-empty", - "no-empty-character-class", - "no-empty-enum", - "no-empty-interface", - "no-empty-pattern", - "no-eval", - "no-ex-assign", - "no-explicit-any", - "no-external-import", - "no-extra-boolean-cast", - "no-extra-non-null-assertion", - "no-fallthrough", - "no-func-assign", - "no-global-assign", - "no-implicit-declare-namespace-export", - "no-import-assertions", - "no-import-assign", - "no-inferrable-types", - "no-inner-declarations", - "no-invalid-regexp", - "no-invalid-triple-slash-reference", - "no-irregular-whitespace", - "no-misused-new", - "no-namespace", - "no-new-symbol", - "no-node-globals", - "no-non-null-asserted-optional-chain", - "no-non-null-assertion", - "no-obj-calls", - "no-octal", - "no-process-globals", - "no-prototype-builtins", - "no-redeclare", - "no-regex-spaces", - "no-self-assign", - "no-self-compare", - "no-setter-return", - "no-shadow-restricted-names", - "no-sloppy-imports", - "no-slow-types", - "no-sparse-arrays", - "no-sync-fn-in-async-fn", - "no-this-alias", - "no-this-before-super", - "no-throw-literal", - "no-top-level-await", - "no-undef", - "no-unreachable", - "no-unsafe-finally", - "no-unsafe-negation", - "no-unused-labels", - "no-unused-vars", - "no-var", - "no-window", - "no-window-prefix", - "no-with", - "prefer-as-const", - "prefer-ascii", - "prefer-const", - "prefer-namespace-keyword", - "prefer-primordials", - "require-await", - "require-yield", - "single-var-declarator", - "triple-slash-reference", - "use-isnan", - "valid-typeof", - "verbatim-module-syntax" + "oneOf": [ + { + "type": "string", + "pattern": "^[a-z0-9-]+\\/[a-z0-9-]+$" + }, + { + "enum": [ + "adjacent-overload-signatures", + "ban-ts-comment", + "ban-types", + "ban-unknown-rule-code", + "ban-untagged-ignore", + "ban-untagged-todo", + "ban-unused-ignore", + "camelcase", + "constructor-super", + "default-param-last", + "eqeqeq", + "explicit-function-return-type", + "explicit-module-boundary-types", + "for-direction", + "fresh-handler-export", + "fresh-server-event-handlers", + "getter-return", + "guard-for-in", + "no-array-constructor", + "no-async-promise-executor", + "no-await-in-loop", + "no-await-in-sync-fn", + "no-boolean-literal-for-arguments", + "no-case-declarations", + "no-class-assign", + "no-compare-neg-zero", + "no-cond-assign", + "no-console", + "no-const-assign", + "no-constant-condition", + "no-control-regex", + "no-debugger", + "no-delete-var", + "no-deprecated-deno-api", + "no-dupe-args", + "no-dupe-class-members", + "no-dupe-else-if", + "no-dupe-keys", + "no-duplicate-case", + "no-empty", + "no-empty-character-class", + "no-empty-enum", + "no-empty-interface", + "no-empty-pattern", + "no-eval", + "no-ex-assign", + "no-explicit-any", + "no-external-import", + "no-extra-boolean-cast", + "no-extra-non-null-assertion", + "no-fallthrough", + "no-func-assign", + "no-global-assign", + "no-implicit-declare-namespace-export", + "no-import-assertions", + "no-import-assign", + "no-inferrable-types", + "no-inner-declarations", + "no-invalid-regexp", + "no-invalid-triple-slash-reference", + "no-irregular-whitespace", + "no-misused-new", + "no-namespace", + "no-new-symbol", + "no-node-globals", + "no-non-null-asserted-optional-chain", + "no-non-null-assertion", + "no-obj-calls", + "no-octal", + "no-process-globals", + "no-prototype-builtins", + "no-redeclare", + "no-regex-spaces", + "no-self-assign", + "no-self-compare", + "no-setter-return", + "no-shadow-restricted-names", + "no-sloppy-imports", + "no-slow-types", + "no-sparse-arrays", + "no-sync-fn-in-async-fn", + "no-this-alias", + "no-this-before-super", + "no-throw-literal", + "no-top-level-await", + "no-undef", + "no-unreachable", + "no-unsafe-finally", + "no-unsafe-negation", + "no-unused-labels", + "no-unused-vars", + "no-var", + "no-window", + "no-window-prefix", + "no-with", + "prefer-as-const", + "prefer-ascii", + "prefer-const", + "prefer-namespace-keyword", + "prefer-primordials", + "require-await", + "require-yield", + "single-var-declarator", + "triple-slash-reference", + "use-isnan", + "valid-typeof", + "verbatim-module-syntax" + ] + } ] } diff --git a/cli/tools/lint/mod.rs b/cli/tools/lint/mod.rs index 6161410d76..5bf7c40880 100644 --- a/cli/tools/lint/mod.rs +++ b/cli/tools/lint/mod.rs @@ -286,6 +286,8 @@ impl WorkspaceLinter { ) -> Result<(), AnyError> { self.file_count += paths.len(); + let exclude = lint_options.rules.exclude.clone(); + let maybe_plugin_specifiers = lint_options.resolve_lint_plugins()?; let lint_rules = self.lint_rule_provider.resolve_lint_rules_err_empty( lint_options.rules, @@ -311,9 +313,12 @@ impl WorkspaceLinter { let mut plugin_runner = None; if let Some(plugin_specifiers) = maybe_plugin_specifiers { let logger = plugins::PluginLogger::new(logger_printer, true); - let runner = - plugins::create_runner_and_load_plugins(plugin_specifiers, logger) - .await?; + let runner = plugins::create_runner_and_load_plugins( + plugin_specifiers, + logger, + exclude, + ) + .await?; plugin_runner = Some(Arc::new(Mutex::new(runner))); } diff --git a/cli/tools/lint/plugins.rs b/cli/tools/lint/plugins.rs index 5e5d8628e4..a41f02940b 100644 --- a/cli/tools/lint/plugins.rs +++ b/cli/tools/lint/plugins.rs @@ -34,7 +34,7 @@ use crate::tools::lint::serialize_ast_to_buffer; #[derive(Debug)] pub enum PluginHostRequest { - LoadPlugins(Vec), + LoadPlugins(Vec, Option>), Run(Vec, PathBuf, SourceTextInfo), } @@ -272,8 +272,8 @@ impl PluginHost { while let Some(req) = self.rx.recv().await { self.logger.log("received message"); match req { - PluginHostRequest::LoadPlugins(specifiers) => { - let r = self.load_plugins(specifiers).await; + PluginHostRequest::LoadPlugins(specifiers, exclude) => { + let r = self.load_plugins(specifiers, exclude).await; let _ = self.tx.send(PluginHostResponse::LoadPlugin(r)).await; } PluginHostRequest::Run(serialized_ast, specifier, source_text_info) => { @@ -361,6 +361,7 @@ impl PluginHost { async fn load_plugins( &mut self, plugin_specifiers: Vec, + exclude: Option>, ) -> Result, AnyError> { let mut load_futures = Vec::with_capacity(plugin_specifiers.len()); for specifier in plugin_specifiers { @@ -393,7 +394,19 @@ impl PluginHost { let install_plugins_local = v8::Local::new(scope, &*self.install_plugin_fn.clone()); let undefined = v8::undefined(scope); - let args = &[default_export]; + + let exclude_v8: v8::Local = + exclude.clone().map_or(v8::null(scope).into(), |v| { + let elems = v + .iter() + .map(|item| v8::String::new(scope, item).unwrap().into()) + .collect::>(); + + v8::Array::new_with_elements(scope, elems.as_slice()).into() + }); + + let args = &[default_export, exclude_v8]; + self.logger.log("Installing plugin..."); // TODO(bartlomieju): do it in a try/catch scope let plugin_info = install_plugins_local @@ -412,10 +425,11 @@ impl PluginHostProxy { pub async fn load_plugins( &self, plugin_specifiers: Vec, + exclude: Option>, ) -> Result<(), AnyError> { self .tx - .send(PluginHostRequest::LoadPlugins(plugin_specifiers)) + .send(PluginHostRequest::LoadPlugins(plugin_specifiers, exclude)) .await?; let mut rx = self.rx.lock().await; self.logger.log("receiving load plugins"); @@ -472,9 +486,10 @@ impl PluginHostProxy { pub async fn create_runner_and_load_plugins( plugin_specifiers: Vec, logger: PluginLogger, + exclude: Option>, ) -> Result { let host_proxy = PluginHost::create(logger)?; - host_proxy.load_plugins(plugin_specifiers).await?; + host_proxy.load_plugins(plugin_specifiers, exclude).await?; Ok(host_proxy) } diff --git a/tests/specs/lint/lint_plugin/__test__.jsonc b/tests/specs/lint/lint_plugin/__test__.jsonc index 91e608e400..228923b2ac 100644 --- a/tests/specs/lint/lint_plugin/__test__.jsonc +++ b/tests/specs/lint/lint_plugin/__test__.jsonc @@ -1,11 +1,14 @@ { - "tempDir": true, "steps": [ { "args": "lint a.ts", "output": "lint.out", "exitCode": 1 }, + { + "args": "lint -c deno_exclude.json a.ts", + "output": "lint_exclude.out" + }, { "args": "lint --fix a.ts", "output": "lint_fixed.out" diff --git a/tests/specs/lint/lint_plugin/deno_exclude.json b/tests/specs/lint/lint_plugin/deno_exclude.json new file mode 100644 index 0000000000..cce33a8736 --- /dev/null +++ b/tests/specs/lint/lint_plugin/deno_exclude.json @@ -0,0 +1,10 @@ +{ + "lint": { + "plugins": ["./plugin.ts"], + "rules": { + "exclude": [ + "test-plugin/my-rule" + ] + } + } +} diff --git a/tests/specs/lint/lint_plugin/lint_exclude.out b/tests/specs/lint/lint_plugin/lint_exclude.out new file mode 100644 index 0000000000..c05ac45a1e --- /dev/null +++ b/tests/specs/lint/lint_plugin/lint_exclude.out @@ -0,0 +1 @@ +Checked 1 file