harper-core 2.0.0

The language checker for developers.
Documentation
use crate::{
    // EDIT You won't need `TokenStringExt` unless you need a span covering multiple tokens.
    Lint,
    Token,
    TokenStringExt,
    // EDIT `SequenceExpr` is the most versatile `Expr` but you can use any `Expr` or `Pattern`
    expr::{Expr, SequenceExpr},
    // EDIT `expr_linter::Chunk` is a run of tokens between commas and is the default.
    // EDIT But if you want to match a pattern containing commas, use `Sentence` instead.
    linting::{ExprLinter, LintKind, Suggestion, debug::format_lint_match, expr_linter::Chunk},
};

// EDIT rename this struct for your new linter
pub struct ExprLinterSkeleton {
    // EDIT `SequenceExpr` is the most versatile `Expr` but you can use any `Expr` or `Pattern`
    expr: SequenceExpr,
}

// EDIT If your linter doesn't need access to the dictionary and doesn't depend on the dialect
// EDIT   then just use `default()` as the only constructor.
// EDIT If you need dictionary or dialect access, use `impl ExprLinterSkeleton` and `fn new()`
// EDIT   instead of `impl Default for ExprLinterSkeleton` and `fn default()`
impl Default for ExprLinterSkeleton {
    fn default() -> Self {
        Self {
            // EDIT `SequenceExpr` is the most versatile `Expr` but you can use any `Expr` or `Pattern`.
            // EDIT `SequenceExpr` has many many useful methods from which you can build fairly complex
            // EDIT   expressions
            expr: SequenceExpr::any_capitalization_of("erorr"),
        }
    }
}

impl ExprLinter for ExprLinterSkeleton {
    type Unit = Chunk;

    // EDIT If you don't need the context before or after the matched tokens
    // EDIT   then use the simpler `fn match_to_lint()` instead.
    // EDIT There are some methods in `expr_linter` to help checking for words
    // EDIT   and punctuation in the "before" or "after" contexts.
    fn match_to_lint_with_context(
        &self,
        // NOTE Whitespace also uses a `Token`. What out for LLMs and agents that
        // NOTE   assume `Token`s are always words. "Hello World" is actually three
        // NOTE   tokens and "World" is `matched_tokens[2]`, not `[1]` as your LLM might assume.
        matched_tokens: &[Token],
        source: &[char],
        context: Option<(&[Token], &[Token])>,
    ) -> Option<Lint> {
        // EDIT A debug printf here while developing is very handy for verifying
        // EDIT   that your `Expr` above is matching what you expect
        // EDIT   make sure you remove this before committing
        eprintln!("🚨 {}", format_lint_match(matched_tokens, context, source));

        // EDIT Place your custom linter logic here.
        // EDIT If your `Expr` can sometimes match

        // EDIT `span` is the range of tokens that you will modify (or insert after).
        // EDIT It is also the range that will be underlined etc.
        // EDIT Each `Token` has a `.span` or you get get a `Span` of a slice of `Token`s
        // EDIT   using `.span()` from `TokenStringExt`.
        // EDIT Note that every `Suggestion` for a single `Lint` must use the same span.
        // EDIT This is important if you are correcting a phrase by suggesting one change
        // EDIT   to one word, or a different change to a different word. In this case
        // EDIT   your `span` will need to cover at least both of those words.
        let span = matched_tokens.span()?;

        // EDIT Look in `harper-core/src/linting/lint_kind.rs` for the lint kinds available
        // EDIT   with their descriptions to help you decide which is appropriate.
        let lint_kind = LintKind::Miscellaneous;

        // EDIT It may not be practical to suggest a correction. In such cases, use `vec![]`.
        // EDIT Otherwise you can offer one or several suggestions. Each suggestion must
        // EDIT   operate on the same `Span`. You can replace the `Span`, remove the `Span`,
        // EDIT   or insert after the `Span`. When replacing text you will normally want to
        // EDIT   maintain how it used uppercase vs lowercase letter. So `replace_with_match_case`.
        // EDIT In that case you need to pass the `template` as well as the new `value`.
        // EDIT To get the template, you pass the original text as a `&[char]` slice.
        // EDIT It's worth keeping in mind that though there are some helper functions that
        // EDIT   work with `String` or `&str` or string literals, most of this infrastructure
        // EDIT   natively works with `Vec<char>` or `&[char]`.
        let suggestions = vec![Suggestion::replace_with_match_case_str(
            "correction",
            span.get_content(source),
        )];

        // EDIT You can return different messages depending on what the problem is and what
        // EDIT   the suggestions are.
        // EDIT If it's not possible to be 100% sure that only real mistakes are flagged
        // EDIT   and there's a likelihood of some non-mistakes being flagged as false positives
        // EDIT   your message should allow for both possibilities.
        // EDIT Likewise, if two or more suggestions are very different, as happens with
        // EDIT   confusable words, it's a good idea to guide the user with concise definitions.
        let message = "Fix this erorr".to_string();

        // EDIT You can return different `Lint`s from different places in your logic.
        Some(Lint {
            span,
            lint_kind,
            suggestions,
            message,
            // EDIT `priority` is not that well defined yet. Feel free to use `..Default::default`
            // EDIT   for now. Note that if you do, you mustn't use a trailing comma.
            ..Default::default()
        })
    }

    fn expr(&self) -> &dyn Expr {
        &self.expr
    }

    fn description(&self) -> &str {
        "A linter skeleton for contributors to copy into `harper_core/src/linting/` and rename."
    }
}

#[cfg(test)]
mod tests {
    // EDIT There's a bunch more useful assertions for unit tests in `linting::tests`.
    use crate::linting::tests::assert_suggestion_result;

    use super::ExprLinterSkeleton;

    #[test]
    fn test_skeleton() {
        assert_suggestion_result("erorr", ExprLinterSkeleton::default(), "correction");
    }
}