From 8c86bea82284110787fba9bceb4dc43e90bddb86 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Thu, 23 Jan 2025 15:15:58 -0700 Subject: [PATCH 1/2] docs: rewrote instructions on how to author a rule --- .../docs/contributors/author-a-rule/+page.md | 150 ++++++++++++++++-- 1 file changed, 135 insertions(+), 15 deletions(-) diff --git a/packages/web/src/routes/docs/contributors/author-a-rule/+page.md b/packages/web/src/routes/docs/contributors/author-a-rule/+page.md index b9cdf002..8d87905d 100644 --- a/packages/web/src/routes/docs/contributors/author-a-rule/+page.md +++ b/packages/web/src/routes/docs/contributors/author-a-rule/+page.md @@ -6,23 +6,143 @@ title: Author a Rule Before we get into how to write a rule, it is important that we get some of the language cleared up. When we refer to a Harper rule, we are talking about [an implementation of the Linter trait](https://docs.rs/harper-core/latest/harper_core/linting/trait.Linter.html). -As you can see, there is an enormous amount of flexibility in this trait and a wide variety of strategies for querying the provided document. +As you can see, there is an enormous amount of flexibility in this trait and a wide variety of potential strategies for querying the provided document to locate errors. -## Patterns +This guide will go through one easy way to add a rule to Harper. +The lofty goal is for this to be doable by someone with little to no Rust experience. +You should, however, be able to figure out how to use Git. -For new contributors, defining a pattern and [implementing the PatternLinter trait](https://docs.rs/harper-core/latest/harper_core/linting/trait.PatternLinter.html) is the easiest way to get started. -If you like to learn by example, you can make a copy of the `ThatWhich` rule and modify it to look for something else. -Here's the general playbook: +## Fork the Harper Monorepo -- Start by forking the [Harper monorepo](https://github.com/Automattic/harper/fork) and create a new file under `harper-core/src/linting` called `my_rule.rs`. -- Follow our [guide to get your environment set up.](./environment) -- Copy in a template rule (like from `that_which.rs`). -- Modify the constructor to create a pattern to look for the problematic text you have in mind. -- Export your rule from the `linting module` ([which you can find here](https://github.com/Automattic/harper/blob/master/harper-core/src/linting/mod.rs)) -- Add your rule to the `LintGroup` [macro call](https://github.com/Automattic/harper/blob/master/harper-core/src/linting/lint_group.rs), which will aggregate its results with the other linters in Harper. -- Open a PR. +Before you can open a pull request or modify any code, you need a mutable copy of our monorepo. +The best way to do that is to [fork it in GitHub](https://github.com/Automattic/harper/fork). -## Querying the Document Directly +Next, you'll want to copy this fork onto your computer and create a new branch. +GitHub has an [excellent page explaining how clone repositories](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository). -If you don't wish to use a Harper [Pattern](https://docs.rs/harper-core/latest/harper_core/patterns/trait.Pattern.html), you may query the `Document` directly. -Make sure you read the [available methods](https://docs.rs/harper-core/latest/harper_core/struct.Document.html) available for `Document`s and for [TokenStrings](https://docs.rs/harper-core/latest/harper_core/struct.Document.html#impl-TokenStringExt-for-Document). +## Get Your Environment Set Up + +Please read our [guide for getting your environment set up](./environment). + +## Open a Draft Pull Request + +Next, you'll want to open a draft pull request. +This gives us (the Harper maintainers) a better view of what is actively being worked on. +It also makes it much easier to ask questions about how Harper works while you're working on your rule. + +GitHub has some [good documentation on how to create a draft PR](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) if this is your first time. + +## Create Your Rule's Module + +We separate each rule into its own file inside the `harper-core/src/linting` [directory.](https://github.com/Automattic/harper/tree/master/harper-core/src/linting) +Create a new file under that directory with the name of your rule in `snake_case`. +If you can't decide yet, just call it `my_rule.rs`. + +Don't put anything in this file yet, there's some bookkeeping we have to do first. + +## Register Your Rule + +Before we start describing to Harper what grammatical errors to look for, we need to register your rule within the system. + +First, add your rule's module to the tree by adding it to [the top of the mod file](https://github.com/Automattic/harper/blob/master/harper-core/src/linting/mod.rs). +It should look something like this: + +```rust title="harper-core/src/linting/mod.rs" +mod an_a; +mod avoid_curses; +mod boring_words; +mod capitalize_personal_pronouns; +// [svp! df:+]mod my_rule; +// [svp! df:+]pub use my_rule::MyRule; +``` + +Next, we need to configure whether your rule will be enabled by default. +While you're working on it, we __highly suggest__ you enable it to avoid confusion. + +To do that, import your rule at the top of the `lint_group` [file](https://github.com/Automattic/harper/blob/master/harper-core/src/linting/mod.rs). + +```rust title="harper-core/src/linting/lint_group.rs" +use super::an_a::AnA; +use super::avoid_curses::AvoidCurses; +use super::boring_words::BoringWords; +use super::capitalize_personal_pronouns::CapitalizePersonalPronouns; +use super::correct_number_suffix::CorrectNumberSuffix; +// [svp! df:+]use super::my_rule::MyRule; +``` + +Finally, enable it in the macro invocation near the bottom: + +```rust title="harper-core/src/linting/lint_group.rs" +create_lint_group_config!( + SpelledNumbers => false, + AnA => true, + SentenceCapitalization => true, + UnclosedQuotes => true, +// [svp! df:+] MyRule => true +); +``` + +That's it! + +## Write Your Rule + +Defining a pattern and [implementing the PatternLinter trait](https://docs.rs/harper-core/latest/harper_core/linting/trait.PatternLinter.html) is the easiest way to define a new rule for Harper. +Here's a template to get you started: + +```rust title="my_rule.rs" +use crate::{ + Lrc, Token +}; + +use super::{Lint, PatternLinter}; + +pub struct MyRule { + pattern: Box, +} + +impl Default for MyRule { + fn default() -> Self { + let mut pattern = todo!(); + + Self { + pattern: Box::new(pattern), + } + } +} + +impl PatternLinter for ThatWhich { + fn pattern(&self) -> &dyn Pattern { + self.pattern.as_ref() + } + + /// Any series of tokens that match the pattern provided in the `default()` method above will + /// be provided to this function, which you are required to map into a [`Lint`] object. + fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Lint { + unimplemented!(); + } + + fn description(&self) -> &'static str { + "Replace this text with a description of what your rule looks for." + } +} +``` + +## Test Your Changes + +To test your rule, write out an example of the error it looks for in a test file at the root of the Harper monorepo. + +```markdown title="test.md" +This is an test of the `an_a` rule. +Your test should look different. +``` + +From there, you can run `just lint `. +It should emit a readable report of the grammatical errors in the document. +If the error your rule looks for does _not_ appear in this list, something is wrong. + +If you need any help writing or debugging rules, don't be afraid to contact the Harper team in your draft pull request. + +## Elevate Your Pull Request + +Once you're satisfied with your rule, you can go ahead and elevate your pull requests to mark it as "ready for review." +At that point, a maintainer on the Harper team take a look at it and (hopefully) merge it. From 4ba61e82093280de8895dfc052335dff56de954d Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Thu, 23 Jan 2025 15:36:10 -0700 Subject: [PATCH 2/2] docs: added instructions for testing in Visual Studio Code --- .../docs/contributors/author-a-rule/+page.md | 23 ++++++++++++++++-- .../web/static/images/vscode_harper_path.webp | Bin 0 -> 11356 bytes 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 packages/web/static/images/vscode_harper_path.webp diff --git a/packages/web/src/routes/docs/contributors/author-a-rule/+page.md b/packages/web/src/routes/docs/contributors/author-a-rule/+page.md index 8d87905d..4b15519b 100644 --- a/packages/web/src/routes/docs/contributors/author-a-rule/+page.md +++ b/packages/web/src/routes/docs/contributors/author-a-rule/+page.md @@ -57,7 +57,7 @@ mod capitalize_personal_pronouns; ``` Next, we need to configure whether your rule will be enabled by default. -While you're working on it, we __highly suggest__ you enable it to avoid confusion. +While you're working on it, we **highly suggest** you enable it to avoid confusion. To do that, import your rule at the top of the `lint_group` [file](https://github.com/Automattic/harper/blob/master/harper-core/src/linting/mod.rs). @@ -115,7 +115,7 @@ impl PatternLinter for ThatWhich { self.pattern.as_ref() } - /// Any series of tokens that match the pattern provided in the `default()` method above will + /// Any series of tokens that match the pattern provided in the `default()` method above will /// be provided to this function, which you are required to map into a [`Lint`] object. fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Lint { unimplemented!(); @@ -136,12 +136,31 @@ This is an test of the `an_a` rule. Your test should look different. ``` +### Using the Command Line + From there, you can run `just lint `. It should emit a readable report of the grammatical errors in the document. If the error your rule looks for does _not_ appear in this list, something is wrong. If you need any help writing or debugging rules, don't be afraid to contact the Harper team in your draft pull request. +> **Note:** if two lints (or suggestions) overlap or address the same problem, this command will only display the first one. +> In that case, you might want to use another method of debugging. + +### Using Visual Studio Code + +First make sure you have [the extension installed from the marketplace](https://marketplace.visualstudio.com/items?itemName=elijah-potter.harper). +Then, can configure the path of the `harper-ls` binary the Visual Studio Code extension uses in settings. +Set it to `/target/release/harper-ls`. + +![How to change the `harper-ls` path](/images/vscode_harper_path.webp) + +Now every time you want to test a change, you'll have to recompile `harper-ls` and reload Visual Studio Code using `Developer: Reload Window`. + +```bash +cargo build --release # Run in the monorepo to compile `harper-ls`. +``` + ## Elevate Your Pull Request Once you're satisfied with your rule, you can go ahead and elevate your pull requests to mark it as "ready for review." diff --git a/packages/web/static/images/vscode_harper_path.webp b/packages/web/static/images/vscode_harper_path.webp new file mode 100644 index 0000000000000000000000000000000000000000..63232062c76cfcd35827ca0e122aed78e7ecf7fc GIT binary patch literal 11356 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 literal 0 HcmV?d00001