Skip to content

refactor(harper-comments): migrate Unit, Lua, and Solidity onto LineWise [WIP - Do Not Merge] - #3909

Draft
rodbegbie wants to merge 7 commits into
Automattic:masterfrom
rodbegbie:feature/linewise-comment-parsers
Draft

refactor(harper-comments): migrate Unit, Lua, and Solidity onto LineWise [WIP - Do Not Merge]#3909
rodbegbie wants to merge 7 commits into
Automattic:masterfrom
rodbegbie:feature/linewise-comment-parsers

Conversation

@rodbegbie

@rodbegbie rodbegbie commented Jul 29, 2026

Copy link
Copy Markdown

AI disclosure: this PR was written by an AI coding agent (Claude), working with me interactively over the course of a session. I reviewed and directed the work throughout — see the "AI Disclosure" section below for specifics.

Draft / work in progress — depends on #3757 landing first.

Issues

Description

Follow-up to the LineWise combinator introduced in #3757 (see this review discussion), which deliberately scoped LineWise to harper-yaml only and left Unit, Lua, and Solidity in harper-comments as a follow-up, since they share the same split-by-line/strip/parse/stitch shape but each have quirks that don't fit the plain trim-a-span contract:

  • Unit swallows lines inside fenced code blocks entirely (no tokens, no separator), tracked via a toggle that must persist across lines within one parse() call.
  • Lua turns an @tag line into a paragraph break (Newline(2)) instead of parsing it.
  • Solidity's SPDX-License-Identifier special case turned out to be unreachable dead code: it searches for a \n terminator within a span that has already been split on \n, so it always falls through to swallowing the whole line. Preserved that observed behavior (line contributes no tokens) without carrying the dead branch forward.

To support these cases, LineWise's strip contract is extended from a bare Span<char> to a small LineClass { span, newline: Option<usize> } struct, so the per-line classifier can also control the separator: suppress it (skip_silently, for Unit's fences), reweight it (skip_with_newline, for Lua's @-lines), or leave it as the default single Newline (matching DedentLines' existing behavior unchanged).

One intentional, test-uncovered normalization: Lua previously emitted a dangling Newline(2) after an @-line even when it was the last line in the source, with nothing following it. LineWise now gates all separators uniformly on "is there a next line", so that dangling token is no longer emitted. No fixture exercises this edge case either way.

This branch is currently rebased on top of #3757 and will need re-rebasing onto master once that PR lands (and shrink to just this one commit's diff at that point).

Demo

N/A — internal parser refactor, no user-visible behavior change beyond the one noted normalization above.

How Has This Been Tested?

  • cargo test --workspace — full workspace suite passes; all 40 harper-comments language_support fixtures keep their exact lint counts.
  • cargo clippy -- -Dwarnings passes clean on the touched files.

AI Disclosure

  • I am a human and didn't use any AI.
  • I used LLM features of my editor, but not an agent.
  • I used an AI agent interactively.
  • I am an agent or I got an agent to do the work autonomously.

If Your PR Implements or Enhances a Linter

  • I made up the sentences in the unit tests.
  • The sentences in the unit tests were generated by an AI.
  • I'm using examples from the bug report / feature request.
  • I collected real-world sentences for the unit tests.

Checklist

  • I have performed a self-review of my own code
  • I have added tests to cover my changes
  • I have considered splitting this into smaller pull requests.

rodbegbie and others added 7 commits July 28, 2026 16:24
Adds a new harper-yaml crate, following the same standalone pattern
as harper-python and harper-html rather than folding into
harper-comments. YamlParser lints `#` comments (with the usual
spellchecker-ignore-family suppression) and prose-like scalar values
(plain, quoted, or block scalars), while leaving structural YAML
(keys, identifiers, enum-like values) untouched.

The core pieces:

