diff --git a/crates/oxc_linter/src/context/host.rs b/crates/oxc_linter/src/context/host.rs index 2ead2913c21f77..a39bcd06fcc46a 100644 --- a/crates/oxc_linter/src/context/host.rs +++ b/crates/oxc_linter/src/context/host.rs @@ -34,7 +34,11 @@ use super::{plugin_name_to_prefix, LintContext}; #[must_use] #[non_exhaustive] pub(crate) struct ContextHost<'a> { + /// Shared semantic information about the file being linted, which includes scopes, symbols + /// and AST nodes. See [`Semantic`]. pub(super) semantic: Rc>, + /// Information about specific rules that should be disabled or enabled, via comment directives like + /// `eslint-disable` or `eslint-disable-next-line`. pub(super) disable_directives: DisableDirectives<'a>, /// Diagnostics reported by the linter. /// @@ -46,9 +50,14 @@ pub(crate) struct ContextHost<'a> { /// Set via the `--fix`, `--fix-suggestions`, and `--fix-dangerously` CLI /// flags. pub(super) fix: FixKind, + /// Path to the file being linted. pub(super) file_path: Box, + /// Global linter configuration, such as globals to include and the target + /// environments, and other settings. pub(super) config: Arc, + /// Front-end frameworks that might be in use in the target file. pub(super) frameworks: FrameworkFlags, + /// A list of all available linter plugins. pub(super) plugins: LintPlugins, } @@ -88,12 +97,14 @@ impl<'a> ContextHost<'a> { .sniff_for_frameworks() } + /// Set the linter configuration for this context. #[inline] pub fn with_config(mut self, config: &Arc) -> Self { self.config = Arc::clone(config); self } + /// Shared reference to the [`Semantic`] analysis of the file. #[inline] pub fn semantic(&self) -> &Semantic<'a> { &self.semantic @@ -115,12 +126,14 @@ impl<'a> ContextHost<'a> { self.semantic.source_type() } + /// Add a diagnostic message to the end of the list of diagnostics. Can be used + /// by any rule to report issues. #[inline] pub(super) fn push_diagnostic(&self, diagnostic: Message<'a>) { self.diagnostics.borrow_mut().push(diagnostic); } - /// Take all diagnostics collected during linting. + /// Take ownership of all diagnostics collected during linting. pub fn take_diagnostics(&self) -> Vec> { // NOTE: diagnostics are only ever borrowed here and in push_diagnostic. // The latter drops the reference as soon as the function returns, so @@ -129,6 +142,7 @@ impl<'a> ContextHost<'a> { std::mem::take(&mut *messages) } + /// Creates a new [`LintContext`] for a specific rule. pub fn spawn(self: Rc, rule: &RuleWithSeverity) -> LintContext<'a> { let rule_name = rule.name(); let plugin_name = self.map_jest(rule.plugin_name(), rule_name); @@ -144,6 +158,7 @@ impl<'a> ContextHost<'a> { } } + /// Creates a new [`LintContext`] for testing purposes only. #[cfg(test)] pub(crate) fn spawn_for_test(self: Rc) -> LintContext<'a> { LintContext { @@ -157,6 +172,10 @@ impl<'a> ContextHost<'a> { } } + /// Maps Jest rule names and maps to Vitest rules when possible, returning the original plugin otherwise. + /// + /// Many Vitest rules are essentially ports of the Jest plugin rules with minor modifications. + /// For these rules, we use the corresponding jest rules with some adjustments for compatibility. fn map_jest(&self, plugin_name: &'static str, rule_name: &str) -> &'static str { if self.plugins.has_vitest() && plugin_name == "jest" diff --git a/crates/oxc_linter/src/context/mod.rs b/crates/oxc_linter/src/context/mod.rs index fc8484f1811b94..f2ac4724293f1b 100644 --- a/crates/oxc_linter/src/context/mod.rs +++ b/crates/oxc_linter/src/context/mod.rs @@ -22,17 +22,28 @@ pub(crate) use host::ContextHost; #[derive(Clone)] #[must_use] +/// Contains all of the state and context specific to this lint rule. Includes information +/// like the rule name, plugin name, and severity of the rule. It also has a reference to +/// the shared linting data [`ContextHost`], which is the same for all rules. pub struct LintContext<'a> { /// Shared context independent of the rule being linted. parent: Rc>, - - // states + /// Name of the plugin this rule belongs to. Example: `eslint`, `unicorn`, `react` current_plugin_name: &'static str, + /// Prefixed version of the plugin name. Examples: + /// - `eslint-plugin-react`, for `react` plugin, + /// - `typescript-eslint`, for `typescript` plugin, + /// - `eslint-plugin-import`, for `import` plugin. current_plugin_prefix: &'static str, + /// Kebab-cased name of the current rule being linted. Example: `no-unused-vars`, `no-undef`. current_rule_name: &'static str, #[cfg(debug_assertions)] + /// Capabilities of the current rule to fix issues. Indicates whether: + /// - Rule cannot be auto-fixed [`RuleFixMeta::None`] + /// - Rule needs an auto-fix to be written still [`RuleFixMeta::FixPending`] + /// - Rule can be fixed in some cases [`RuleFixMeta::Conditional`] + /// - Rule is fully auto-fixable [`RuleFixMeta::Fixable`] current_rule_fix_capabilities: RuleFixMeta, - /// Current rule severity. Allows for user severity overrides, e.g. /// ```json /// // .oxlintrc.json @@ -46,20 +57,24 @@ pub struct LintContext<'a> { } impl<'a> LintContext<'a> { + /// Base URL for the documentation, used to generate rule documentation URLs when a diagnostic is reported. const WEBSITE_BASE_URL: &'static str = "https://oxc.rs/docs/guide/usage/linter/rules"; + /// Set the plugin name for the current rule. pub fn with_plugin_name(mut self, plugin: &'static str) -> Self { self.current_plugin_name = plugin; self.current_plugin_prefix = plugin_name_to_prefix(plugin); self } + /// Set the current rule name. Name should be kebab-cased like: `no-unused-vars` or `no-undef`. pub fn with_rule_name(mut self, name: &'static str) -> Self { self.current_rule_name = name; self } #[cfg(debug_assertions)] + /// Set the current rule fix capabilities. See [`RuleFixMeta`] for more information. pub fn with_rule_fix_capabilities(mut self, capabilities: RuleFixMeta) -> Self { self.current_rule_fix_capabilities = capabilities; self @@ -83,6 +98,7 @@ impl<'a> LintContext<'a> { } #[inline] + /// Get the control flow graph for the current program. pub fn cfg(&self) -> &ControlFlowGraph { // SAFETY: `LintContext::new` is the only way to construct a `LintContext` and we always // assert the existence of control flow so it should always be `Some`. @@ -90,6 +106,7 @@ impl<'a> LintContext<'a> { } #[inline] + /// List of all disable directives in the file being linted. pub fn disable_directives(&self) -> &DisableDirectives<'a> { &self.parent.disable_directives } @@ -125,6 +142,7 @@ impl<'a> LintContext<'a> { } #[inline] + /// Sets of global variables that have been enabled or disabled. pub fn globals(&self) -> &OxlintGlobals { &self.parent.config.globals } @@ -137,6 +155,12 @@ impl<'a> LintContext<'a> { &self.parent.config.env } + /// Checks if a given variable named is defined as a global variable in the current environment. + /// + /// Example: + /// - `env_contains_var("Date")` returns `true` because it is a global builtin in all environments. + /// - `env_contains_var("HTMLElement")` returns `true` only if the `browser` environment is enabled. + /// - `env_contains_var("globalThis")` returns `true` only if the `es2020` environment or higher is enabled. pub fn env_contains_var(&self, var: &str) -> bool { if GLOBALS["builtin"].contains_key(var) { return true; @@ -153,6 +177,8 @@ impl<'a> LintContext<'a> { /* Diagnostics */ + /// Add a diagnostic message to the list of diagnostics. Outputs a diagnostic with the current rule + /// name, severity, and a link to the rule's documentation URL. fn add_diagnostic(&self, mut message: Message<'a>) { if self.parent.disable_directives.contains(self.current_rule_name, message.span()) { return; @@ -251,6 +277,12 @@ impl<'a> LintContext<'a> { self.diagnostic_with_fix_of_kind(diagnostic, FixKind::DangerousFix, fix); } + /// Report a lint rule violation and provide an automatic fix of a specific kind. + /// + /// The second argument is a [closure] that takes a [`RuleFixer`] and + /// returns something that can turn into a [`RuleFix`]. + /// + /// [closure]: #[allow(clippy::missing_panics_doc)] // only panics in debug mode pub fn diagnostic_with_fix_of_kind( &self, @@ -285,6 +317,7 @@ impl<'a> LintContext<'a> { } } + /// Framework flags, indicating front-end frameworks that might be in use. pub fn frameworks(&self) -> FrameworkFlags { self.parent.frameworks } @@ -325,11 +358,19 @@ impl<'a> LintContext<'a> { } } +/// Gets the prefixed plugin name, given the short plugin name. +/// +/// Example: +/// +/// ```rust +/// assert_eq!(plugin_name_to_prefix("react"), "eslint-plugin-react"); +/// ``` #[inline] fn plugin_name_to_prefix(plugin_name: &'static str) -> &'static str { PLUGIN_PREFIXES.get(plugin_name).copied().unwrap_or(plugin_name) } +/// Map of plugin names to their prefixed versions. const PLUGIN_PREFIXES: phf::Map<&'static str, &'static str> = phf::phf_map! { "import" => "eslint-plugin-import", "jest" => "eslint-plugin-jest",