Skip to content

Commit

Permalink
Merge pull request #305 from grantlemons/ignore-link-title-cfg
Browse files Browse the repository at this point in the history
feat(#104): Markdown linter config for ignoring link titles
  • Loading branch information
elijah-potter authored Jan 15, 2025
2 parents 636be54 + 743ed8b commit 04b65b3
Show file tree
Hide file tree
Showing 25 changed files with 367 additions and 147 deletions.
22 changes: 13 additions & 9 deletions harper-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use ariadne::{Color, Label, Report, ReportKind, Source};
use clap::Parser;
use harper_comments::CommentParser;
use harper_core::linting::{LintGroup, LintGroupConfig, Linter};
use harper_core::parsers::Markdown;
use harper_core::parsers::{Markdown, MarkdownOptions};
use harper_core::{remove_overlaps, Dictionary, Document, FstDictionary, TokenKind};
use harper_literate_haskell::LiterateHaskellParser;

Expand Down Expand Up @@ -43,12 +43,14 @@ enum Args {

fn main() -> anyhow::Result<()> {
let args = Args::parse();
let markdown_options = MarkdownOptions::default();
let linting_options = LintGroupConfig::default();

match args {
Args::Lint { file, count } => {
let (doc, source) = load_file(&file)?;
let (doc, source) = load_file(&file, markdown_options)?;

let mut linter = LintGroup::new(LintGroupConfig::default(), FstDictionary::curated());
let mut linter = LintGroup::new(linting_options, FstDictionary::curated());
let mut lints = linter.lint(&doc);

if count {
Expand Down Expand Up @@ -86,7 +88,7 @@ fn main() -> anyhow::Result<()> {
Ok(())
}
Args::Parse { file } => {
let (doc, _) = load_file(&file)?;
let (doc, _) = load_file(&file, markdown_options)?;

for token in doc.tokens() {
let json = serde_json::to_string(&token)?;
Expand All @@ -99,7 +101,7 @@ fn main() -> anyhow::Result<()> {
file,
include_newlines,
} => {
let (doc, source) = load_file(&file)?;
let (doc, source) = load_file(&file, markdown_options)?;

let primary_color = Color::Blue;
let secondary_color = Color::Magenta;
Expand Down Expand Up @@ -166,16 +168,18 @@ fn main() -> anyhow::Result<()> {
}
}

fn load_file(file: &Path) -> anyhow::Result<(Document, String)> {
fn load_file(file: &Path, markdown_options: MarkdownOptions) -> anyhow::Result<(Document, String)> {
let source = std::fs::read_to_string(file)?;

let parser: Box<dyn harper_core::parsers::Parser> =
match file.extension().map(|v| v.to_str().unwrap()) {
Some("md") => Box::new(Markdown),
Some("lhs") => Box::new(LiterateHaskellParser),
Some("md") => Box::new(Markdown::default()),
Some("lhs") => Box::new(LiterateHaskellParser::new_markdown(
MarkdownOptions::default(),
)),
Some("typ") => Box::new(harper_typst::Typst),
_ => Box::new(
CommentParser::new_from_filename(file)
CommentParser::new_from_filename(file, markdown_options)
.map(Box::new)
.ok_or(format_err!("Could not detect language ID."))?,
),
Expand Down
19 changes: 12 additions & 7 deletions harper-comments/src/comment_parser.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::path::Path;

use comment_parsers::{Go, JavaDoc, JsDoc, Unit};
use harper_core::parsers::{self, Parser};
use harper_core::parsers::{self, MarkdownOptions, Parser};
use harper_core::{FullDictionary, Token};
use harper_tree_sitter::TreeSitterMasker;
use tree_sitter::Node;
Expand All @@ -17,7 +17,10 @@ impl CommentParser {
self.inner.masker.create_ident_dict(source)
}

pub fn new_from_language_id(language_id: &str) -> Option<Self> {
pub fn new_from_language_id(
language_id: &str,
markdown_options: MarkdownOptions,
) -> Option<Self> {
let language = match language_id {
"rust" => tree_sitter_rust::language(),
"typescriptreact" => tree_sitter_typescript::language_tsx(),
Expand All @@ -42,10 +45,12 @@ impl CommentParser {
};

let comment_parser: Box<dyn Parser> = match language_id {
"javascriptreact" | "typescript" | "typescriptreact" | "javascript" => Box::new(JsDoc),
"javascriptreact" | "typescript" | "typescriptreact" | "javascript" => {
Box::new(JsDoc::new_markdown(markdown_options))
}
"java" => Box::new(JavaDoc::default()),
"go" => Box::new(Go),
_ => Box::new(Unit),
"go" => Box::new(Go::new_markdown(markdown_options)),
_ => Box::new(Unit::new_markdown(markdown_options)),
};

Some(Self {
Expand All @@ -57,8 +62,8 @@ impl CommentParser {
}

/// Infer the programming language from a provided filename.
pub fn new_from_filename(filename: &Path) -> Option<Self> {
Self::new_from_language_id(Self::filename_to_filetype(filename)?)
pub fn new_from_filename(filename: &Path, markdown_options: MarkdownOptions) -> Option<Self> {
Self::new_from_language_id(Self::filename_to_filetype(filename)?, markdown_options)
}

/// Convert a provided path to a corresponding Language Server Protocol file
Expand Down
21 changes: 17 additions & 4 deletions harper-comments/src/comment_parsers/go.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
use harper_core::parsers::{Markdown, Parser};
use harper_core::parsers::{Markdown, MarkdownOptions, Parser};
use harper_core::Lrc;
use harper_core::Token;

use super::without_initiators;

#[derive(Debug, Clone, Copy)]
pub struct Go;
#[derive(Clone)]
pub struct Go {
inner: Lrc<dyn Parser>,
}

impl Go {
pub fn new(parser: Lrc<dyn Parser>) -> Self {
Self { inner: parser }
}

pub fn new_markdown(markdown_options: MarkdownOptions) -> Self {
Self::new(Lrc::new(Markdown::new(markdown_options)))
}
}

impl Parser for Go {
fn parse(&self, source: &[char]) -> Vec<Token> {
Expand All @@ -25,7 +38,7 @@ impl Parser for Go {
actual_source = new_source
}

let mut new_tokens = Markdown.parse(actual_source);
let mut new_tokens = self.inner.parse(actual_source);

new_tokens
.iter_mut()
Expand Down
41 changes: 29 additions & 12 deletions harper-comments/src/comment_parsers/jsdoc.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
use harper_core::parsers::{Markdown, Parser};
use harper_core::parsers::{Markdown, MarkdownOptions, Parser};
use harper_core::Lrc;
use harper_core::{Punctuation, Span, Token, TokenKind};
use itertools::Itertools;

use super::without_initiators;

pub struct JsDoc;
#[derive(Clone)]
pub struct JsDoc {
inner: Lrc<dyn Parser>,
}

impl JsDoc {
pub fn new(parser: Lrc<dyn Parser>) -> Self {
Self { inner: parser }
}

pub fn new_markdown(markdown_options: MarkdownOptions) -> Self {
Self::new(Lrc::new(Markdown::new(markdown_options)))
}
}

impl Parser for JsDoc {
fn parse(&self, source: &[char]) -> Vec<Token> {
Expand All @@ -13,7 +27,7 @@ impl Parser for JsDoc {
let mut chars_traversed = 0;

for line in source.split(|c| *c == '\n') {
let mut new_tokens = parse_line(line);
let mut new_tokens = parse_line(line, self.inner.clone());

if chars_traversed + line.len() < source.len() {
new_tokens.push(Token::new(
Expand All @@ -34,7 +48,7 @@ impl Parser for JsDoc {
}
}

fn parse_line(source: &[char]) -> Vec<Token> {
fn parse_line(source: &[char], parser: Lrc<dyn Parser>) -> Vec<Token> {
let actual_line = without_initiators(source);

if actual_line.is_empty() {
Expand All @@ -43,7 +57,7 @@ fn parse_line(source: &[char]) -> Vec<Token> {

let source_line = actual_line.get_content(source);

let mut new_tokens = Markdown.parse(source_line);
let mut new_tokens = parser.parse(source_line);

// Handle inline tags
mark_inline_tags(&mut new_tokens);
Expand Down Expand Up @@ -148,22 +162,24 @@ fn parse_inline_tag(tokens: &[Token]) -> Option<usize> {

#[cfg(test)]
mod tests {
use harper_core::{Document, Punctuation, TokenKind};
use harper_core::{parsers::MarkdownOptions, Document, Punctuation, TokenKind};

use crate::CommentParser;

#[test]
fn escapes_loop() {
let source = "/** This should _not_cause an infinite loop: {@ */";
let mut parser = CommentParser::new_from_language_id("javascript").unwrap();
Document::new_curated(source, &mut parser);
let parser =
CommentParser::new_from_language_id("javascript", MarkdownOptions::default()).unwrap();
Document::new_curated(source, &parser);
}

#[test]
fn handles_inline_link() {
let source = "/** See {@link MyClass} and [MyClass's foo property]{@link MyClass#foo}. */";
let mut parser = CommentParser::new_from_language_id("javascript").unwrap();
let document = Document::new_curated(source, &mut parser);
let parser =
CommentParser::new_from_language_id("javascript", MarkdownOptions::default()).unwrap();
let document = Document::new_curated(source, &parser);

assert!(matches!(
document
Expand Down Expand Up @@ -206,8 +222,9 @@ mod tests {
#[test]
fn handles_class() {
let source = "/** @class Circle representing a circle. */";
let mut parser = CommentParser::new_from_language_id("javascript").unwrap();
let document = Document::new_curated(source, &mut parser);
let parser =
CommentParser::new_from_language_id("javascript", MarkdownOptions::default()).unwrap();
let document = Document::new_curated(source, &parser);

assert!(document
.tokens()
Expand Down
25 changes: 20 additions & 5 deletions harper-comments/src/comment_parsers/unit.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use harper_core::parsers::{Markdown, Parser};
use harper_core::parsers::{Markdown, MarkdownOptions, Parser};
use harper_core::Lrc;
use harper_core::{Span, Token};

use super::without_initiators;
Expand All @@ -9,7 +10,20 @@ use super::without_initiators;
///
/// It assumes it is being provided a single line of comment at a time,
/// including the comment initiation characters.
pub struct Unit;
#[derive(Clone)]
pub struct Unit {
inner: Lrc<dyn Parser>,
}

impl Unit {
pub fn new(parser: Lrc<dyn Parser>) -> Self {
Self { inner: parser }
}

pub fn new_markdown(markdown_options: MarkdownOptions) -> Self {
Self::new(Lrc::new(Markdown::new(markdown_options)))
}
}

impl Parser for Unit {
fn parse(&self, source: &[char]) -> Vec<Token> {
Expand All @@ -28,7 +42,7 @@ impl Parser for Unit {
continue;
}

let mut new_tokens = parse_line(line);
let mut new_tokens = parse_line(line, self.inner.clone());

if chars_traversed + line.len() < source.len() {
new_tokens.push(Token::new(
Expand All @@ -49,15 +63,16 @@ impl Parser for Unit {
}
}

fn parse_line(source: &[char]) -> Vec<Token> {
fn parse_line(source: &[char], parser: Lrc<dyn Parser>) -> Vec<Token> {
let actual = without_initiators(source);

if actual.is_empty() {
return Vec::new();
}

let source = actual.get_content(source);
let mut new_tokens = Markdown.parse(source);

let mut new_tokens = parser.parse(source);

new_tokens
.iter_mut()
Expand Down
5 changes: 3 additions & 2 deletions harper-comments/tests/language_support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::path::Path;

use harper_comments::CommentParser;
use harper_core::linting::{LintGroup, LintGroupConfig, Linter};
use harper_core::parsers::MarkdownOptions;
use harper_core::{Document, FstDictionary};

/// Creates a unit test checking that the linting of a source file in
Expand All @@ -20,9 +21,9 @@ macro_rules! create_test {
)
);

let mut parser = CommentParser::new_from_filename(Path::new(filename)).unwrap();
let parser = CommentParser::new_from_filename(Path::new(filename), MarkdownOptions::default()).unwrap();
let dict = FstDictionary::curated();
let document = Document::new(&source, &mut parser, &dict);
let document = Document::new(&source, &parser, &dict);

let mut linter = LintGroup::new(
LintGroupConfig::default(),
Expand Down
6 changes: 3 additions & 3 deletions harper-core/benches/parse_demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ static DEMO: &str = include_str!("../../demo.md");

fn parse_demo(c: &mut Criterion) {
c.bench_function("parse_demo", |b| {
b.iter(|| Document::new_markdown_curated(black_box(DEMO)))
b.iter(|| Document::new_markdown_default_curated(black_box(DEMO)));
});
}

fn lint_demo(c: &mut Criterion) {
let dictionary = FstDictionary::curated();
let mut lint_set = LintGroup::new(Default::default(), dictionary);
let document = Document::new_markdown_curated(black_box(DEMO));
let document = Document::new_markdown_default_curated(black_box(DEMO));

c.bench_function("lint_demo", |b| {
b.iter(|| lint_set.lint(&document));
Expand All @@ -25,7 +25,7 @@ fn lint_demo_uncached(c: &mut Criterion) {
b.iter(|| {
let dictionary = FstDictionary::curated();
let mut lint_set = LintGroup::new(LintGroupConfig::default(), dictionary.clone());
let document = Document::new_markdown(black_box(DEMO), &dictionary);
let document = Document::new_markdown_default(black_box(DEMO), &dictionary);
lint_set.lint(&document)
})
});
Expand Down
Loading

0 comments on commit 04b65b3

Please sign in to comment.