Skip to content

Commit

Permalink
docs(linter): add docs for ContextHost and LintContext
Browse files Browse the repository at this point in the history
  • Loading branch information
camchenry committed Oct 4, 2024
1 parent 2f888ed commit 90a4532
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 4 deletions.
21 changes: 20 additions & 1 deletion crates/oxc_linter/src/context/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Semantic<'a>>,
/// 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.
///
Expand All @@ -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<Path>,
/// Global linter configuration, such as globals to include and the target
/// environments, and other settings.
pub(super) config: Arc<LintConfig>,
/// 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,
}

Expand Down Expand Up @@ -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<LintConfig>) -> 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
Expand All @@ -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<Message<'a>> {
// NOTE: diagnostics are only ever borrowed here and in push_diagnostic.
// The latter drops the reference as soon as the function returns, so
Expand All @@ -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<Self>, rule: &RuleWithSeverity) -> LintContext<'a> {
let rule_name = rule.name();
let plugin_name = self.map_jest(rule.plugin_name(), rule_name);
Expand All @@ -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<Self>) -> LintContext<'a> {
LintContext {
Expand All @@ -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"
Expand Down
47 changes: 44 additions & 3 deletions crates/oxc_linter/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ContextHost<'a>>,

// 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
Expand All @@ -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
Expand All @@ -83,13 +98,15 @@ 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`.
unsafe { self.semantic().cfg().unwrap_unchecked() }
}

#[inline]
/// List of all disable directives in the file being linted.
pub fn disable_directives(&self) -> &DisableDirectives<'a> {
&self.parent.disable_directives
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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]: <https://doc.rust-lang.org/book/ch13-01-closures.html>
#[allow(clippy::missing_panics_doc)] // only panics in debug mode
pub fn diagnostic_with_fix_of_kind<C, F>(
&self,
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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",
Expand Down

0 comments on commit 90a4532

Please sign in to comment.