- YamlMasker runs two independent tree-sitter passes over the parsed
  document -- one for comment nodes, one for scalar-value nodes --
  and combines the surviving spans. Scalar nodes exclude mapping
  keys (unquoted keys are the same tree-sitter node kind as values,
  so a key-vs-value distinction has to be made explicitly by
  comparing a candidate node's start position against its enclosing
  mapping pair's start position).
- heuristics::is_prose_scalar decides whether a scalar value looks
  like prose worth checking, as opposed to structural config data:
  it requires 3+ words and rejects values that are, in their
  entirety, a single URL/path/version-shaped token (so "v1.2.3" is
  skipped, but "upgrade to version 1.2.3" is still checked).
- DedentLines wraps the inner PlainEnglish parser to parse each line
  of a scalar independently after trimming its whitespace, avoiding a
  spurious "double space" false positive that raw multi-line block
  scalar indentation would otherwise trigger.

Closes Automattic#2700.

Entire-Checkpoint: 8eae5ee14317
harper-ls now routes the "yaml" language ID directly to YamlParser,
the same way it already routes "python" to PythonParser. harper-cli
routes .yaml/.yml files the same way it routes .py/.pyi.

Entire-Checkpoint: 173be144ecc2
- New fuzz_harper_yaml target, mirroring fuzz_harper_html.
- VS Code plugin: register onLanguage:yaml activation and add a
  YAML case to the language integration test suite.
- Document YAML in the language-server support table. Marked
  "Comments Only: no" (unlike TOML), since YamlParser also lints
  prose-like scalar values, not just comments.

Entire-Checkpoint: 3d4124766057
Address review findings on the YAML support crate:

- Remove unreachable dead code in `is_prose_scalar`: under the
  `word_count >= 3` gate, `looks_url_path_or_version` (bails at >1 word)
  and `is_snake_or_kebab_case` (bails on whitespace) could never fire.
  Behaviour is unchanged - the word-count gate already subsumes every
  single-word URL/path/version/identifier case - but the code and its
  helpers are gone, so tests no longer imply filtering that isn't there.
- Strip YAML block-scalar indicators (`|`, `>`, `|-`, `>+2`, ...) before
  the word-count gate so a short block-scalar body isn't pushed over the
  prose threshold by the leading indicator token.
- Replace `is_mapping_key`'s byte-position heuristic with tree-sitter's
  named `key` field, so explicit-key syntax (`? key` / `: value`) no
  longer misclassifies the key as a lintable value.
- Coalesce overlapping spans before building the `Mask`, so adversarial
  input cannot trigger the `FromIterator` overlap panic (a DoS in
  harper-ls).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Entire-Checkpoint: 6c181a01b5d3
…combinator

DedentLines duplicated a split-by-line/strip/parse/stitch pattern already
present three times in harper-comments (Unit, Lua, Solidity). Pull the
shared mechanism into harper_core::parsers::LineWise, parameterized by a
per-line strip function, so DedentLines becomes a thin wrapper supplying
just the YAML-specific whitespace-trim policy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Entire-Checkpoint: 88a3dc81119c
`just check-rust` runs `cargo clippy -- -Dwarnings ...` against whatever
Rust "stable" currently resolves to. A recent stable release added new
lints (useless_borrows_in_formatting, question_mark) and promoted
float_literal_f32_fallback from a future-incompat warning towards a hard
error, which broke previously-clean code across harper-core, harper-cli,
and harper-desktop with no code change on their part. This has been
failing on master's own CI since at least 2026-07-05 and was blocking
CI on this branch too (rebased onto upstream/master, which already
picked up a separate fix for 3 more issues of the same kind in
harper-desktop's mac_broker module - see 3f46f06).

Also drops an unused `Config` import in harper-desktop's mac_broker
module, left over from upstream's 607d0f8 (2026-07-20) which replaced
its only two uses with `Integration` but didn't clean up the import.
Same "unblock CI, not our diff" rationale as the rest of this commit.

Unrelated to the YAML work in this branch; bundled here only because it
was blocking this PR's CI and nothing else had fixed it yet upstream.

Verified against the exact CI command plus `cargo hack check
--each-feature` and the full test suite.

Entire-Checkpoint: 902cc73d5bab
Follow-up to 93075ce. Unit, Lua, and Solidity each hand-rolled the same
split-by-line/strip/parse/stitch loop that LineWise now centralizes, but
none of them fit the plain trim-a-span contract:

- Unit swallows lines inside fenced code blocks entirely (no tokens, no
  separator), tracked via a toggle that must persist across lines within
  one parse() call.
- Lua turns an "@tag" line into a paragraph break (Newline(2)) instead of
  parsing it.
- Solidity's SPDX-License-Identifier special case turned out to be
  unreachable dead code: it searches for a '\n' terminator within a
  span that has already been split on '\n', so it always falls through
  to swallowing the whole line. Preserved that observed behavior
  (line contributes no tokens) without carrying the dead branch forward.

Extended LineWise's strip contract from a bare Span<char> to a small
LineClass { span, newline: Option<usize> } struct so the per-line
classifier can also control the separator: suppress it (skip_silently,
for Unit's fences), reweight it (skip_with_newline, for Lua's @-lines),
or leave it as the default single Newline (skip/parse, matching
DedentLines' existing behavior unchanged).

One intentional, test-uncovered normalization: Lua previously emitted a
dangling Newline(2) after an "@"-line even when it was the last line in
the source, with nothing following it. LineWise now gates all separators
uniformly on "is there a next line", so that dangling token is no longer
emitted. No fixture exercises this edge case either way.

All 40 harper-comments language_support fixtures keep their exact lint
counts; full workspace test suite and clippy (-D warnings) pass clean on
the touched files.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@rodbegbie rodbegbie changed the title refactor(harper-comments): migrate Unit, Lua, and Solidity onto LineWise refactor(harper-comments): migrate Unit, Lua, and Solidity onto LineWise [WIP - Do Not Merge] Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant