Copy disabled (too large)
Download .txt
Showing preview only (10,154K chars total). Download the full file to get everything.
Repository: ValeLint/vale
Branch: v3
Commit: cdf6f9e55798
Files: 889
Total size: 24.8 MB
Directory structure:
gitextract_021vlxez/
├── .gitattributes
├── .github/
│ ├── CODE_OF_CONDUCT.md
│ ├── CONTRIBUTING.md
│ ├── ISSUE_TEMPLATE/
│ │ ├── 0_feature_request.yml
│ │ └── 1_bug_report.yml
│ └── workflows/
│ ├── codeql.yml
│ ├── golangci-lint.yml
│ └── main.yml
├── .gitignore
├── .gitlab-ci.yml
├── .golangci.yml
├── .goreleaser.yml
├── .pre-commit-hooks.yaml
├── .vale.ini
├── .well-known/
│ └── funding-manifest-urls
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── appveyor.yml
├── cmd/
│ └── vale/
│ ├── api.go
│ ├── color.go
│ ├── command.go
│ ├── common.go
│ ├── custom.go
│ ├── error.go
│ ├── flag.go
│ ├── funcs.go
│ ├── info.go
│ ├── json.go
│ ├── library.go
│ ├── line.go
│ ├── main.go
│ ├── main_unix.go
│ ├── main_windows.go
│ ├── native.go
│ ├── pkg_test.go
│ ├── sync.go
│ └── util.go
├── go.mod
├── go.sum
├── internal/
│ ├── check/
│ │ ├── action.go
│ │ ├── capitalization.go
│ │ ├── conditional.go
│ │ ├── consistency.go
│ │ ├── definition.go
│ │ ├── doc.go
│ │ ├── existence.go
│ │ ├── existence_test.go
│ │ ├── filter.go
│ │ ├── manager.go
│ │ ├── manager_test.go
│ │ ├── metric.go
│ │ ├── occurrence.go
│ │ ├── readability.go
│ │ ├── repetition.go
│ │ ├── scope.go
│ │ ├── scope_test.go
│ │ ├── script.go
│ │ ├── sequence.go
│ │ ├── spelling.go
│ │ ├── substitution.go
│ │ ├── substitution_test.go
│ │ ├── variables.go
│ │ └── variables_test.go
│ ├── core/
│ │ ├── alert.go
│ │ ├── config.go
│ │ ├── config_test.go
│ │ ├── doc.go
│ │ ├── error.go
│ │ ├── file.go
│ │ ├── format.go
│ │ ├── ini.go
│ │ ├── ini_test.go
│ │ ├── location.go
│ │ ├── markup.go
│ │ ├── source.go
│ │ ├── source_test.go
│ │ ├── util.go
│ │ ├── util_test.go
│ │ └── view.go
│ ├── glob/
│ │ ├── glob.go
│ │ └── glob_test.go
│ ├── lint/
│ │ ├── adoc.go
│ │ ├── ast.go
│ │ ├── code/
│ │ │ ├── c.go
│ │ │ ├── comments.go
│ │ │ ├── comments_test.go
│ │ │ ├── cpp.go
│ │ │ ├── css.go
│ │ │ ├── go.go
│ │ │ ├── java.go
│ │ │ ├── jl.go
│ │ │ ├── js.go
│ │ │ ├── lang.go
│ │ │ ├── proto.go
│ │ │ ├── py.go
│ │ │ ├── query.go
│ │ │ ├── rb.go
│ │ │ ├── rs.go
│ │ │ ├── ts.go
│ │ │ ├── tsx.go
│ │ │ ├── util.go
│ │ │ └── yaml.go
│ │ ├── code.go
│ │ ├── data.go
│ │ ├── dita.go
│ │ ├── doc.go
│ │ ├── fragment.go
│ │ ├── html.go
│ │ ├── html_test.go
│ │ ├── lint.go
│ │ ├── lint_test.go
│ │ ├── md.go
│ │ ├── mdx.go
│ │ ├── metadata.go
│ │ ├── org.go
│ │ ├── rst.go
│ │ ├── util.go
│ │ ├── walk.go
│ │ ├── xml.go
│ │ └── xml_test.go
│ ├── nlp/
│ │ ├── doc.go
│ │ ├── http.go
│ │ ├── prose.go
│ │ ├── provider.go
│ │ ├── tokenize.go
│ │ ├── tokenize_test.go
│ │ ├── util.go
│ │ └── var.go
│ ├── spell/
│ │ ├── LICENSE
│ │ ├── aff.go
│ │ ├── aff_test.go
│ │ ├── data/
│ │ │ ├── en_US-web.aff
│ │ │ └── en_US-web.dic
│ │ ├── doc.go
│ │ ├── gospell.go
│ │ ├── multi.go
│ │ └── words.go
│ └── system/
│ ├── cmd.go
│ ├── dir.go
│ ├── file.go
│ ├── path.go
│ ├── path_test.go
│ ├── system.go
│ ├── walk.go
│ └── zip.go
└── testdata/
├── Gemfile
├── comments/
│ ├── in/
│ │ ├── 0.go
│ │ ├── 1.rs
│ │ ├── 2.py
│ │ ├── 3.cpp
│ │ ├── 4.js
│ │ └── 5.yml
│ └── out/
│ ├── 0.json
│ ├── 1.json
│ ├── 2.json
│ ├── 3.json
│ ├── 4.json
│ └── 5.json
├── features/
│ ├── CLI.feature
│ ├── checks.feature
│ ├── comments.feature
│ ├── config.feature
│ ├── fragments.feature
│ ├── frontmatter.feature
│ ├── lint.feature
│ ├── misc.feature
│ ├── patterns.feature
│ ├── pkg.feature
│ ├── scopes.feature
│ ├── steps.rb
│ ├── styles.feature
│ ├── support/
│ │ └── env.rb
│ └── views.feature
├── fixtures/
│ ├── XSL/
│ │ └── docbook-xsl-snapshot/
│ │ ├── VERSION.xsl
│ │ ├── build.xml
│ │ ├── catalog.xml
│ │ ├── common/
│ │ │ ├── addns.xsl
│ │ │ ├── af.xml
│ │ │ ├── am.xml
│ │ │ ├── ar.xml
│ │ │ ├── as.xml
│ │ │ ├── ast.xml
│ │ │ ├── autoidx-kimber.xsl
│ │ │ ├── autoidx-kosek.xsl
│ │ │ ├── az.xml
│ │ │ ├── bg.xml
│ │ │ ├── bn.xml
│ │ │ ├── bn_in.xml
│ │ │ ├── bs.xml
│ │ │ ├── build.xml
│ │ │ ├── ca.xml
│ │ │ ├── charmap.xml
│ │ │ ├── charmap.xsl
│ │ │ ├── common.xml
│ │ │ ├── common.xsl
│ │ │ ├── cs.xml
│ │ │ ├── cy.xml
│ │ │ ├── da.xml
│ │ │ ├── de.xml
│ │ │ ├── el.xml
│ │ │ ├── en.xml
│ │ │ ├── entities.ent
│ │ │ ├── eo.xml
│ │ │ ├── es.xml
│ │ │ ├── et.xml
│ │ │ ├── eu.xml
│ │ │ ├── fa.xml
│ │ │ ├── fi.xml
│ │ │ ├── fr.xml
│ │ │ ├── ga.xml
│ │ │ ├── gentext.xsl
│ │ │ ├── gl.xml
│ │ │ ├── gu.xml
│ │ │ ├── he.xml
│ │ │ ├── hi.xml
│ │ │ ├── hr.xml
│ │ │ ├── hu.xml
│ │ │ ├── id.xml
│ │ │ ├── insertfile.xsl
│ │ │ ├── is.xml
│ │ │ ├── it.xml
│ │ │ ├── ja.xml
│ │ │ ├── ka.xml
│ │ │ ├── kn.xml
│ │ │ ├── ko.xml
│ │ │ ├── ky.xml
│ │ │ ├── l10n.dtd
│ │ │ ├── l10n.xml
│ │ │ ├── l10n.xsl
│ │ │ ├── la.xml
│ │ │ ├── labels.xsl
│ │ │ ├── lt.xml
│ │ │ ├── lv.xml
│ │ │ ├── ml.xml
│ │ │ ├── mn.xml
│ │ │ ├── mr.xml
│ │ │ ├── nb.xml
│ │ │ ├── nds.xml
│ │ │ ├── nl.xml
│ │ │ ├── nn.xml
│ │ │ ├── olink.xsl
│ │ │ ├── or.xml
│ │ │ ├── pa.xml
│ │ │ ├── pi.xml
│ │ │ ├── pi.xsl
│ │ │ ├── pl.xml
│ │ │ ├── pt.xml
│ │ │ ├── pt_br.xml
│ │ │ ├── refentry.xml
│ │ │ ├── refentry.xsl
│ │ │ ├── ro.xml
│ │ │ ├── ru.xml
│ │ │ ├── sk.xml
│ │ │ ├── sl.xml
│ │ │ ├── sq.xml
│ │ │ ├── sr.xml
│ │ │ ├── sr_Latn.xml
│ │ │ ├── stripns.xsl
│ │ │ ├── subtitles.xsl
│ │ │ ├── sv.xml
│ │ │ ├── ta.xml
│ │ │ ├── table.xsl
│ │ │ ├── targetdatabase.dtd
│ │ │ ├── targets.xsl
│ │ │ ├── te.xml
│ │ │ ├── th.xml
│ │ │ ├── titles.xsl
│ │ │ ├── tl.xml
│ │ │ ├── tr.xml
│ │ │ ├── uk.xml
│ │ │ ├── ur.xml
│ │ │ ├── utility.xml
│ │ │ ├── utility.xsl
│ │ │ ├── vi.xml
│ │ │ ├── xh.xml
│ │ │ ├── zh.xml
│ │ │ ├── zh_cn.xml
│ │ │ └── zh_tw.xml
│ │ ├── html/
│ │ │ ├── admon.xsl
│ │ │ ├── annotations.xsl
│ │ │ ├── autoidx-kimber.xsl
│ │ │ ├── autoidx-kosek.xsl
│ │ │ ├── autoidx-ng.xsl
│ │ │ ├── autoidx.xsl
│ │ │ ├── autotoc.xsl
│ │ │ ├── biblio-iso690.xsl
│ │ │ ├── biblio.xsl
│ │ │ ├── block.xsl
│ │ │ ├── build.xml
│ │ │ ├── callout.xsl
│ │ │ ├── changebars.xsl
│ │ │ ├── chunk-changebars.xsl
│ │ │ ├── chunk-code.xsl
│ │ │ ├── chunk-common.xsl
│ │ │ ├── chunk.xsl
│ │ │ ├── chunker.xsl
│ │ │ ├── chunkfast.xsl
│ │ │ ├── chunktoc.xsl
│ │ │ ├── component.xsl
│ │ │ ├── division.xsl
│ │ │ ├── docbook.css.xml
│ │ │ ├── docbook.xsl
│ │ │ ├── ebnf.xsl
│ │ │ ├── footnote.xsl
│ │ │ ├── formal.xsl
│ │ │ ├── glossary.xsl
│ │ │ ├── graphics.xsl
│ │ │ ├── highlight.xsl
│ │ │ ├── html-rtf.xsl
│ │ │ ├── html.xsl
│ │ │ ├── htmltbl.xsl
│ │ │ ├── index.xsl
│ │ │ ├── info.xsl
│ │ │ ├── inline.xsl
│ │ │ ├── its.xsl
│ │ │ ├── keywords.xsl
│ │ │ ├── lists.xsl
│ │ │ ├── maketoc.xsl
│ │ │ ├── manifest.xsl
│ │ │ ├── math.xsl
│ │ │ ├── oldchunker.xsl
│ │ │ ├── onechunk.xsl
│ │ │ ├── param.xml
│ │ │ ├── param.xsl
│ │ │ ├── pi.xml
│ │ │ ├── pi.xsl
│ │ │ ├── profile-chunk-code.xsl
│ │ │ ├── profile-chunk.xsl
│ │ │ ├── profile-docbook.xsl
│ │ │ ├── profile-onechunk.xsl
│ │ │ ├── publishers.xsl
│ │ │ ├── qandaset.xsl
│ │ │ ├── refentry.xsl
│ │ │ ├── sections.xsl
│ │ │ ├── synop.xsl
│ │ │ ├── table.xsl
│ │ │ ├── task.xsl
│ │ │ ├── titlepage.templates.xml
│ │ │ ├── titlepage.templates.xsl
│ │ │ ├── titlepage.xsl
│ │ │ ├── toc.xsl
│ │ │ ├── verbatim.xsl
│ │ │ └── xref.xsl
│ │ └── lib/
│ │ ├── build.xml
│ │ └── lib.xsl
│ ├── YAML/
│ │ ├── NoExtends.yml
│ │ └── NoMsg.yml
│ ├── actions/
│ │ ├── .vale.ini
│ │ ├── script.json
│ │ └── spell.json
│ ├── benchmarks/
│ │ ├── bench.md
│ │ └── bench.rst
│ ├── checks/
│ │ ├── Capitalization/
│ │ │ ├── .vale.ini
│ │ │ ├── test.md
│ │ │ ├── test2.md
│ │ │ ├── test3.md
│ │ │ └── test4.md
│ │ ├── Conditional/
│ │ │ ├── .vale.ini
│ │ │ └── test.md
│ │ ├── Existence/
│ │ │ ├── .vale.ini
│ │ │ ├── test.md
│ │ │ └── test2.md
│ │ ├── Metric/
│ │ │ ├── .vale.ini
│ │ │ ├── frontmatter.md
│ │ │ └── test.md
│ │ ├── Occurrence/
│ │ │ ├── .vale.ini
│ │ │ ├── test.md
│ │ │ ├── test2.md
│ │ │ └── test3.md
│ │ ├── Repetition/
│ │ │ ├── _vale
│ │ │ ├── test.md
│ │ │ └── text.rst
│ │ ├── Script/
│ │ │ ├── .vale.ini
│ │ │ └── test.md
│ │ ├── SentenceCase/
│ │ │ ├── .vale.ini
│ │ │ ├── test.html
│ │ │ └── test.md
│ │ ├── Sequence/
│ │ │ ├── .vale.ini
│ │ │ ├── test.adoc
│ │ │ ├── test.md
│ │ │ └── test.txt
│ │ ├── Spelling/
│ │ │ ├── .vale.ini
│ │ │ ├── test.md
│ │ │ └── test.org
│ │ └── Substitution/
│ │ ├── .vale.ini
│ │ ├── test.md
│ │ └── test2.md
│ ├── comments/
│ │ ├── .vale.ini
│ │ ├── test.adoc
│ │ ├── test.md
│ │ ├── test.mdx
│ │ ├── test.org
│ │ ├── test.rst
│ │ └── test2.mdx
│ ├── configs/
│ │ ├── .vale.ini
│ │ ├── ini/
│ │ │ ├── .vale.ini
│ │ │ └── styles/
│ │ │ ├── Joblint/
│ │ │ │ ├── Acronyms.yml
│ │ │ │ ├── Benefits.yml
│ │ │ │ ├── Bro.yml
│ │ │ │ ├── Competitive.yml
│ │ │ │ ├── Derogatory.yml
│ │ │ │ ├── DevEnv.yml
│ │ │ │ ├── DumbTitles.yml
│ │ │ │ ├── Gendered.yml
│ │ │ │ ├── Hair.yml
│ │ │ │ ├── LegacyTech.yml
│ │ │ │ ├── Meritocracy.yml
│ │ │ │ ├── Profanity.yml
│ │ │ │ ├── README.md
│ │ │ │ ├── Reassure.yml
│ │ │ │ ├── Sexualised.yml
│ │ │ │ ├── Starter.yml
│ │ │ │ ├── TechTerms.yml
│ │ │ │ ├── Visionary.yml
│ │ │ │ └── meta.json
│ │ │ └── proselint/
│ │ │ ├── Airlinese.yml
│ │ │ ├── AnimalLabels.yml
│ │ │ ├── Annotations.yml
│ │ │ ├── Apologizing.yml
│ │ │ ├── Archaisms.yml
│ │ │ ├── But.yml
│ │ │ ├── Cliches.yml
│ │ │ ├── CorporateSpeak.yml
│ │ │ ├── Currency.yml
│ │ │ ├── Cursing.yml
│ │ │ ├── DateCase.yml
│ │ │ ├── DateMidnight.yml
│ │ │ ├── DateRedundancy.yml
│ │ │ ├── DateSpacing.yml
│ │ │ ├── DenizenLabels.yml
│ │ │ ├── Diacritical.yml
│ │ │ ├── GenderBias.yml
│ │ │ ├── GroupTerms.yml
│ │ │ ├── Hedging.yml
│ │ │ ├── Hyperbole.yml
│ │ │ ├── Illusions.yml
│ │ │ ├── Jargon.yml
│ │ │ ├── LGBTOffensive.yml
│ │ │ ├── LGBTTerms.yml
│ │ │ ├── Malapropisms.yml
│ │ │ ├── Needless.yml
│ │ │ ├── Nonwords.yml
│ │ │ ├── Oxymorons.yml
│ │ │ ├── P-Value.yml
│ │ │ ├── RASSyndrome.yml
│ │ │ ├── README.md
│ │ │ ├── Skunked.yml
│ │ │ ├── Spelling.yml
│ │ │ ├── Typography.yml
│ │ │ ├── Uncomparables.yml
│ │ │ ├── Very.yml
│ │ │ └── meta.json
│ │ └── test.md
│ ├── filters/
│ │ ├── .vale.ini
│ │ └── test.md
│ ├── folders/
│ │ └── .vale.ini
│ ├── formats/
│ │ ├── .vale.ini
│ │ ├── adoc/
│ │ │ ├── .vale.ini
│ │ │ ├── styles/
│ │ │ │ └── Test/
│ │ │ │ ├── Rule.yml
│ │ │ │ └── Rule2.yml
│ │ │ ├── test.adoc
│ │ │ ├── test2.adoc
│ │ │ └── test3.adoc
│ │ ├── rst/
│ │ │ ├── .vale.ini
│ │ │ ├── styles/
│ │ │ │ └── Test/
│ │ │ │ └── Rule.yml
│ │ │ └── test.rst
│ │ ├── subdir1/
│ │ │ ├── test.hs
│ │ │ └── test.rs
│ │ ├── subdir2/
│ │ │ └── test.lua
│ │ ├── subdir3/
│ │ │ ├── .vale.ini
│ │ │ └── test.mdx
│ │ ├── test.adoc
│ │ ├── test.cc
│ │ ├── test.clj
│ │ ├── test.css
│ │ ├── test.dita
│ │ ├── test.hs
│ │ ├── test.java
│ │ ├── test.jl
│ │ ├── test.json
│ │ ├── test.jsx
│ │ ├── test.lua
│ │ ├── test.md
│ │ ├── test.mdx
│ │ ├── test.org
│ │ ├── test.php
│ │ ├── test.proto
│ │ ├── test.ps1
│ │ ├── test.py
│ │ ├── test.r
│ │ ├── test.rb
│ │ ├── test.rs
│ │ ├── test.rst
│ │ ├── test.ts
│ │ ├── test.txt
│ │ ├── test.xml
│ │ ├── test.xyz
│ │ └── test.yml
│ ├── fragments/
│ │ ├── .vale.ini
│ │ ├── styles/
│ │ │ └── config/
│ │ │ └── vocabularies/
│ │ │ └── Base/
│ │ │ ├── accept.txt
│ │ │ └── reject.txt
│ │ ├── test.cc
│ │ ├── test.go
│ │ ├── test.js
│ │ ├── test.py
│ │ ├── test.rb
│ │ ├── test.rs
│ │ ├── test2.py
│ │ └── test2.rs
│ ├── frontmatter/
│ │ ├── .vale.ini
│ │ ├── test.adoc
│ │ ├── test.md
│ │ ├── test.rst
│ │ ├── test2.md
│ │ ├── test2.mdx
│ │ ├── test3.md
│ │ └── test3.mdx
│ ├── glob/
│ │ ├── .vale.ini
│ │ └── content/
│ │ ├── a.md
│ │ ├── b/
│ │ │ └── b.md
│ │ └── c.md
│ ├── i18n/
│ │ ├── .vale.ini
│ │ ├── ru.adoc
│ │ └── zh.md
│ ├── misc/
│ │ ├── duplicates/
│ │ │ ├── .vale
│ │ │ ├── test.adoc
│ │ │ ├── test.md
│ │ │ └── test2.md
│ │ ├── filesystem/
│ │ │ └── projects/
│ │ │ ├── .vale.ini
│ │ │ ├── ini/
│ │ │ │ ├── .vale.ini
│ │ │ │ └── styles/
│ │ │ │ ├── Microsoft/
│ │ │ │ │ └── Headings.yml
│ │ │ │ └── Vocab/
│ │ │ │ └── Basic/
│ │ │ │ ├── accept.txt
│ │ │ │ ├── reject.txt
│ │ │ │ └── vocab.txt
│ │ │ └── test.md
│ │ ├── ids/
│ │ │ ├── .vale.ini
│ │ │ ├── test.adoc
│ │ │ └── test2.adoc
│ │ ├── infostring/
│ │ │ ├── .vale.ini
│ │ │ └── test.md
│ │ ├── line-endings/
│ │ │ ├── .vale
│ │ │ ├── CR.md
│ │ │ └── CRLF.md
│ │ ├── markup/
│ │ │ ├── .vale.ini
│ │ │ └── test.md
│ │ ├── one/
│ │ │ └── two/
│ │ │ └── three/
│ │ │ ├── four/
│ │ │ │ └── test.yml
│ │ │ └── test.yml
│ │ ├── symlinks/
│ │ │ ├── .vale.ini
│ │ │ ├── Symlinked/
│ │ │ │ └── Code.yml
│ │ │ ├── styles/
│ │ │ │ └── .gitkeep
│ │ │ └── test.md
│ │ └── unicode/
│ │ ├── .vale
│ │ ├── test.py
│ │ ├── test.rst
│ │ └── test.txt
│ ├── patterns/
│ │ ├── .vale.ini
│ │ ├── test.adoc
│ │ ├── test.html
│ │ ├── test.md
│ │ ├── test.mdx
│ │ ├── test.org
│ │ ├── test.py
│ │ ├── test.rst
│ │ ├── test2.html
│ │ ├── test2.rst
│ │ ├── test3.html
│ │ └── test4.html
│ ├── pkg/
│ │ ├── complete/
│ │ │ ├── .gitignore
│ │ │ ├── .vale.ini
│ │ │ ├── styles/
│ │ │ │ └── .gitkeep
│ │ │ └── test.md
│ │ ├── config/
│ │ │ ├── .gitignore
│ │ │ ├── .vale.ini
│ │ │ ├── styles/
│ │ │ │ └── .gitkeep
│ │ │ └── test.md
│ │ └── style/
│ │ ├── .gitignore
│ │ ├── .vale.ini
│ │ ├── styles/
│ │ │ └── .gitkeep
│ │ └── test.md
│ ├── plugins/
│ │ ├── .vale.ini
│ │ └── test.md
│ ├── scopes/
│ │ ├── attr/
│ │ │ ├── .vale.ini
│ │ │ ├── test.adoc
│ │ │ ├── test.html
│ │ │ ├── test.md
│ │ │ └── test.rst
│ │ ├── blockquote/
│ │ │ ├── .vale.ini
│ │ │ ├── test.adoc
│ │ │ ├── test.md
│ │ │ └── test.rst
│ │ ├── heading/
│ │ │ ├── _vale
│ │ │ ├── test.adoc
│ │ │ ├── test.dita
│ │ │ ├── test.html
│ │ │ ├── test.md
│ │ │ ├── test.rst
│ │ │ └── test.xml
│ │ ├── link/
│ │ │ ├── .vale.ini
│ │ │ ├── test.adoc
│ │ │ ├── test.md
│ │ │ ├── test.rst
│ │ │ └── vale
│ │ ├── list/
│ │ │ ├── _vale
│ │ │ ├── test.adoc
│ │ │ ├── test.html
│ │ │ ├── test.md
│ │ │ └── test.rst
│ │ ├── multi/
│ │ │ ├── .vale.ini
│ │ │ └── test.md
│ │ ├── raw/
│ │ │ ├── .vale.ini
│ │ │ ├── test.md
│ │ │ └── test.py
│ │ ├── rules/
│ │ │ ├── Alt.yml
│ │ │ ├── And.yml
│ │ │ ├── Code.yml
│ │ │ ├── Fence.yml
│ │ │ ├── H2.yml
│ │ │ ├── H3.yml
│ │ │ ├── HN.yml
│ │ │ ├── Heading.yml
│ │ │ ├── Link.yml
│ │ │ ├── List.yml
│ │ │ ├── MinH2.yml
│ │ │ ├── Negated.yml
│ │ │ ├── Para.yml
│ │ │ ├── Quote.yml
│ │ │ ├── Raw.yml
│ │ │ ├── Sentence.yml
│ │ │ ├── Strong.yml
│ │ │ ├── Table.yml
│ │ │ └── Title.yml
│ │ ├── sentence/
│ │ │ ├── .vale.ini
│ │ │ └── test.md
│ │ ├── skip/
│ │ │ ├── .vale.ini
│ │ │ ├── test.md
│ │ │ └── test.rst
│ │ └── table/
│ │ ├── _vale
│ │ ├── test.adoc
│ │ ├── test.html
│ │ ├── test.md
│ │ └── test.rst
│ ├── sections/
│ │ ├── .vale.ini
│ │ └── test.md
│ ├── spelling/
│ │ ├── .vale.ini
│ │ ├── test.adoc
│ │ ├── test.dic.md
│ │ ├── test.html
│ │ ├── test.md
│ │ ├── test.rst
│ │ └── test.txt
│ ├── spellingv3/
│ │ ├── .vale.ini
│ │ ├── test.adoc
│ │ ├── test.dic.md
│ │ ├── test.html
│ │ ├── test.md
│ │ └── test.rst
│ ├── styles/
│ │ ├── Readability/
│ │ │ ├── .vale.ini
│ │ │ ├── test.md
│ │ │ ├── test2.md
│ │ │ └── test3.md
│ │ ├── Scripts/
│ │ │ ├── .vale.ini
│ │ │ └── test.md
│ │ ├── demo/
│ │ │ ├── _vale
│ │ │ ├── test.adoc
│ │ │ ├── test.html
│ │ │ ├── test.md
│ │ │ ├── test.mdx
│ │ │ ├── test.rst
│ │ │ └── vocab.txt
│ │ ├── proselint/
│ │ │ ├── _vale
│ │ │ └── test.md
│ │ └── write-good/
│ │ ├── .vale
│ │ ├── test.cc
│ │ ├── test.md
│ │ └── test.txt
│ ├── templates/
│ │ ├── .vale.ini
│ │ ├── test.md
│ │ └── test2.md
│ ├── views/
│ │ ├── .vale.ini
│ │ ├── API.yml
│ │ ├── Petstore.yaml
│ │ ├── Rule.yml
│ │ ├── ansible.yml
│ │ ├── github-workflow.json
│ │ ├── newline.yml
│ │ ├── test.java
│ │ └── test.py
│ └── vocab/
│ ├── Basic/
│ │ ├── .vale.ini
│ │ └── test.md
│ ├── Multi/
│ │ ├── .vale.ini
│ │ └── test.md
│ └── styles/
│ └── config/
│ ├── ignorefiles/
│ │ └── vocab.txt
│ └── vocabularies/
│ ├── Basic/
│ │ ├── accept.txt
│ │ └── reject.txt
│ └── Second/
│ ├── accept.txt
│ └── reject.txt
├── pkg/
│ └── write-good/
│ ├── Cliches.yml
│ ├── E-Prime.yml
│ ├── Illusions.yml
│ ├── Passive.yml
│ ├── README.md
│ ├── So.yml
│ ├── ThereIs.yml
│ ├── TooWordy.yml
│ ├── Weasel.yml
│ └── meta.json
└── styles/
├── Bugs/
│ ├── EmptyReplace.yml
│ ├── KeepCase.yml
│ ├── MatchCase.yml
│ ├── Newline.yml
│ ├── SameCase.yml
│ ├── TermCase.yml
│ ├── URLCtx.yml
│ └── WrongExp.yml
├── Checks/
│ ├── MetricUndefined.yml
│ ├── MetricValue.yml
│ ├── MetricWords.yml
│ ├── MultiCapture.yml
│ └── ScriptRE.yml
├── Joblint/
│ ├── Acronyms.yml
│ ├── Benefits.yml
│ ├── Bro.yml
│ ├── Competitive.yml
│ ├── Derogatory.yml
│ ├── DevEnv.yml
│ ├── DumbTitles.yml
│ ├── Gendered.yml
│ ├── Hair.yml
│ ├── LegacyTech.yml
│ ├── Meritocracy.yml
│ ├── Profanity.yml
│ ├── README.md
│ ├── Reassure.yml
│ ├── Sexualised.yml
│ ├── Starter.yml
│ ├── TechTerms.yml
│ ├── Visionary.yml
│ └── meta.json
├── LanguageTool/
│ ├── AMBIG.yml
│ ├── APOS_ARE.yml
│ ├── ARE_USING.yml
│ ├── HYPHEN.yml
│ ├── Metadata.yml
│ ├── OF_ALL_TIMES.yml
│ └── WOULD_BE_JJ_VB.yml
├── Limit/
│ └── Rule.yml
├── Markup/
│ ├── ID.yml
│ ├── Repetition.yml
│ └── SentSpacing.yml
├── Meta/
│ └── Title.yml
├── README.md
├── RU/
│ └── RUSwap.yml
├── Readability/
│ ├── AutomatedReadability.yml
│ ├── ColemanLiau.yml
│ ├── FleschKincaid.yml
│ ├── FleschReadingEase.yml
│ ├── GunningFog.yml
│ ├── LIX.yml
│ └── SMOG.yml
├── Scopes/
│ ├── Code.yml
│ └── Titles.yml
├── Scripts/
│ ├── CustomMsg.yml
│ └── Test.yml
├── Spelling/
│ ├── GB.yml
│ ├── Ignore.yml
│ ├── Ignores.yml
│ ├── Test.yml
│ └── ignore/
│ ├── base.txt
│ └── second.txt
├── ZH/
│ └── Simple.yml
├── config/
│ ├── actions/
│ │ └── CamelToSnake.tengo
│ ├── dictionaries/
│ │ ├── en-GB.aff
│ │ ├── en-GB.dic
│ │ ├── en_US.aff
│ │ ├── en_US.dic
│ │ ├── en_medical.aff
│ │ ├── en_medical.dic
│ │ ├── test.aff
│ │ └── test.dic
│ ├── filters/
│ │ ├── extends.expr
│ │ ├── levels.expr
│ │ ├── min.expr
│ │ └── scope.expr
│ ├── ignore/
│ │ ├── base.txt
│ │ └── tech/
│ │ └── second.txt
│ ├── scripts/
│ │ └── NewSection.tengo
│ ├── templates/
│ │ ├── cli.tmpl
│ │ ├── collate.tmpl
│ │ ├── gitlab.tmpl
│ │ └── line.tmpl
│ ├── views/
│ │ ├── Ansible.yml
│ │ ├── GitHubActions.yml
│ │ ├── NewLine.yml
│ │ ├── OpenAPI.yml
│ │ ├── Petstore.yml
│ │ ├── Python.yml
│ │ ├── Strings.yml
│ │ └── Vale.yml
│ └── vocabularies/
│ ├── Cap/
│ │ ├── accept.txt
│ │ └── reject.txt
│ └── Rep/
│ ├── accept.txt
│ └── reject.txt
├── demo/
│ ├── Abbreviations.yml
│ ├── Cap.yml
│ ├── CapSub.yml
│ ├── CharCount.yml
│ ├── Code.yml
│ ├── CommasPerSentence.yml
│ ├── Contractions.yml
│ ├── CustomCap.yml
│ ├── Ending-Preposition.yml
│ ├── Fancy.yml
│ ├── Filters.yml
│ ├── HeadingStartsWithCapital.yml
│ ├── Hyphen.yml
│ ├── LookAround.yml
│ ├── Meet-up.yml
│ ├── Meetup.yml
│ ├── MinCount.yml
│ ├── ParagraphLength.yml
│ ├── Raw.yml
│ ├── Reading.yml
│ ├── ScopedHeading.yml
│ ├── SentenceCase.yml
│ ├── SentenceCaseAny.yml
│ ├── SentenceLength.yml
│ ├── Smart.yml
│ ├── Spacing.yml
│ ├── Spellcheck.yml
│ ├── Spelling.yml
│ ├── Terms.yml
│ └── ZeroOccurrence.yml
├── proselint/
│ ├── Airlinese.yml
│ ├── AnimalLabels.yml
│ ├── Annotations.yml
│ ├── Apologizing.yml
│ ├── Archaisms.yml
│ ├── But.yml
│ ├── Cliches.yml
│ ├── CorporateSpeak.yml
│ ├── Currency.yml
│ ├── Cursing.yml
│ ├── DateCase.yml
│ ├── DateMidnight.yml
│ ├── DateRedundancy.yml
│ ├── DateSpacing.yml
│ ├── DenizenLabels.yml
│ ├── Diacritical.yml
│ ├── GenderBias.yml
│ ├── GroupTerms.yml
│ ├── Hedging.yml
│ ├── Hyperbole.yml
│ ├── Illusions.yml
│ ├── Jargon.yml
│ ├── LGBTOffensive.yml
│ ├── LGBTTerms.yml
│ ├── Malapropisms.yml
│ ├── Needless.yml
│ ├── Nonwords.yml
│ ├── Oxymorons.yml
│ ├── P-Value.yml
│ ├── RASSyndrome.yml
│ ├── README.md
│ ├── Skunked.yml
│ ├── Spelling.yml
│ ├── Typography.yml
│ ├── Uncomparables.yml
│ ├── Very.yml
│ └── meta.json
├── vale/
│ ├── Annotations.yml
│ ├── Editorializing.yml
│ ├── Hedging.yml
│ ├── Litotes.yml
│ ├── Redundancy.yml
│ ├── Spacing.yml
│ └── Uncomparables.yml
└── write-good/
├── Cliches.yml
├── E-Prime.yml
├── Illusions.yml
├── Passive.yml
├── README.md
├── So.yml
├── ThereIs.yml
├── TooWordy.yml
├── We.yml
├── Weasel.yml
└── meta.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
CRLF.md text eol=crlf
CR.md text eol=cr
fixtures/** linguist-vendored=true
features/** linguist-vendored=true
testdata/** linguist-vendored=true
================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
jdkato@vale.sh.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing to Vale
Interested in contributing to Vale? Great—we welcome contributions of any kind including documentation improvements, bug reports, feature requests, and pull requests.
## Table of Contents
- [Contributing to Vale](#contributing-to-vale)
- [Table of Contents](#table-of-contents)
- [Introduction](#introduction)
- [Testing](#testing)
- [Setting up a Development Environment](#setting-up-a-development-environment)
- [Code Contribution Guidelines](#code-contribution-guidelines)
- [Git Commit Message Guidelines](#git-commit-message-guidelines)
- [Terminology](#terminology)
## Introduction
Vale is a natural language linter that supports plain text, markup (Markdown, reStructuredText, AsciiDoc, and HTML), and source code comments. However, unlike many similar projects, Vale's primary focus isn't on providing a collection of rules everyone must follow—instead, Vale aims to be flexible enough to support many different styles (see [Styles](https://errata-ai.github.io/vale/styles/) for more information).
More specifically, Vale is written in Go and split into packages that are tasked with implementing specific functionality:
- `check` handles the loading and validating of external rules (YAML files).
- `core`: includes the main structures used throughout the application (e.g., `File` and `Alert`) and manages configuration files.
- `lint` handles the actual linting, which includes knowing when to apply rules and how to handle specific file formats.
- `rule` implements Vale's built-in style.
- `ui` manages displaying information to users.
If you're looking to improve Vale's documentation, check out the [`docs/`](https://github.com/errata-ai/vale/tree/master/docs) directory.
## Testing
Vale is tested using both integration and unit tests.
Integration tests are the most plentiful at the moment. They're implemented using the behavior-driven development framework [Cucumber](https://cucumber.io/). You'll find the relevant files for these tests in the `fixtures` and `features` directories. Unit tests are found in the `*_test.go` files inside the actual Go packages.
We also track Vale's performance on a per-commit basis through benchmarks. On every commit, you'll see comparison against the last tagged release (over 5 runs) on CI builds:
```text
LintRST-2 1.63s ± 2% 1.65s ± 2% +0.95% (p=0.031 n=10+10)
LintMD-2 1.54s ± 1% 1.54s ± 1% ~ (p=0.604 n=10+10)
```
To run the tests, you'll want to invoke either `make bench` or `make ci` (see [Setting up a Development Environment]() for more information).
## Setting up a Development Environment
Prerequisites:
* [Ruby](https://www.ruby-lang.org/en/downloads/) (v2.3+)
* [Go](https://golang.org/) (v1.7+) installed.
* [Asciidoctor](http://asciidoctor.org/) available on your `$PATH`.
* [rst2html](http://docutils.sourceforge.net/docs/user/tools.html#rst2html-py) available on your `$PATH`. The latter is installed with both [Sphinx](http://www.sphinx-doc.org/en/stable/) and [docutils](https://pypi.python.org/pypi/docutils).
* [xsltproc](http://xmlsoft.org/xslt/xsltproc.html) available on your `$PATH`.
* [dita](https://www.dita-ot.org/download) available on your `$PATH` (v3.6+).
```bash
$ gem install bundler:2.2.31 # if necessary
$ make setup
$ make test
```
## Code Contribution Guidelines
To make the contribution process as seamless as possible, we ask for the following:
* Fork the project and make your changes.
* When you’re ready to create a pull request, be sure to:
* Run `make lint` to check your Go code style.
* Squash your commits into a single commit. `git rebase -i`. It’s okay to force update your pull request with `git push -f`.
* Follow the **Git Commit Message Guidelines** below.
## Git Commit Message Guidelines
Vale follows a modified version of the [AngularJS Commit Guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#-git-commit-guidelines). A commit message should take the following form:
```
<type>: <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```
with `<body>` and `<footer>` being optional. `<type>` should be one of the following:
- `feat`: A new feature
- `fix`: A bug fix
- `docs`: Documentation only changes (e.g., this document, the README, or source comments)
- `style`: Changes that do not affect the meaning of the code (e.g., code formatting)
- refactor: A code change that neither fixes a bug nor adds a feature
- `perf`: A code change that improves performance (in this case, please include relevant benchmark(s))
- `test`: Adding missing or correcting existing tests
- `chore`: Changes to the build process or auxiliary tools
An example would be something like:
```text
refactor: make "warning" the default lint level
Also demotes `Annotations` and `PassiveVoice` to "suggestions."
Related to #30.
```
## Terminology
| Term | Definition |
|:-----:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| check | A "check" is one of Vale's extension points (e.g., `existence` and `substitution`) that performs a single task such as looking for the existence of a word. |
| rule | A "rule" is an actual implementation of a check. For example, [`Hedging`](https://github.com/errata-ai/vale/blob/master/rule/vale/Hedging.yml) is one of Vale's built-in rules. |
| style | A "style" is a collection of rules. For example, [`Joblint`](https://github.com/errata-ai/vale/tree/master/rule/Joblint) is a style that consists of rules such as `LegacyTech`. |
================================================
FILE: .github/ISSUE_TEMPLATE/0_feature_request.yml
================================================
name: Feature Request
description: "Request new functionality or improvements to existing functionality."
labels: ["Type: Enhancement"]
body:
- type: checkboxes
attributes:
label: Check for existing issues
description: >
Check the backlog of issues to reduce the chances of creating
duplicates; if an issue already exists, place a `+1` (👍) on it.
options:
- label: Completed
required: true
- type: textarea
attributes:
label: Describe the feature
description: A clear and concise description of what you want to happen.
validations:
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/1_bug_report.yml
================================================
name: Bug Report
description: "Report a bug, crash, or unexpected behavior."
labels: ["Type: Bug"]
body:
- type: checkboxes
attributes:
label: Check for existing issues
description: >
Check the backlog of issues to reduce the chances of creating
duplicates; if an issue already exists, place a `+1` (👍) on it.
options:
- label: Completed
required: true
- type: textarea
id: environment
attributes:
label: Environment
description: |
Please provide the following information:
- OS (e.g., macOS, Windows, Linux, etc.)
- Install method (e.g., Go, Homebrew, direct download, etc.)
- Vale version (the result of `vale -v`)
validations:
required: true
- type: textarea
attributes:
label: Describe the bug / provide steps to reproduce it
description: >
A clear and concise description of what the bug is. When possible,
please provide plain-text examples instead of screenshots.
validations:
required: true
================================================
FILE: .github/workflows/codeql.yml
================================================
name: "CodeQL"
on:
push:
branches: [ 'v2' ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ 'v2' ]
schedule:
- cron: '14 15 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
queries: +security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"
================================================
FILE: .github/workflows/golangci-lint.yml
================================================
name: golangci-lint
on:
push:
branches:
- v3
pull_request:
permissions:
contents: read
# Optional: allow read access to pull requests. Use with `only-new-issues` option.
# pull-requests: read
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
go-version: "1.25.7"
- name: golangci-lint
uses: golangci/golangci-lint-action@v8
with:
version: v2.5
================================================
FILE: .github/workflows/main.yml
================================================
name: Build + Lint
on: push
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.23"
- name: Setup release environment
run: |-
echo 'GITHUB_TOKEN=${{secrets.GITHUB_TOKEN}}' > .release-env
echo 'CHOCOLATEY_API_KEY=${{secrets.CHOCOLATEY_API_KEY}}' >> .release-env
- name: GoReleaser
run: make choco-cross
if: startsWith(github.ref, 'refs/tags/')
lint:
name: runner / vale
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: errata-ai/vale-action@v2.1.1
with:
files: README.md
debug: true
fail_on_error: true
reporter: github-check
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
================================================
FILE: .gitignore
================================================
# Folders
docs/public*
_obj
_test
vendor/bundle/
builds
bin
tmp
tags
site
target
dist
_vendor-*
docs/public/
fixtures/formats/Sphinx/_build/
.vscode
.idea
.fleet
*.sublime-workspace
*.sublime-project
.release-env
.DS_Store
# Files
*.o
*.a
*.so
*.exe
*.prof
*.swp
*~
.bundle
.vagrant
*.snap
.github/styles/*
!.github/styles/config/
.github/styles/config/*
!.github/styles/config/vocabularies/
.github/styles/config/vocabularies/*
!.github/styles/config/vocabularies/Vale
================================================
FILE: .gitlab-ci.yml
================================================
image: golang:1.25
stages:
- test
- release
test:
stage: test
script:
- export BUNDLE_GEMFILE=$PWD/testdata/Gemfile
- export PATH=$PATH:$PWD/dita-ot-3.6/bin:$PWD/bin
- apt-get -qq update
- apt-get install -y curl gnupg build-essential default-jre
- apt-get install -y xsltproc zip unzip python3-pip ruby-full nodejs npm
- apt install -y python3-sphinx
- gem install asciidoctor bundler
- npm install -g mdx2vast
- wget https://github.com/dita-ot/dita-ot/releases/download/3.6/dita-ot-3.6.zip
- unzip dita-ot-3.6.zip > /dev/null 2>&1
- which dita
- dita -h
- make setup
- make build os=linux exe=vale
- make test
================================================
FILE: .golangci.yml
================================================
version: "2"
run:
go: "1.25"
linters:
default: none
enable:
- asciicheck
- bidichk
- bodyclose
- contextcheck
- dupl
- dupword
- durationcheck
- errname
- errorlint
- exhaustive
- gocritic
- gomoddirectives
- gomodguard
- goprintffuncname
- gosec
- govet
- ineffassign
- makezero
- mirror
- nakedret
- nilerr
- nilnil
- nolintlint
- nonamedreturns
- nosprintfhostport
- predeclared
- promlinter
- revive
- rowserrcheck
- sqlclosecheck
- thelper
- tparallel
- unconvert
- unparam
- unused
- usestdlibvars
- wastedassign
- whitespace
settings:
cyclop:
max-complexity: 30
package-average: 10
errcheck:
check-type-assertions: true
gocritic:
settings:
captLocal:
paramsOnly: false
underef:
skipRecvDeref: false
gomodguard:
blocked:
modules:
- github.com/golang/protobuf:
recommendations:
- google.golang.org/protobuf
reason: see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules
- github.com/satori/go.uuid:
recommendations:
- github.com/google/uuid
reason: satori's package is not maintained
- github.com/gofrs/uuid:
recommendations:
- github.com/google/uuid
reason: "see recommendation from dev-infra team: https://confluence.gtforge.com/x/gQI6Aw"
govet:
disable:
- fieldalignment
enable-all: true
settings:
shadow:
strict: true
nakedret:
max-func-lines: 0
nolintlint:
require-explanation: false
require-specific: true
allow-no-explanation:
- funlen
- gocognit
- lll
rowserrcheck:
packages:
- github.com/jmoiron/sqlx
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
================================================
FILE: .goreleaser.yml
================================================
env:
- CGO_ENABLED=1
builds:
- id: vale-darwin-amd64
main: ./cmd/vale
binary: vale
goarch:
- amd64
goos:
- darwin
env:
- CC=o64-clang
- CXX=o64-clang++
flags:
- -trimpath
- id: vale-darwin-arm64
main: ./cmd/vale
binary: vale
goarch:
- arm64
goos:
- darwin
env:
- CC=oa64-clang
- CXX=oa64-clang++
flags:
- -trimpath
- id: vale-linux-amd64
main: ./cmd/vale
binary: vale
goarch:
- amd64
goos:
- linux
env:
- CC=x86_64-linux-gnu-gcc
- CXX=x86_64-linux-gnu-g++
flags:
- -trimpath
- id: vale-linux-arm64
main: ./cmd/vale
binary: vale
goarch:
- arm64
goos:
- linux
env:
- CC=aarch64-linux-gnu-gcc
- CXX=aarch64-linux-gnu-g++
flags:
- -trimpath
- id: vale-windows-amd64
main: ./cmd/vale
binary: vale
goarch:
- amd64
goos:
- windows
env:
- CC=x86_64-w64-mingw32-gcc
- CXX=x86_64-w64-mingw32-g++
flags:
- -trimpath
- -buildmode=exe
ldflags:
- -s -w -X main.version={{.Version}}
- -extldflags=-static
archives:
- format: tar.gz
format_overrides:
- goos: windows
format: zip
name_template: >-
vale_{{ .Version }}_
{{- if eq .Os "darwin" }}macOS_
{{- else }}{{- title .Os }}_{{ end }}
{{- if eq .Arch "amd64" }}64-bit
{{- else }}{{ .Arch }}{{ end }}
chocolateys:
- # Your app's package name.
# The value may not contain spaces or character that are not valid for a URL.
# If you want a good separator for words, use '-', not '.'.
#
# Defaults to `ProjectName`.
name: Vale
# IDs of the archives to use.
# Defaults to empty, which includes all artifacts.
# ids:
# - vale
# Your app's owner.
# It basically means your.
# Defaults empty.
owners: Joseph Kato
# The app's title.
# A human-friendly title of the package.
# Defaults to `ProjectName`.
title: Vale
# Your app's authors (probably you).
# Defaults are shown below.
authors: Joseph Kato
# Your app's project url.
# It is a required field.
project_url: https://github.com/errata-ai/vale
# Template for the url which is determined by the given Token (github,
# gitlab or gitea)
# Default depends on the client.
#
# https://github.com/errata-ai/vale/releases/download/v2.21.0/vale_2.21.0_Windows_64-bit.zip
url_template: "https://github.com/errata-ai/vale/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
# App's icon.
# Default is empty.
icon_url: https://github.com/errata-ai.png
# Your app's copyright details.
# Default is empty.
copyright: © Joseph Kato
# App's license information url.
license_url: https://github.com/errata-ai/vale/blob/master/LICENSE
# Your apps's require license acceptance:
# Specify whether the client must prompt the consumer to accept the package
# license before installing.
# Default is false.
require_license_acceptance: false
# Your app's source url.
# Default is empty.
project_source_url: https://github.com/errata-ai/install/tree/main/pkg/chocolatey
# Your app's documentation url.
# Default is empty.
docs_url: https://vale.sh/
# App's bugtracker url.
# Default is empty.
bug_tracker_url: https://github.com/errata-ai/vale/issues
# Your app's tag list.
# Default is empty.
tags: "vale linter prose admin"
# Your app's summary:
summary: Vale — the customizable linter for prose.
# This the description of your chocolatey package.
# Supports markdown.
description: |
Vale is a free, open-source linter for prose built with speed and
extensibility in mind.
# Your app's release notes.
# A description of the changes made in this release of the package.
# Supports markdown. To prevent the need to continually update this field,
# providing a URL to an external list of Release Notes is perfectly
# acceptable.
# Default is empty.
release_notes: "https://github.com/errata-ai/vale/releases/tag/v{{ .Version }}"
# The api key that should be used to push to the chocolatey repository.
#
# WARNING: do not expose your api key in the configuration file!
api_key: "{{ .Env.CHOCOLATEY_API_KEY }}"
# The source repository that will push the package to.
#
# Defaults are shown below.
source_repo: "https://push.chocolatey.org/"
# Setting this will prevent goreleaser to actually try to push the package
# to chocolatey repository, leaving the responsability of publishing it to
# the user.
skip_publish: false
================================================
FILE: .pre-commit-hooks.yaml
================================================
- id: vale
name: vale
description: Run vale on your text
entry: vale
language: golang
types: [text]
================================================
FILE: .vale.ini
================================================
StylesPath = .github/styles
MinAlertLevel = suggestion
Vocab = Vale
Packages = Microsoft, Readability
[README.md]
BasedOnStyles = Vale
================================================
FILE: .well-known/funding-manifest-urls
================================================
https://vale.sh/funding.json
================================================
FILE: Dockerfile
================================================
# syntax=docker/dockerfile:1
ARG GOLANG_VER=1.25
FROM golang:${GOLANG_VER}-alpine AS build
# See https://cloud.docker.com/repository/docker/jdkato/vale
# TODO: DITA / XML:
# openjdk11 \
# libxslt \
# COPY bin/dita-ot-3.6 /
#
# This currently isn't packaged because it makes the size 7x as big.
# Debug shell: $ docker run -it --entrypoint /bin/sh jdkato/vale -s
RUN apk add build-base
COPY . /app/
WORKDIR /app
ENV CGO_ENABLED=1
ARG ltag
RUN go build -ldflags "-s -w -X main.version=$ltag" -o /app/vale ./cmd/vale
FROM alpine
RUN apk add --no-cache \
py3-docutils \
asciidoctor \
nodejs \
npm
RUN npm install -g mdx2vast
COPY --from=build /app/vale /bin
# ENV PATH="/bin:/dita-ot-3.6/bin:$PATH"
ENTRYPOINT ["/bin/vale"]
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2016 Joseph Kato
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile
================================================
PACKAGE_NAME := github.com/errata-ai/vale/v3
GOLANG_CROSS_VERSION ?= v0.2.0
SYSROOT_DIR ?= sysroots
SYSROOT_ARCHIVE ?= sysroots.tar.bz2
LAST_TAG := $(shell git describe --abbrev=0 --tags)
CURR_SHA := $(shell git rev-parse --verify HEAD)
LDFLAGS := -ldflags "-s -w -X main.version=$(LAST_TAG)"
DOCKER_BUILD_TARGETS := linux/arm64,linux/amd64
DOCKER_USER ?= jdkato
.PHONY: data test lint install rules setup bench compare release choco-cross
all: build
# make release tag=v0.4.3
release:
git tag $(tag)
git push origin $(tag)
# If os and/or arch are not set, default values are used which are set by the system used for building
# make build os=darwin
# make build os=windows
# make build os=linux
# make build os=darwin arch=arm64
# make build os=windows arch=amd64
# make build os=linux arch=amd64
# make build os=linux arch=arm64
build:
GOOS=$(os) GOARCH=$(arch) go build ${LDFLAGS} -o bin/$(exe) ./cmd/vale
bench:
go test -bench=. -benchmem ./internal/core ./internal/lint ./internal/check
profile:
go test -benchmem -run=^$$ -bench ^BenchmarkLintMD$$ github.com/errata-ai/vale/v2/internal/lint -cpuprofile=bin/cpu.out -memprofile=bin/mem.out -trace=bin/trace.out
mv lint.test bin
# go install github.com/aclements/go-misc/benchmany@latest
# go install golang.org/x/tools/cmd/benchcmp@latest
# go install golang.org/x/perf/cmd/benchstat@latest
compare:
cd internal/lint && \
benchmany -n 10 -o new.txt ${CURR_SHA} && \
benchmany -n 10 -o old.txt ${LAST_TAG} && \
benchstat old.txt new.txt
setup:
cd testdata && bundle install && cd -
test:
go test ./internal/core ./internal/lint ./internal/check ./internal/nlp ./internal/glob ./cmd/vale
cd testdata && cucumber --format progress && cd -
docker:
@echo ${DOCKER_PASS} | docker login -u ${DOCKER_USER} --password-stdin
# Ignore command failure
-docker buildx create \
--name container-builder \
--driver docker-container \
--use --bootstrap
docker buildx build \
--build-arg ltag=${LAST_TAG} \
--platform=${DOCKER_BUILD_TARGETS} \
--file Dockerfile \
--tag ${DOCKER_USER}/vale:${LAST_TAG} \
--tag ${DOCKER_USER}/vale:latest \
--push \
.
docker buildx rm container-builder #Tidy up
choco-cross:
@docker run \
--rm \
-e CGO_ENABLED=1 \
--env-file .release-env \
-v /var/run/docker.sock:/var/run/docker.sock \
-v `pwd`:/go/src/$(PACKAGE_NAME) \
-v `pwd`/sysroot:/sysroot \
-w /go/src/$(PACKAGE_NAME) \
jdkato/choco-cross:${GOLANG_CROSS_VERSION} \
release --clean
================================================
FILE: README.md
================================================
# Vale: Your style, our editor [](https://ci.appveyor.com/project/jdkato/vale) [](https://github.com/errata-ai/vale/releases) [](https://hub.docker.com/r/jdkato/vale) [](https://community.chocolatey.org/packages/vale) [](https://formulae.brew.sh/formula/vale) [](https://gurubase.io/g/vale)
<p align="center">
<b>Vale</b> is a command-line tool that brings code-like linting to prose. It's <b><a href="#mag-at-a-glance-vale-vs-">fast</a></b>, <b>cross-platform</b> (Windows, macOS, and Linux), and <b>highly customizable</b>.
</p>
<p align="center">
<img width="75%" alt="A demo screenshot." src="https://vale.sh/media/mac.png">
</p>
<div align="center">
<table>
<thead>
<tr>
<th><a href="https://vale.sh/docs/vale-cli/installation/">Docs</a></th>
<th><a href="https://studio.vale.sh/">Vale Studio</a></th>
<th><a href="https://vale.sh/hub/">Package Hub</a></th>
<th><a href="https://vale.sh/explorer/">Rule Explorer</a></th>
<th><a href="https://vale.sh/generator/">Config Generator</a></th>
</tr>
</thead>
</table>
</div>
## :heart: Sponsors
> Hi there! I'm [@jdkato](https://github.com/jdkato), the sole developer of Vale. If you'd like to help me dedicate more time to _developing_, _documenting_, and _supporting_ Vale, feel free to donate through [GitHub Sponsors](https://github.com/sponsors/jdkato) or [Open Collective](https://opencollective.com/vale). Any donation—big, small, one-time, or recurring—is greatly appreciated!
### Organizations
<a href="https://opencollective.com/vale"><img src="https://opencollective.com/vale/organizations.svg?width=890"></a>
### Other
> Thanks to [DigitalOcean][1] for providing hosting credits for [Vale Studio][2].
<p>
<a href="https://www.digitalocean.com/?refcode=dc0864bb87fd&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge"><img src="https://web-platforms.sfo2.cdn.digitaloceanspaces.com/WWW/Badge%202.svg" alt="DigitalOcean Referral Badge" /></a>
</p>
[Deploy now on DigitalOcean](https://m.do.co/c/dc0864bb87fd) and get $200 in free credits!
### Individuals
<a href="https://opencollective.com/vale"><img src="https://opencollective.com/vale/individuals.svg?width=890"></a>
## :boom: Key Features
- [x] **Support for markup**: Vale has a rich understanding of many [markup formats](https://vale.sh/docs/topics/scoping/#formats), allowing it to avoid syntax-related false positives and intelligently exclude code snippets from prose-related rules.
- [x] A **highly customizable** [extension system](https://vale.sh/docs/topics/styles/): Vale is capable of enforcing _your style_—be it a standard [editorial style guide](https://github.com/errata-ai/styles#available-styles) or a custom in-house set of rules (see [examples][6]).
- [x] **Easy-to-install**, stand-alone binaries: Unlike other tools, Vale doesn't require you to install and configure a particular programming language and its related tooling (such as Python/pip or Node.js/npm).
See the [documentation](https://vale.sh) for more information.
## :mag: At a Glance: Vale vs. `<...>`
> **NOTE**: While all of the options listed below are open-source (CLI-based) linters for prose, their implementations and features vary significantly. And so, the "best" option will depends on your specific needs and preferences.
### Functionality
| Tool | Extensible | Checks | Supports Markup | Built With | License |
| ---------- | -------------------- | --------------- | ----------------------------------------------------------------------- | ---------- | ------------ |
| Vale | Yes (via YAML) | spelling, style | Yes (Markdown, AsciiDoc, reStructuredText, HTML, XML, Org) | Go | MIT |
| textlint | Yes (via JavaScript) | spelling, style | Yes (Markdown, AsciiDoc, reStructuredText, HTML, Re:VIEW) | JavaScript | MIT |
| RedPen | Yes (via Java) | spelling, style | Yes (Markdown, AsciiDoc, reStructuredText, Textile, Re:VIEW, and LaTeX) | Java | Apache-2.0 |
| write-good | Yes (via JavaScript) | style | No | JavaScript | MIT |
| proselint | No | style | No | Python | BSD 3-Clause |
| Joblint | No | style | No | JavaScript | MIT |
| alex | No | style | Yes (Markdown) | JavaScript | MIT |
The exact definition of "Supports Markup" varies by tool but, in general, it means that the format is understood at a higher level than a regular plain-text file (for example, features like excluding code blocks from spell check).
Extensibility means that there's a built-in means of creating your own rules without modifying the original source code.
### Benchmarks
<table>
<tr>
<td width="50%">
<a href="https://user-images.githubusercontent.com/8785025/97052257-809aa300-1535-11eb-83cd-65a52b29d6de.png">
<img src="https://user-images.githubusercontent.com/8785025/97052257-809aa300-1535-11eb-83cd-65a52b29d6de.png" width="100%">
</a>
</td>
<td width="50%">
<a href="https://user-images.githubusercontent.com/8785025/97051175-91e2b000-1533-11eb-9a57-9d44d6def4c3.png">
<img src="https://user-images.githubusercontent.com/8785025/97051175-91e2b000-1533-11eb-9a57-9d44d6def4c3.png" width="100%">
</a>
</td>
</tr>
<tr>
<td width="50%">
This benchmark has all three tools configured to use their implementations of the <code>write-good</code> rule set and Unix-style output.
</td>
<td width="50%">This benchmark runs Vale's implementation of <code>proselint</code>'s rule set against the original. Both tools are configured to use JSON output.</td>
</tr>
<tr>
<td width="50%">
<a href="https://user-images.githubusercontent.com/8785025/97053402-c5bfd480-1537-11eb-815b-a33ab13a59cf.png">
<img src="https://user-images.githubusercontent.com/8785025/97053402-c5bfd480-1537-11eb-815b-a33ab13a59cf.png" width="100%">
</a>
</td>
<td width="50%">
<a href="https://user-images.githubusercontent.com/8785025/97055850-7b8d2200-153c-11eb-86fa-d882ce6babf8.png">
<img src="https://user-images.githubusercontent.com/8785025/97055850-7b8d2200-153c-11eb-86fa-d882ce6babf8.png" width="100%">
</a>
</td>
</tr>
<tr>
<td width="50%">
This benchmark runs Vale's implementation of Joblint's rule set against the original. Both tools are configured to use JSON output.
</td>
<td width="50%">This benchmark has all three tools configured to perform only English spell checking using their default output styles.</td>
</tr>
</table>
All benchmarking was performed using the open-source [hyperfine](https://github.com/sharkdp/hyperfine) tool on a MacBook Pro (2.9 GHz Intel Core i7):
```
hyperfine --warmup 3 '<command>'
```
The corpus IDs in the above plots—`gitlab` and `ydkjs`—correspond to the following files:
- A [snapshot][7] of GitLab's open-source documentation (1,500 Markdown files).
- A [chapter][8] from the open-source book _You Don't Know JS_.
[1]: https://www.digitalocean.com/open-source/credits-for-projects
[2]: https://studio.vale.sh/
[3]: https://appwrite.io/oss-fund
[4]: https://appwrite.io/
[5]: https://page.famewall.io/vale
[6]: https://vale.sh/#users
[7]: https://gitlab.com/gitlab-org/gitlab/-/tree/7d6a4025a0346f1f50d2825c85742e5a27b39a8b/doc
[8]: https://raw.githubusercontent.com/getify/You-Dont-Know-JS/1st-ed/es6%20%26%20beyond/ch2.md
================================================
FILE: appveyor.yml
================================================
version: "{build}"
image: Visual Studio 2019
clone_folder: c:\GOPATH\src\github.com\vale-cli\vale
environment:
GOPATH: c:\GOPATH
GOROOT: C:\go
GOVERSION: 1.25.3
# tree-sitter
CGO_ENABLED: 1
CC: C:\msys64\mingw64\bin\gcc.exe
init:
- cmd: set PATH=C:\msys64\mingw64\bin;C:\MinGW\bin;%cd%/bin;%PATH%
install:
- set PATH=%GOPATH%\bin;c:\go\bin;C:\Ruby24\bin;%cd%;%PATH%
- go version
- go env
- choco install ansicon
- pip install docutils
- gem install asciidoctor
- npm install -g mdx2vast
- choco install xsltproc
# Git on AppVeyor includes a non-functional xsltproc. Remove this.
- if exist "%ProgramFiles%\Git\usr\bin\xsltproc.exe" del
"%ProgramFiles%\Git\usr\bin\xsltproc.exe"
- curl -fsSL -o C:\dita-ot-3.4.zip
https://github.com/dita-ot/dita-ot/releases/download/3.4/dita-ot-3.4.zip
- 7z x C:\dita-ot-3.4.zip -y -r -oC:\dita
- set PATH=C:\dita\dita-ot-3.4\bin;%PATH%
build_script:
- cmd: go version
- cmd: C:\msys64\usr\bin\make.exe setup
- cmd: C:\msys64\usr\bin\make.exe build os=windows exe=vale.exe
test_script:
- cmd: C:\msys64\usr\bin\make.exe test
# after_test:
# - cmd: make compare
================================================
FILE: cmd/vale/api.go
================================================
package main
import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"github.com/spf13/pflag"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/system"
)
// Style represents an externally-hosted style.
type Style struct {
// User-provided fields.
Author string `json:"author"`
Description string `json:"description"`
Deps string `json:"deps"`
Feed string `json:"feed"`
Homepage string `json:"homepage"`
Name string `json:"name"`
URL string `json:"url"`
// Generated fields.
HasUpdate bool `json:"has_update"`
InLibrary bool `json:"in_library"`
Installed bool `json:"installed"`
Addon bool `json:"addon"`
}
// Meta represents an installed style's meta data.
type Meta struct {
Author string `json:"author"`
Coverage float64 `json:"coverage"`
Description string `json:"description"`
Email string `json:"email"`
Feed string `json:"feed"`
Lang string `json:"lang"`
License string `json:"license"`
Name string `json:"name"`
Sources []string `json:"sources"`
URL string `json:"url"`
Vale string `json:"vale_version"`
Version string `json:"version"`
}
func init() {
pflag.BoolVar(&Flags.Remote, "mode-rev-compat", false,
"prioritize local Vale configurations")
pflag.StringVar(&Flags.Built, "built", "", "post-processed file path")
Actions["install"] = install
}
func fetch(src, dst string) error {
// Fetch the resource from the web:
resp, err := http.Get(src) //nolint:gosec,noctx
if err != nil {
return err
} else if resp.StatusCode != http.StatusOK {
return fmt.Errorf("could not fetch '%s' (status code '%d')", src, resp.StatusCode)
}
// Create a temp file to represent the archive locally:
tmpfile, err := os.CreateTemp("", "temp.*.zip")
if err != nil {
return err
}
defer os.Remove(tmpfile.Name()) // clean up
// Write to the local archive:
_, err = io.Copy(tmpfile, resp.Body)
if err != nil {
return err
} else if err = tmpfile.Close(); err != nil {
return err
}
resp.Body.Close()
return system.Unarchive(tmpfile.Name(), dst)
}
func install(args []string, flags *core.CLIFlags) error {
cfg, err := core.ReadPipeline(flags, false)
if err != nil {
return err
}
style := filepath.Join(cfg.StylesPath(), args[0])
if system.IsDir(style) {
os.RemoveAll(style) // Remove existing version
}
err = fetch(args[1], cfg.StylesPath())
if err != nil {
return sendResponse(
fmt.Sprintf("Failed to install '%s'", args[1]),
err)
}
return sendResponse(fmt.Sprintf(
"Successfully installed '%s'", args[1]), nil)
}
================================================
FILE: cmd/vale/color.go
================================================
package main
import (
"fmt"
"os"
"strings"
"github.com/olekukonko/tablewriter/tw"
"github.com/pterm/pterm"
"github.com/errata-ai/vale/v3/internal/core"
)
// PrintVerboseAlerts prints Alerts in verbose format.
func PrintVerboseAlerts(linted []*core.File, wrap bool) bool {
var errors, warnings, suggestions int
var e, w, s int
var symbol string
for _, f := range linted {
e, w, s = printVerboseAlert(f, wrap)
errors += e
warnings += w
suggestions += s
}
etotal := fmt.Sprintf("%d %s", errors, pluralize("error", errors))
wtotal := fmt.Sprintf("%d %s", warnings, pluralize("warning", warnings))
stotal := fmt.Sprintf("%d %s", suggestions, pluralize("suggestion", suggestions))
if errors > 0 || warnings > 0 {
symbol = "\u2716"
} else {
symbol = "\u2714"
}
n := len(linted)
if n == 1 && strings.HasPrefix(linted[0].Path, "stdin") {
fmt.Printf("%s %s, %s and %s in %s.\n", symbol,
pterm.Red(etotal), pterm.Yellow(wtotal),
pterm.Blue(stotal), "stdin")
} else {
fmt.Printf("%s %s, %s and %s in %d %s.\n", symbol,
pterm.Red(etotal), pterm.Yellow(wtotal),
pterm.Blue(stotal), n, pluralize("file", n))
}
return errors != 0
}
// printVerboseAlert includes an alert's line, column, level, and message.
func printVerboseAlert(f *core.File, wrap bool) (int, int, int) {
var loc, level string
var errors, warnings, notifications int
alerts := f.SortedAlerts()
if len(alerts) == 0 {
return 0, 0, 0
}
wrapMode := tw.WrapNone
if !wrap {
wrapMode = tw.WrapNormal
}
table := newBorderlessTable(os.Stdout, wrapMode)
fmt.Printf("\n %s", pterm.Underscore.Sprint(f.Path))
for _, a := range alerts {
switch a.Severity {
case "suggestion":
level = pterm.Blue(a.Severity)
notifications++
case "warning":
level = pterm.Yellow(a.Severity)
warnings++
case "error":
level = pterm.Red(a.Severity)
errors++
}
loc = fmt.Sprintf("%d:%d", a.Line, a.Span[0])
table.Append([]string{loc, level, a.Message, a.Check})
}
fmt.Println()
table.Render()
fmt.Println()
return errors, warnings, notifications
}
================================================
FILE: cmd/vale/command.go
================================================
package main
import (
"errors"
"fmt"
"os"
"path/filepath"
"github.com/jdkato/twine/nlp/tag"
"github.com/pterm/pterm"
"github.com/errata-ai/vale/v3/internal/check"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/lint"
"github.com/errata-ai/vale/v3/internal/nlp"
"github.com/errata-ai/vale/v3/internal/system"
)
// TaggedWord is a word with an NLP context.
type TaggedWord struct {
Token tag.Token
Line int
Span []int
}
// CompiledRule is a rule's compiled regex.
type CompiledRule struct {
Pattern string
}
var commandInfo = map[string]string{
"ls-config": "Print the current configuration to stdout.",
"ls-metrics": "Print the given file's internal metrics to stdout.",
"ls-dirs": "Print the default configuration directories to stdout.",
"ls-vars": "Print the supported environment variables to stdout.",
"sync": "Download and install external configuration sources.",
"host-install": "Install the Vale native messaging host for the given browser.",
"host-uninstall": "Uninstall the Vale native messaging host for the given browser.",
"fix": "Attempt to automatically fix the given alert.",
}
// Actions are the available CLI commands.
var Actions = map[string]func(args []string, flags *core.CLIFlags) error{
"ls-config": printConfig,
"ls-metrics": printMetrics,
"ls-dirs": printDirs,
"ls-vars": printVars,
"sync": sync,
// private
"host-install": installNativeHost,
"host-uninstall": uninstallNativeHost,
"compile": compileRule,
"run": runRule,
"transform": transform,
"ls-path": pathInfo,
"fix": fix,
"tag": runTag,
"dc": printConfig,
"load": loadConfigs,
}
func fix(args []string, flags *core.CLIFlags) error {
if len(args) != 1 {
return core.NewE100("fix", errors.New("one argument expected"))
}
alert := args[0]
if system.FileExists(args[0]) {
b, err := os.ReadFile(args[0])
if err != nil {
return err
}
alert = string(b)
}
cfg, err := core.ReadPipeline(flags, false)
if err != nil {
return err
}
resp, err := check.ParseAlert(alert, cfg)
if err != nil {
return err
}
return printJSON(resp)
}
func sync(_ []string, flags *core.CLIFlags) error {
cfg, err := core.ReadPipeline(flags, true)
if err != nil {
return err
} else if err = initPath(cfg); err != nil {
return err
}
// NOTE: sync should *only* run for a single config file.
rootINI, noRoot := cfg.Root()
if noRoot != nil {
return core.NewE100("sync", noRoot)
}
pkgs, err := core.GetPackages(rootINI)
if err != nil {
return err
}
stylesPath := cfg.StylesPath()
p, err := pterm.DefaultProgressbar.WithTotal(len(pkgs)).Start()
if err != nil {
return err
}
for idx, pkg := range pkgs {
name := system.FileNameWithoutExt(pkg)
p.UpdateTitle("Syncing " + name)
p.Increment()
if err = readPkg(pkg, stylesPath, idx); err != nil {
return err
}
}
msg := fmt.Sprintf("Synced %d package(s) to '%s'.", len(pkgs), stylesPath)
pterm.Success.Println(msg)
return nil
}
func printConfig(_ []string, flags *core.CLIFlags) error {
cfg, err := core.ReadPipeline(flags, false)
if err != nil {
return err
}
fmt.Println(cfg.String())
return nil
}
func printMetrics(args []string, _ *core.CLIFlags) error {
if len(args) != 1 {
return core.NewE100("ls-metrics", errors.New("one argument expected"))
} else if !system.FileExists(args[0]) {
return errors.New("file not found")
}
cfg, err := core.NewConfig(&core.CLIFlags{})
if err != nil {
return err
}
cfg.MinAlertLevel = 0
cfg.GBaseStyles = []string{"Vale"}
cfg.Flags.InExt = ".txt" // default value
linter, err := lint.NewLinter(cfg)
if err != nil {
return err
}
linted, err := linter.Lint([]string{args[0]}, "*")
if err != nil {
return err
}
computed, _ := linted[0].ComputeMetrics()
return printJSON(computed)
}
func runTag(args []string, _ *core.CLIFlags) error {
if len(args) != 3 {
return core.NewE100("tag", errors.New("three arguments expected"))
}
text, err := os.ReadFile(args[0])
if err != nil {
return err
}
out := core.TextToContext(
string(text), &nlp.Info{Lang: args[1], Endpoint: args[2]})
return printJSON(out)
}
func compileRule(args []string, _ *core.CLIFlags) error {
if len(args) != 1 {
return core.NewE100("compile", errors.New("one argument expected"))
}
cfg, err := core.NewConfig(&core.CLIFlags{})
if err != nil {
return err
}
path := args[0]
name := filepath.Base(path)
mgr, err := check.NewManager(cfg)
if err != nil {
return err
}
err = mgr.AddRuleFromFile(name, path)
if err != nil {
return err
}
rule := CompiledRule{Pattern: mgr.Rules()[name].Pattern()}
return printJSON(rule)
}
func runRule(args []string, _ *core.CLIFlags) error {
if len(args) != 2 {
return core.NewE100("run", errors.New("two arguments expected"))
}
cfg, err := core.NewConfig(&core.CLIFlags{})
if err != nil {
return err
}
cfg.MinAlertLevel = 0
cfg.GBaseStyles = []string{"Test"}
cfg.Flags.InExt = ".txt" // default value
linter, err := lint.NewLinter(cfg)
if err != nil {
return err
}
err = linter.Manager.AddRuleFromFile("Test.Rule", args[0])
if err != nil {
return err
}
linted, err := linter.Lint([]string{args[1]}, "*")
if err != nil {
return err
}
PrintJSONAlerts(linted)
return nil
}
func printVars(_ []string, flags *core.CLIFlags) error {
if flags.Output == "JSON" {
var out = map[string]string{}
for name := range core.ConfigVars {
value, _ := os.LookupEnv(name)
out[name] = value
}
return printJSON(out)
}
tableData := pterm.TableData{
{"Variable", "Description", "Value"},
}
for name, info := range core.ConfigVars {
found := pterm.FgRed.Sprint("✗")
if value, ok := os.LookupEnv(name); ok {
found = value
}
tableData = append(tableData, []string{toCodeStyle(name), info, found})
}
return pterm.DefaultTable.WithHasHeader().WithData(tableData).Render()
}
func printDirs(_ []string, flags *core.CLIFlags) error {
styles, _ := core.DefaultStylesPath()
stylesFound := pterm.FgGreen.Sprint("✓")
if !system.IsDir(styles) {
stylesFound = pterm.FgRed.Sprint("✗")
}
cfg, _ := core.DefaultConfig()
configFound := pterm.FgGreen.Sprint("✓")
if !system.FileExists(cfg) {
configFound = pterm.FgRed.Sprint("✗")
}
native, _ := getNativeConfig()
nativeDir := filepath.Dir(native)
nativeExe := filepath.Join(nativeDir, getExecName("vale-native"))
nativeFound := pterm.FgGreen.Sprint("✓")
if !system.FileExists(native) {
nativeFound = pterm.FgRed.Sprint("✗")
}
if flags.Output == "JSON" {
return printJSON(map[string]string{
"StylesPath": styles,
".vale.ini": cfg,
"vale-native": nativeExe,
})
}
tableData := pterm.TableData{
{"Asset", "Default Location", "Found"},
{toCodeStyle("StylesPath"), styles, stylesFound},
{toCodeStyle(".vale.ini"), cfg, configFound},
{toCodeStyle("vale-native"), nativeExe, nativeFound},
}
return pterm.DefaultTable.WithHasHeader().WithData(tableData).Render()
}
func transform(args []string, flags *core.CLIFlags) error {
if len(args) != 1 {
return core.NewE100("transform", errors.New("one argument expected"))
} else if !system.FileExists(args[0]) {
return fmt.Errorf("file not found: %s", args[0])
}
cfg, err := core.ReadPipeline(flags, false)
if err != nil {
return err
}
linter, err := lint.NewLinter(cfg)
if err != nil {
return err
}
f, err := core.NewFile(args[0], cfg)
if err != nil {
return err
}
out, err := linter.Transform(f)
if err != nil {
return err
}
fmt.Println(out)
return nil
}
func pathInfo(_ []string, flags *core.CLIFlags) error {
system := map[string][]string{}
cfg, err := core.ReadPipeline(flags, false)
if err != nil {
return err
}
system["configs"] = append(system["configs"], cfg.ConfigFiles...)
return printJSON(system)
}
func loadConfigs(args []string, _ *core.CLIFlags) error {
if len(args) != 2 {
return core.NewE100("run", errors.New("two arguments expected"))
}
cfg, err := core.NewConfig(&core.CLIFlags{})
if err != nil {
return err
}
cfg.RootINI = args[0]
cfg.AddConfigFile(args[0])
cfg.AddConfigFile(args[1])
err = core.MockLoad(args[0], args[1], cfg)
if err != nil {
return err
}
return printJSON(cfg)
}
================================================
FILE: cmd/vale/common.go
================================================
package main
import (
"sort"
"github.com/errata-ai/vale/v3/internal/core"
)
// PrintAlerts prints the given alerts in the user-specified format.
func PrintAlerts(linted []*core.File, config *core.Config) (bool, error) {
if config.Flags.Sorted {
sort.Sort(core.ByName(linted))
}
switch config.Flags.Output {
case "JSON":
return PrintJSONAlerts(linted), nil
case "line":
return PrintLineAlerts(linted, config.Flags.Relative), nil
case "CLI":
return PrintVerboseAlerts(linted, config.Flags.Wrap), nil
default:
return PrintCustomAlerts(linted, config)
}
}
================================================
FILE: cmd/vale/custom.go
================================================
package main
import (
"os"
"path/filepath"
"text/template"
"github.com/Masterminds/sprig/v3"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/system"
)
// ProcessedFile represents a file that Vale has linted.
type ProcessedFile struct {
Alerts []core.Alert
Path string
}
// Data holds the information exposed to UI templates.
type Data struct {
Files []ProcessedFile
LintedTotal int
}
// PrintCustomAlerts formats the given alerts using a user-defined template.
func PrintCustomAlerts(linted []*core.File, cfg *core.Config) (bool, error) {
var alertCount int
path := cfg.Flags.Output
if !system.FileExists(path) {
path = core.FindAsset(cfg, path)
}
b, err := os.ReadFile(path)
if err != nil {
return false, core.NewE100("template", err)
}
text := string(b)
t, err := template.New(filepath.Base(path)).Funcs(sprig.TxtFuncMap()).Funcs(funcs).Parse(text)
if err != nil {
return false, core.NewE100("template", err)
}
formatted := []ProcessedFile{}
for _, f := range linted {
if len(f.Alerts) == 0 {
continue
}
for _, a := range f.SortedAlerts() {
if a.Severity == "error" {
alertCount++
break
}
}
formatted = append(formatted, ProcessedFile{
Path: f.Path,
Alerts: f.Alerts,
})
}
return alertCount != 0, t.Execute(os.Stdout, Data{
Files: formatted,
LintedTotal: len(linted),
})
}
================================================
FILE: cmd/vale/error.go
================================================
package main
import (
"errors"
"fmt"
"io"
"log"
"os"
"regexp"
"strconv"
"strings"
"github.com/errata-ai/vale/v3/internal/core"
)
type valeError struct {
line int
path string
text string
code string
span int
}
var logger = log.New(os.Stderr, "", 0)
var header = regexp.MustCompile(`(\w+) .+ \[(.+):(\d+):(\d+)\]`)
func parseError(err error) (valeError, error) {
var parsed valeError
plain := core.StripANSI(err.Error())
lines := strings.Split(plain, "\n\n")
if len(lines) < 3 {
return parsed, errors.New("missing body")
} else if !header.MatchString(lines[0]) {
return parsed, errors.New("missing header")
}
groups := header.FindStringSubmatch(lines[0])
parsed.code = groups[1]
parsed.path = groups[2]
i, err := strconv.Atoi(groups[3])
if err != nil {
return parsed, errors.New("missing line")
}
parsed.line = i
i, err = strconv.Atoi(groups[4])
if err != nil {
return parsed, errors.New("missing span")
}
parsed.span = i
parsed.text = lines[len(lines)-2]
return parsed, nil
}
// ShowError displays the given error in the user-specified format.
func ShowError(err error, style string, out io.Writer) {
parsed, failed := parseError(err)
logger.SetOutput(out)
switch style {
case "JSON":
var data interface{}
if failed != nil {
data = struct {
Line int
Path string
Text string
Code string
Span int
}{
Line: 0,
Path: "",
Text: core.StripANSI(err.Error()),
Code: "E100",
Span: 0,
}
} else {
data = struct {
Line int
Path string
Text string
Code string
Span int
}{
Line: parsed.line,
Path: parsed.path,
Text: parsed.text,
Code: parsed.code,
Span: parsed.span,
}
}
logger.Println(getJSON(data))
case "line":
var data string
if failed != nil {
data = err.Error()
} else {
data = fmt.Sprintf("%s:%d:%s:%s",
parsed.path, parsed.line, parsed.code, parsed.text)
}
logger.Println(data)
default:
logger.Println(err)
}
}
================================================
FILE: cmd/vale/flag.go
================================================
package main
import (
"fmt"
"github.com/spf13/pflag"
"github.com/errata-ai/vale/v3/internal/core"
)
// Flags are the user-defined CLI flags.
var Flags core.CLIFlags
var shortcodes = map[string]string{
"version": "v",
"help": "h",
}
func init() {
pflag.StringVar(&Flags.Sources, "sources", "", "A config files to load")
pflag.StringVar(&Flags.Filter, "filter", "", "An expression to filter rules by.")
pflag.StringVar(&Flags.Glob, "glob", "*",
fmt.Sprintf(`A glob pattern (%s)`, toCodeStyle(`--glob='*.{md,txt}.'`)))
pflag.StringVar(&Flags.Path, "config", "",
fmt.Sprintf(`A file path (%s).`, toCodeStyle(`--config='some/file/path/.vale.ini'`)))
pflag.StringVar(&Flags.Output, "output", "CLI", `An output style ("line", "JSON", or a template file).`)
pflag.StringVar(&Flags.InExt, "ext", ".txt",
fmt.Sprintf(`An extension to associate with stdin (%s).`, toCodeStyle(`--ext=.md`)))
pflag.StringVar(&Flags.InPath, "path", "",
fmt.Sprintf(`A file path to associate with stdin (%s).`, toCodeStyle(`--path=docs/example.md`)))
pflag.StringVar(&Flags.AlertLevel, "minAlertLevel", "",
fmt.Sprintf(`The minimum level to display (%s).`, toCodeStyle(`--minAlertLevel=error`)))
pflag.BoolVar(&Flags.Wrap, "no-wrap", false, "Don't wrap CLI output.")
pflag.BoolVar(&Flags.NoExit, "no-exit", false, "Don't return a nonzero exit code on errors.")
pflag.BoolVar(&Flags.Simple, "ignore-syntax", false, "Lint all files line-by-line.")
pflag.BoolVarP(&Flags.Version, "version", "v", false, "Print the current version.")
pflag.BoolVarP(&Flags.Help, "help", "h", false, "Print this help message.")
pflag.BoolVar(&Flags.Local, "mode-compat", false, "prioritize local Vale configurations")
pflag.BoolVar(&Flags.Sorted, "sort", false, "sort files by their name in output")
pflag.BoolVar(&Flags.Normalize, "normalize", false, "replace each path separator with a slash ('/')")
pflag.BoolVar(&Flags.Relative, "relative", false, "return relative paths")
pflag.BoolVar(&Flags.IgnoreGlobal, "no-global", false, "Don't load the global configuration.")
}
================================================
FILE: cmd/vale/funcs.go
================================================
package main
import (
"encoding/json"
"fmt"
"io"
"os"
"text/template"
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
"github.com/pterm/pterm"
)
var funcs = template.FuncMap{}
func newBorderlessTable(w io.Writer, wrap int) *tablewriter.Table {
return tablewriter.NewTable(w,
tablewriter.WithRowAutoWrap(wrap),
tablewriter.WithRendition(tw.Rendition{
Borders: tw.BorderNone,
Symbols: tw.NewSymbols(tw.StyleNone),
Settings: tw.Settings{
Lines: tw.LinesNone,
Separators: tw.SeparatorsNone,
},
}),
)
}
func init() {
funcs["red"] = func(s string) string {
return pterm.Red(s)
}
funcs["blue"] = func(s string) string {
return pterm.Blue(s)
}
funcs["yellow"] = func(s string) string {
return pterm.Yellow(s)
}
funcs["underline"] = func(s string) string {
return pterm.Underscore.Sprint(s)
}
funcs["newTable"] = func(wrap bool) *tablewriter.Table {
wrapMode := tw.WrapNone
if wrap {
wrapMode = tw.WrapNormal
}
return newBorderlessTable(os.Stdout, wrapMode)
}
funcs["addRow"] = func(t *tablewriter.Table, r []string) *tablewriter.Table {
t.Append(r)
return t
}
funcs["renderTable"] = func(t *tablewriter.Table) *tablewriter.Table {
fmt.Println()
t.Render()
fmt.Println()
t.Reset()
return t
}
funcs["jsonEscape"] = func(i string) string {
b, err := json.Marshal(i)
if err != nil {
panic(err)
}
return string(b[1 : len(b)-1])
}
}
================================================
FILE: cmd/vale/info.go
================================================
package main
import (
"fmt"
"os"
"slices"
"github.com/olekukonko/tablewriter/tw"
"github.com/pterm/pterm"
"github.com/spf13/pflag"
"golang.org/x/exp/maps"
"github.com/errata-ai/vale/v3/internal/core"
)
var exampleConfig = `MinAlertLevel = suggestion
[*]
BasedOnStyles = Vale`
var intro = fmt.Sprintf(`vale - A command-line linter for prose.
%s: %s
%s
%s
Vale is a syntax-aware linter for prose built with speed and extensibility in
mind. It supports Markdown, AsciiDoc, reStructuredText, HTML, and more.
To get started, you'll need a configuration file (%s):
%s:
%s
See %s for more setup information.`,
pterm.Bold.Sprintf("Usage"),
toCodeStyle("vale [options] [input...]"),
toCodeStyle("vale myfile.md myfile1.md mydir1"),
toCodeStyle("vale --output=JSON [input...]"),
toCodeStyle(".vale.ini"),
pterm.Bold.Sprintf("Example"),
toCodeStyle(exampleConfig),
pterm.Underscore.Sprintf("https://vale.sh"))
var info = fmt.Sprintf(`%s
(Or use %s for a listing of all CLI options.)`,
intro,
toCodeStyle("vale --help"))
var hidden = []string{
"mode-compat",
"mode-rev-compat",
"normalize",
"relative",
"sort",
"sources",
"built",
// API stuff
"tag",
"compile",
"run",
"fix",
"verify",
"transform",
"ls-path",
"host-install",
"host-uninstall",
"load",
}
// PrintIntro shows basic usage / getting started info.
func PrintIntro() {
fmt.Println(info)
os.Exit(0)
}
func toFlag(name string) string {
if code, ok := shortcodes[name]; ok {
return fmt.Sprintf("%s, %s", toCodeStyle("-"+code), toCodeStyle("--"+name))
}
return toCodeStyle("--" + name)
}
func init() {
pflag.Usage = func() {
fmt.Println(intro)
table := newBorderlessTable(os.Stdout, tw.WrapNone)
fmt.Println(pterm.Bold.Sprintf("\nFlags:"))
pflag.VisitAll(func(f *pflag.Flag) {
if !core.StringInSlice(f.Name, hidden) {
table.Append([]string{toFlag(f.Name), f.Usage})
}
})
fmt.Println()
table.Render()
fmt.Println()
table.Reset()
commandKeys := maps.Keys(commandInfo)
slices.Sort(commandKeys)
fmt.Println(pterm.Bold.Sprintf("Commands:"))
for _, cmd := range commandKeys {
if !core.StringInSlice(cmd, hidden) {
table.Append([]string{toCodeStyle(cmd), commandInfo[cmd]})
}
}
fmt.Println()
table.Render()
fmt.Println()
os.Exit(0)
}
}
================================================
FILE: cmd/vale/json.go
================================================
package main
import (
"fmt"
"github.com/errata-ai/vale/v3/internal/core"
)
// PrintJSONAlerts prints Alerts in map[file.path][]Alert form.
func PrintJSONAlerts(linted []*core.File) bool {
alertCount := 0
formatted := map[string][]core.Alert{}
for _, f := range linted {
for _, a := range f.SortedAlerts() {
if a.Severity == "error" {
alertCount++
}
formatted[f.Path] = append(formatted[f.Path], a)
}
}
fmt.Println(getJSON(formatted))
return alertCount != 0
}
================================================
FILE: cmd/vale/library.go
================================================
package main
import "encoding/json"
var library = "https://raw.githubusercontent.com/errata-ai/styles/master/library.json"
func getLibrary(_ string) ([]Style, error) {
styles := []Style{}
resp, err := fetchJSON(library)
if err != nil {
return styles, err
} else if err = json.Unmarshal(resp, &styles); err != nil {
return styles, err
}
return styles, err
}
func inLibrary(name, path string) string {
lookup, err := getLibrary(path)
if err != nil {
return ""
}
for _, entry := range lookup {
if name == entry.Name {
return entry.URL
}
}
return ""
}
================================================
FILE: cmd/vale/line.go
================================================
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/errata-ai/vale/v3/internal/core"
)
// PrintLineAlerts prints Alerts in <path>:<line>:<col>:<check>:<message> format.
func PrintLineAlerts(linted []*core.File, relative bool) bool {
var base string
exeDir, _ := filepath.Abs(filepath.Dir(os.Args[0]))
alertCount := 0
for _, f := range linted {
// If vale is run from a parent directory of f, we use a shorter file
// path -- e.g., if run from the directory 'vale', we use
// 'testdata/test.cc: ...' instead of
// /Users/.../.../.../vale/testdata/test.cc: ...'.
if relative && strings.Contains(f.Path, exeDir) {
// FIXME: This doesn't work as intended, but our tests rely on its
// output -- so, we hide it behind a flag for now.
base = strings.Split(f.Path, exeDir)[1]
} else {
base = f.Path
}
for _, a := range f.SortedAlerts() {
if a.Severity == "error" {
alertCount++
}
fmt.Printf("%s:%d:%d:%s:%s\n",
base, a.Line, a.Span[0], a.Check, a.Message)
}
}
return alertCount != 0
}
================================================
FILE: cmd/vale/main.go
================================================
package main
import (
"fmt"
"io"
"os"
"github.com/spf13/pflag"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/lint"
"github.com/errata-ai/vale/v3/internal/system"
)
// version is set during the release build process.
var version = "master"
func stat() bool {
stat, err := os.Stdin.Stat()
if err != nil || (stat.Mode()&os.ModeCharDevice) != 0 {
return false
}
return true
}
func looksLikeStdin(s string) int {
isDir := system.IsDir(s)
if !(system.FileExists(s) || isDir) && s != "" {
return 1
} else if isDir {
return 0
}
return -1
}
func doLint(args []string, l *lint.Linter, glob string) ([]*core.File, error) {
var linted []*core.File
var err error
length := len(args)
if length == 1 && looksLikeStdin(args[0]) == 1 { //nolint:gocritic
// Case 1:
//
// $ vale "some text in a string"
linted, err = l.LintString(args[0])
} else if length > 0 {
// Case 2:
//
// $ vale file1 dir1 file2
input := []string{}
for _, file := range args {
status := looksLikeStdin(file)
if status == 1 {
return linted, core.NewE100(
"doLint",
fmt.Errorf("argument '%s' does not exist", file),
)
}
l.HasDir = status == 0
input = append(input, file)
}
linted, err = l.Lint(input, glob)
} else {
// Case 3:
//
// $ cat file.md | vale
stdin, readErr := io.ReadAll(os.Stdin)
if readErr != nil {
return linted, core.NewE100("doLint", readErr)
}
linted, err = l.LintString(string(stdin))
if err != nil {
return linted, core.NewE100("doLint", err)
}
}
return linted, err
}
func handleError(err error) {
ShowError(err, Flags.Output, os.Stderr)
os.Exit(2)
}
func main() {
pflag.Parse()
args := pflag.Args()
argc := len(args)
if Flags.Version { //nolint:gocritic
fmt.Println("vale version " + version)
os.Exit(0)
} else if Flags.Help {
pflag.Usage()
} else if argc == 0 && !stat() {
PrintIntro()
}
if argc > 0 {
cmd, exists := Actions[args[0]]
if exists {
if err := cmd(args[1:], &Flags); err != nil {
handleError(err)
}
os.Exit(0)
}
}
config, err := core.ReadPipeline(&Flags, false)
if err != nil {
handleError(err)
}
linter, err := lint.NewLinter(config)
if err != nil {
handleError(err)
}
linted, err := doLint(args, linter, Flags.Glob)
if err != nil {
handleError(err)
}
hasErrors, err := PrintAlerts(linted, config)
if err != nil {
handleError(err)
} else if hasErrors && !Flags.NoExit {
os.Exit(1)
}
os.Exit(0)
}
================================================
FILE: cmd/vale/main_unix.go
================================================
//go:build !windows
// +build !windows
package main
func unsetManifestRegistry(_ string) error {
return nil
}
func setManifestRegistry(_, _ string) error {
return nil
}
================================================
FILE: cmd/vale/main_windows.go
================================================
//go:build windows
// +build windows
package main
import (
"fmt"
"golang.org/x/sys/windows/registry"
)
func unsetManifestRegistry(browser string) error {
var key string
switch browser {
case "chrome", "opera", "chromium":
key = `SOFTWARE\Google\Chrome\NativeMessagingHosts\`
case "firefox":
key = `SOFTWARE\Mozilla\NativeMessagingHosts\`
case "edge":
key = `SOFTWARE\Microsoft\Edge\NativeMessagingHosts\`
default:
return fmt.Errorf("unsupported browser: %s", browser)
}
key += nativeHostName
return registry.DeleteKey(registry.CURRENT_USER, key)
}
func setManifestRegistry(browser, manifestPath string) error {
var key string
switch browser {
case "chrome", "opera", "chromium":
key = `SOFTWARE\Google\Chrome\NativeMessagingHosts\`
case "firefox":
key = `SOFTWARE\Mozilla\NativeMessagingHosts\`
case "edge":
key = `SOFTWARE\Microsoft\Edge\NativeMessagingHosts\`
default:
return fmt.Errorf("unsupported browser: %s", browser)
}
key += nativeHostName
k, _, err := registry.CreateKey(registry.CURRENT_USER, key, registry.WOW64_64KEY|registry.WRITE)
if err != nil {
return err
}
defer k.Close()
if err := k.SetStringValue("", manifestPath); err != nil {
return err
}
return nil
}
================================================
FILE: cmd/vale/native.go
================================================
package main
import (
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/adrg/xdg"
"github.com/pterm/pterm"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/system"
)
const nativeHostName = "sh.vale.native"
const releaseURL = "https://github.com/errata-ai/vale-native/releases/download/%s/vale-native_%s.%s"
var supportedBrowsers = []string{
"chrome",
"firefox",
"opera",
"chromium",
"edge",
}
var extensionByBrowser = map[string]string{
"chrome": "chrome-extension://kfmjcegeklidlnjoechfggipjjjahedj/",
}
var (
errMissingBrowser = errors.New("missing argument 'browser'")
errInvalidBrowser = fmt.Errorf("invalid browser; must one of %v", supportedBrowsers)
errMissingExt = errors.New("no extension for the given browser")
)
type manifest struct {
Name string `json:"name"`
Description string `json:"description"`
Path string `json:"path"`
Type string `json:"type"`
AllowedExtensions []string `json:"allowed_extensions,omitempty"`
AllowedOrigins []string `json:"allowed_origins,omitempty"`
}
// getNativeConfig returns the path to the native host's config file.
//
// NOTE: When the browser (e.g., Chrome) launches the native host, it does
// not have access to the user's shell environment. This is actually why we
// need a config file at all -- to tell the host where to find the Vale
// binary.
//
// The problem is, however, that we can't rely on `XDG_CONFIG_HOME` to be set,
// so we need to use the default value.
func getNativeConfig() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
name := system.Name()
switch name {
case "windows":
cfg, notFound := xdg.ConfigFile("vale/native/config.json")
if notFound != nil {
return "", notFound
}
return cfg, nil
case "linux":
path := filepath.Join(home, ".config/vale/native/config.json")
if err = system.Mkdir(filepath.Dir(path)); err != nil {
return "", err
}
return path, nil
case "darwin":
path := filepath.Join(home, "Library/Application Support/vale/native/config.json")
if err = system.Mkdir(filepath.Dir(path)); err != nil {
return "", err
}
return path, nil
default:
return "", fmt.Errorf("unsupported OS: %s", name)
}
}
func getExecName(name string) string {
if system.IsWindows() {
return name + ".exe"
}
return name
}
func getManifestDirs() (map[string]string, error) {
home, err := os.UserHomeDir()
if err != nil {
return nil, err
}
manifests := map[string]string{}
switch system.Name() {
case "linux":
manifests = map[string]string{
"chrome": filepath.Join(home, ".config/google-chrome/NativeMessagingHosts"),
"firefox": filepath.Join(home, ".mozilla/native-messaging-hosts"),
"opera": filepath.Join(home, ".config/google-chrome/NativeMessagingHosts"),
"chromium": filepath.Join(home, ".config/chromium/NativeMessagingHosts"),
}
case "darwin":
manifests = map[string]string{
"chrome": filepath.Join(home, "Library/Application Support/Google/Chrome/NativeMessagingHosts"),
"firefox": filepath.Join(home, "Library/Application Support/Mozilla/NativeMessagingHosts"),
"opera": filepath.Join(home, "Library/Application Support/Google/Chrome/NativeMessagingHosts"),
"chromium": filepath.Join(home, "Library/Application Support/Chromium/NativeMessagingHosts"),
"edge": filepath.Join(home, "Library/Application Support/Microsoft Edge/NativeMessagingHosts"),
}
}
return manifests, nil
}
func getLocation(browser string) (map[string]string, error) {
cfg, err := getNativeConfig()
if err != nil {
return nil, err
}
bin := filepath.Dir(cfg)
if system.IsWindows() {
return map[string]string{
"appDir": bin,
"manifestDir": "",
}, nil
}
manifestDirs, err := getManifestDirs()
if err != nil {
return nil, err
}
manifest := ""
if found, ok := manifestDirs[browser]; ok {
manifest = found
}
return map[string]string{
"appDir": bin,
"manifestDir": manifest,
}, nil
}
func writeNativeConfig() (string, error) {
cfgFile, err := getNativeConfig()
if err != nil {
return "", err
}
exe, err := exec.LookPath("vale")
if err != nil {
return "", err
}
cfg := map[string]string{
"path": exe,
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return "", err
}
return cfgFile, os.WriteFile(cfgFile, jsonCfg, 0600)
}
func installNativeHostUnix(manifestData []byte, manifestFile string) error {
err := os.WriteFile(manifestFile, manifestData, 0600)
if err != nil {
return err
}
return nil
}
func installNativeHostWindows(manifestData []byte, manifestFile, browser string) error {
cfg, err := getNativeConfig()
if err != nil {
return err
}
manifestDir := filepath.Join(filepath.Dir(cfg), "manifest", browser)
err = os.MkdirAll(manifestDir, os.ModePerm)
if err != nil {
return err
}
subdir := filepath.Join(manifestDir, manifestFile)
err = os.WriteFile(subdir, manifestData, 0600)
if err != nil {
return err
}
err = setManifestRegistry(browser, subdir)
if err != nil {
return err
}
return nil
}
func getLatestHostRelease() (string, error) {
resp, err := fetchJSON("https://api.github.com/repos/errata-ai/vale-native/releases/latest")
if err != nil {
return "", err
}
var release struct {
TagName string `json:"tag_name"`
}
err = json.Unmarshal(resp, &release)
if err != nil {
return "", err
}
return release.TagName, nil
}
func hostDownloadURL() (string, error) {
hostVersion, err := getLatestHostRelease()
if err != nil {
return "", err
}
name := system.PlatformAndArch()
return fmt.Sprintf(releaseURL, hostVersion, name, "zip"), nil
}
func installHost(manifestJSON []byte, manifestFile, browser string) error {
name := system.Name()
switch name {
case "linux", "darwin":
return installNativeHostUnix(manifestJSON, manifestFile)
case "windows":
return installNativeHostWindows(manifestJSON, manifestFile, browser)
default:
return fmt.Errorf("unsupported OS: %s", name)
}
}
func installNativeHost(args []string, _ *core.CLIFlags) error { //nolint:funlen
if len(args) != 1 {
return core.NewE100("host-install", errMissingBrowser)
}
browser := args[0]
if !core.StringInSlice(browser, supportedBrowsers) {
return core.NewE100("host-install", errInvalidBrowser)
}
steps := []string{"writing config", "fetching binary", "installing host"}
p, _ := pterm.DefaultProgressbar.WithTotal(len(steps)).WithTitle("Installing host").Start()
p.UpdateTitle(steps[0])
cfgFile, err := writeNativeConfig()
if err != nil {
return progressError("host-install", err, p)
}
pterm.Success.Println(fmt.Sprintf("wrote '%s'", cfgFile))
p.Increment()
locations, err := getLocation(browser)
if err != nil {
return progressError("host-install", err, p)
}
hostURL, err := hostDownloadURL()
if err != nil {
return progressError("host-install", err, p)
}
exeName := getExecName("vale-native")
oldInstall := []string{exeName, "LICENSE", "README.md"}
for _, file := range oldInstall {
fp := filepath.Join(locations["appDir"], file)
if system.FileExists(fp) {
err = os.Remove(fp)
if err != nil {
return progressError("host-install", err, p)
}
}
}
p.UpdateTitle(steps[1])
err = fetch(hostURL, locations["appDir"])
if err != nil {
return progressError("host-install", err, p)
}
pterm.Success.Println(fmt.Sprintf("fetched '%s'", hostURL))
p.Increment()
manifestData := manifest{
Name: nativeHostName,
Description: "A native messaging for the Vale CLI.",
Type: "stdio",
Path: filepath.Join(locations["appDir"], exeName),
}
manifestFile := filepath.Join(locations["manifestDir"], manifestData.Name+".json")
extension, found := extensionByBrowser[browser]
if !found {
return progressError("host-install", errMissingExt, p)
}
allowed := []string{extension}
devID := fmt.Sprintf("VALE_DEV_%s_ID", strings.ToUpper(browser))
if id, set := os.LookupEnv(devID); set {
allowed = append(allowed, id)
}
if browser == "firefox" {
manifestData.AllowedExtensions = allowed
} else {
manifestData.AllowedOrigins = allowed
}
manifestJSON, err := json.MarshalIndent(manifestData, "", " ")
if err != nil {
return progressError("host-install", err, p)
}
p.UpdateTitle(steps[2])
err = installHost(manifestJSON, manifestFile, browser)
if err != nil {
return progressError("host-install", err, p)
}
pterm.Success.Println(fmt.Sprintf("installed '%s'", manifestFile))
p.Increment()
return nil
}
func uninstallNativeHost(args []string, _ *core.CLIFlags) error {
if len(args) != 1 {
return core.NewE100("host-uninstall", errMissingBrowser)
}
browser := args[0]
if !core.StringInSlice(browser, supportedBrowsers) {
return core.NewE100("host-uninstall", errInvalidBrowser)
}
steps := []string{"removing files", "uninstalling host"}
p, _ := pterm.DefaultProgressbar.WithTotal(len(steps)).WithTitle("Uninstalling host").Start()
locations, err := getLocation(browser)
if err != nil {
return progressError("host-uninstall", err, p)
}
p.UpdateTitle(steps[0])
exeName := getExecName("vale-native")
for _, file := range []string{"config.json", exeName, "LICENSE", "README.md", "host.log"} {
fp := filepath.Join(locations["appDir"], file)
if system.FileExists(fp) {
err = os.Remove(filepath.Join(locations["appDir"], file))
if err != nil {
return progressError("host-uninstall", err, p)
}
}
}
pterm.Success.Println(steps[0])
p.Increment()
p.UpdateTitle(steps[1])
manifestFile := filepath.Join(locations["manifestDir"], nativeHostName+".json")
if system.FileExists(manifestFile) {
err = os.Remove(manifestFile)
if err != nil {
return progressError("host-uninstall", err, p)
}
}
err = unsetManifestRegistry(browser)
if err != nil {
return progressError("host-uninstall", err, p)
}
pterm.Success.Println(steps[1])
p.Increment()
return nil
}
================================================
FILE: cmd/vale/pkg_test.go
================================================
package main
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/system"
)
var TestData = "../../testdata/pkg"
func mockPath() (string, error) {
cfg, err := core.NewConfig(&core.CLIFlags{})
if err != nil {
return "", err
}
cfg.AddStylesPath(os.TempDir())
err = initPath(cfg)
if err != nil {
return "", err
}
return cfg.StylesPath(), nil
}
func TestNoPkgFound(t *testing.T) {
path, err := mockPath()
if err != nil {
t.Fatal(err)
}
err = readPkg("https://github.com/errata-ai/Microsoft/releases/download/v0.14.x/Microsoft.zip", path, 0)
if err == nil {
t.Fatal("expected error, got nil")
}
msg := "could not fetch 'https://github.com/errata-ai/Microsoft/releases/download/v0.14.x/Microsoft.zip' (status code '404')"
if !strings.Contains(err.Error(), msg) {
t.Fatalf("expected '%s', got '%s'", msg, err.Error())
}
}
func TestLibrary(t *testing.T) {
path, err := mockPath()
if err != nil {
t.Fatal(err)
}
err = readPkg("write-good", path, 0)
if err != nil {
t.Fatal(err)
}
if !system.IsDir(filepath.Join(path, "write-good")) {
t.Fatal("unable to find 'write-good' in StylesPath")
}
if !system.FileExists(filepath.Join(path, "write-good", "E-Prime.yml")) {
t.Fatal("unable to find 'E-Prime' in StylesPath")
}
}
func TestLocalZip(t *testing.T) {
path, err := mockPath()
if err != nil {
t.Fatal(err)
}
zip, err := filepath.Abs(filepath.Join(TestData, "write-good.zip"))
if err != nil {
t.Fatal(err)
}
err = readPkg(zip, path, 0)
if err != nil {
t.Fatal(err)
}
if !system.IsDir(filepath.Join(path, "write-good")) {
t.Fatal("unable to find 'write-good' in StylesPath")
}
if !system.FileExists(filepath.Join(path, "write-good", "E-Prime.yml")) {
t.Fatal("unable to find 'E-Prime' in StylesPath")
}
}
func TestLocalDir(t *testing.T) {
path, err := mockPath()
if err != nil {
t.Fatal(err)
}
zip, err := filepath.Abs(filepath.Join(TestData, "write-good"))
if err != nil {
t.Fatal(err)
}
err = readPkg(zip, path, 0)
if err != nil {
t.Fatal(err)
}
if !system.IsDir(filepath.Join(path, "write-good")) {
t.Fatal("unable to find 'write-good' in StylesPath")
}
if !system.FileExists(filepath.Join(path, "write-good", "E-Prime.yml")) {
t.Fatal("unable to find 'E-Prime' in StylesPath")
}
}
func TestLocalComplete(t *testing.T) { //nolint:dupl
path, err := mockPath()
if err != nil {
t.Fatal(err)
}
zip, err := filepath.Abs(filepath.Join(TestData, "ISC.zip"))
if err != nil {
t.Fatal(err)
}
err = readPkg(zip, path, 0)
if err != nil {
t.Fatal(err)
}
if !system.IsDir(filepath.Join(path, "ISC")) {
t.Fatal("unable to find 'ISC' in StylesPath")
}
vocab := filepath.Join(path, "Vocab", "ISC_General", "accept.txt")
if !system.FileExists(vocab) {
t.Fatal("unable to find 'ISC_General' in Vocab")
}
b, err := os.ReadFile(vocab)
if err != nil {
t.Fatal(err)
}
lines := strings.Split(string(b), "\n")
if !core.StringInSlice("bar", lines) {
t.Fatalf("unable to find 'bar' in %v", lines)
}
}
func TestLocalOnlyStyles(t *testing.T) { //nolint:dupl
path, err := mockPath()
if err != nil {
t.Fatal(err)
}
zip, err := filepath.Abs(filepath.Join(TestData, "OnlyStyles.zip"))
if err != nil {
t.Fatal(err)
}
err = readPkg(zip, path, 0)
if err != nil {
t.Fatal(err)
}
if !system.IsDir(filepath.Join(path, "ISC")) {
t.Fatal("unable to find 'ISC' in StylesPath")
}
vocab := filepath.Join(path, "Vocab", "ISC_General", "accept.txt")
if !system.FileExists(vocab) {
t.Fatal("unable to find 'ISC_General' in Vocab")
}
b, err := os.ReadFile(vocab)
if err != nil {
t.Fatal(err)
}
lines := strings.Split(string(b), "\n")
if !core.StringInSlice("bar", lines) {
t.Fatalf("unable to find 'bar' in %v", lines)
}
}
func TestV3Pkg(t *testing.T) {
path, err := mockPath()
if err != nil {
t.Fatal(err)
}
zip, err := filepath.Abs(filepath.Join(TestData, "v3.zip"))
if err != nil {
t.Fatal(err)
}
err = readPkg(zip, path, 0)
if err != nil {
t.Fatal(err)
}
if !system.IsDir(filepath.Join(path, "config")) {
t.Fatal("unable to find 'config' in StylesPath")
}
if !system.FileExists(filepath.Join(path, core.VocabDir, "Basic", "accept.txt")) {
t.Fatal("unable to find 'accept.txt'")
}
if !system.FileExists(filepath.Join(path, core.TmplDir, "t.tmpl")) {
t.Fatal("unable to find 't.tmpl'")
}
}
================================================
FILE: cmd/vale/sync.go
================================================
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
cp "github.com/otiai10/copy"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/system"
)
func initPath(cfg *core.Config) error {
// The first entry is always the default `StylesPath`.
stylesPath := cfg.StylesPath()
if !system.IsDir(stylesPath) {
if err := os.MkdirAll(cfg.StylesPath(), os.ModePerm); err != nil {
e := fmt.Errorf("unable to initialize StylesPath (value = '%s')", cfg.StylesPath())
return core.NewE100("initPath", e)
}
}
// Remove any existing .vale-config directory.
err := os.RemoveAll(filepath.Join(stylesPath, core.PipeDir))
if err != nil {
return core.NewE100("initPath", err)
}
return nil
}
func readPkg(pkg, path string, idx int) error {
if core.IsPhrase(pkg) && !system.IsDir(pkg) {
entry := inLibrary(pkg, path)
if entry != "" {
return download(pkg, entry, path, idx)
}
}
return loadPkg(system.FileNameWithoutExt(pkg), pkg, path, idx)
}
func loadPkg(name, urlOrPath, styles string, index int) error {
if fileInfo, err := os.Stat(urlOrPath); err == nil {
if fileInfo.IsDir() {
return loadLocalPkg(name, urlOrPath, styles, index)
}
return loadLocalZipPkg(name, urlOrPath, styles, index)
}
return download(name, urlOrPath, styles, index)
}
func loadLocalPkg(name, pkgPath, styles string, index int) error {
return installPkg(filepath.Dir(pkgPath), name, styles, index)
}
func loadLocalZipPkg(name, pkgPath, styles string, index int) error {
dir, err := os.MkdirTemp("", name)
if err != nil {
return err
}
if err = system.Unarchive(pkgPath, dir); err != nil {
return err
}
return installPkg(dir, name, styles, index)
}
func download(name, url, styles string, index int) error {
dir, err := os.MkdirTemp("", name)
if err != nil {
return err
}
if err = fetch(url, dir); err != nil {
if strings.Contains(err.Error(), "unsupported protocol scheme") {
err = fmt.Errorf("'%s' is not a valid URL or the directory doesn't exist", url)
}
return core.NewE100("download", err)
}
return installPkg(dir, name, styles, index)
}
func installPkg(dir, name, styles string, index int) error {
root := filepath.Join(dir, name)
path := filepath.Join(root, "styles")
pipe := filepath.Join(styles, core.PipeDir)
cfg := filepath.Join(root, ".vale.ini")
if !system.IsDir(path) && !system.FileExists(cfg) {
return moveAsset(name, dir, styles) // style-only
}
// StylesPath
if system.IsDir(path) {
if err := moveDir(path, styles); err != nil {
return err
}
// $StylesPath/config
//
// NOTE: We treat this directory differently than the rest of the
// entries on the path: we merge its contents with the existing
// $StylesPath/config directory.
for _, dir := range core.ConfigDirs {
loc1 := filepath.Join(path, dir)
if system.IsDir(loc1) {
loc2 := filepath.Join(styles, dir)
if err := moveDir(loc1, loc2); err != nil {
return err
}
}
}
}
// .vale.ini
if system.FileExists(cfg) {
pkgs, err := core.GetPackages(cfg)
if err != nil {
return err
}
for idx, pkg := range pkgs {
if err = readPkg(pkg, styles, idx); err != nil {
return err
}
}
entry := fmt.Sprintf("%d-%s.ini", index, name)
err = os.Rename(cfg, filepath.Join(root, entry))
if err != nil {
return err
} else if err = moveAsset(entry, root, pipe); err != nil {
return err
}
}
return nil
}
func moveDir(oldPath, newPath string) error {
files, err := os.ReadDir(oldPath)
if err != nil {
return err
}
for _, file := range files {
if !file.IsDir() || file.Name() != "config" {
if err = moveAsset(file.Name(), oldPath, newPath); err != nil {
return err
}
}
}
return nil
}
func moveAsset(name, oldPath, newPath string) error {
src := filepath.Join(oldPath, name)
dst := filepath.Join(newPath, name)
if system.FileExists(dst) || system.IsDir(dst) {
if err := os.RemoveAll(dst); err != nil {
return err
}
}
err := os.MkdirAll(newPath, os.ModePerm)
if err != nil {
return err
}
return cp.Copy(src, dst)
}
================================================
FILE: cmd/vale/util.go
================================================
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/pterm/pterm"
"github.com/errata-ai/vale/v3/internal/core"
)
// Response is returned after an action.
type Response struct {
Msg string
Error string
Success bool
}
func progressError(context string, err error, p *pterm.ProgressbarPrinter) error {
_, _ = p.Stop()
return core.NewE100(context, err)
}
func pluralize(s string, n int) string {
if n != 1 {
return s + "s"
}
return s
}
func getJSON(data interface{}) string {
b, err := json.MarshalIndent(data, "", " ")
if err != nil {
return err.Error()
}
return string(b)
}
func fetchJSON(url string) ([]byte, error) {
resp, err := http.Get(url) //nolint:gosec,noctx
if err != nil {
return []byte{}, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
func printJSON(t interface{}) error {
b, err := json.MarshalIndent(t, "", " ")
if err != nil {
fmt.Println("{}")
return err
}
fmt.Println(string(b))
return nil
}
// Send a JSON response after a local action.
func sendResponse(msg string, err error) error {
resp := Response{Msg: msg, Success: err == nil}
if !resp.Success {
resp.Error = err.Error()
}
return printJSON(resp)
}
func toCodeStyle(s string) string {
return pterm.Fuzzy.Sprint(s)
}
================================================
FILE: go.mod
================================================
module github.com/errata-ai/vale/v3
go 1.25.7
require (
github.com/Masterminds/sprig/v3 v3.3.0
github.com/adrg/frontmatter v0.2.0
github.com/adrg/strutil v0.3.1
github.com/adrg/xdg v0.5.3
github.com/bmatcuk/doublestar/v4 v4.7.1
github.com/d5/tengo/v2 v2.17.0
github.com/errata-ai/ini v1.63.0
github.com/errata-ai/regexp2 v1.7.0
github.com/expr-lang/expr v1.17.7
github.com/gobwas/glob v0.2.3
github.com/jdkato/go-tree-sitter-julia v0.1.0
github.com/jdkato/twine v0.10.2
github.com/mitchellh/mapstructure v1.5.0
github.com/niklasfasching/go-org v1.7.0
github.com/olekukonko/tablewriter v1.1.4
github.com/otiai10/copy v1.14.0
github.com/pelletier/go-toml/v2 v2.2.5-0.20250826075308-a0e846496753
github.com/pterm/pterm v0.12.40
github.com/remeh/sizedwaitgroup v1.0.0
github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82
github.com/spf13/pflag v1.0.5
github.com/tomwright/dasel/v2 v2.8.1
github.com/tomwright/dasel/v3 v3.3.2
github.com/yuin/goldmark v1.7.8
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8
golang.org/x/net v0.47.0
golang.org/x/sys v0.38.0
gopkg.in/yaml.v2 v2.4.0
)
require (
dario.cat/mergo v1.0.1 // indirect
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/MarvinJWendt/testza v0.4.2 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.1 // indirect
github.com/atomicgo/cursor v0.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clipperhouse/displaywidth v0.10.0 // indirect
github.com/clipperhouse/uax29/v2 v2.6.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
github.com/olekukonko/errors v1.2.0 // indirect
github.com/olekukonko/ll v0.1.6 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/term v0.37.0 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/neurosnap/sentences.v1 v1.0.7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: go.sum
================================================
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k=
github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI=
github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c=
github.com/MarvinJWendt/testza v0.4.2 h1:Vbw9GkSB5erJI2BPnBL9SVGV9myE+XmUSFahBGUhW2Q=
github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/adrg/frontmatter v0.2.0 h1:/DgnNe82o03riBd1S+ZDjd43wAmC6W35q67NHeLkPd4=
github.com/adrg/frontmatter v0.2.0/go.mod h1:93rQCj3z3ZlwyxxpQioRKC1wDLto4aXHrbqIsnH9wmE=
github.com/adrg/strutil v0.3.1 h1:OLvSS7CSJO8lBii4YmBt8jiK9QOtB9CzCzwl4Ic/Fz4=
github.com/adrg/strutil v0.3.1/go.mod h1:8h90y18QLrs11IBffcGX3NW/GFBXCMcNg4M7H6MspPA=
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
github.com/atomicgo/cursor v0.0.1 h1:xdogsqa6YYlLfM+GyClC/Lchf7aiMerFiZQn7soTOoU=
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q=
github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+UraiZGD4HJQ3Y8g=
github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs=
github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos=
github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/d5/tengo/v2 v2.17.0 h1:BWUN9NoJzw48jZKiYDXDIF3QrIVZRm1uV1gTzeZ2lqM=
github.com/d5/tengo/v2 v2.17.0/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/errata-ai/ini v1.63.0 h1:XRFKXTn7FvF8mnC9RPOlYaL4Ud7dP0i35LnLcbIhWYU=
github.com/errata-ai/ini v1.63.0/go.mod h1:PhjYff6ijif0unCnaJtXxnVsmlY95CSiNJDLXQYXdX8=
github.com/errata-ai/regexp2 v1.7.0 h1:N+weOlhwTd5iyDTcTCAMljXnfzkftcOZrdXno6G+QPM=
github.com/errata-ai/regexp2 v1.7.0/go.mod h1:59rO+jaxayJPF1WKI5m9R5F3Y3zR2Wn0DHnQbxtPm4A=
github.com/expr-lang/expr v1.17.7 h1:Q0xY/e/2aCIp8g9s/LGvMDCC5PxYlvHgDZRQ4y16JX8=
github.com/expr-lang/expr v1.17.7/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/jdkato/go-tree-sitter-julia v0.1.0 h1:z+6zTbd6PHMKAge7GJx9QIwPQX2NOKb4Pj5jteJvaYY=
github.com/jdkato/go-tree-sitter-julia v0.1.0/go.mod h1:lXNEZorcvU63DcANEklLMbDRjwam4VQ44MIV1Cck0w8=
github.com/jdkato/twine v0.10.2 h1:oUsxT6PT1Kz9JlJmAYOIjU/81+6uaj3/EVYIZJFIAdI=
github.com/jdkato/twine v0.10.2/go.mod h1:bYejIksa/MD4jxI5/o+DFxMb7Bw7JcGZDoA6ib4j+dg=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/neurosnap/sentences v1.1.2 h1:iphYOzx/XckXeBiLIUBkPu2EKMJ+6jDbz/sLJZ7ZoUw=
github.com/neurosnap/sentences v1.1.2/go.mod h1:/pwU4E9XNL21ygMIkOIllv/SMy2ujHwpf8GQPu1YPbQ=
github.com/niklasfasching/go-org v1.7.0 h1:vyMdcMWWTe/XmANk19F4k8XGBYg0GQ/gJGMimOjGMek=
github.com/niklasfasching/go-org v1.7.0/go.mod h1:WuVm4d45oePiE0eX25GqTDQIt/qPW1T9DGkRscqLW5o=
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo=
github.com/olekukonko/errors v1.2.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
github.com/olekukonko/ll v0.1.6 h1:lGVTHO+Qc4Qm+fce/2h2m5y9LvqaW+DCN7xW9hsU3uA=
github.com/olekukonko/ll v0.1.6/go.mod h1:NVUmjBb/aCtUpjKk75BhWrOlARz3dqsM+OtszpY4o88=
github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I=
github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY=
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
github.com/pelletier/go-toml/v2 v2.2.5-0.20250826075308-a0e846496753 h1:aTpyfgn3dz2npHl011BHQehdSavqjzhZdE6fJuJlO3A=
github.com/pelletier/go-toml/v2 v2.2.5-0.20250826075308-a0e846496753/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg=
github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE=
github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU=
github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=
github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
github.com/pterm/pterm v0.12.40 h1:LvQE43RYegVH+y5sCDcqjlbsRu0DlAecEn9FDfs9ePs=
github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s=
github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7Kyl5E=
github.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 h1:6C8qej6f1bStuePVkLSFxoU22XBS165D3klxlzRg8F4=
github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82/go.mod h1:xe4pgH49k4SsmkQq5OT8abwhWmnzkhpgnXeekbx2efw=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tomwright/dasel/v2 v2.8.1 h1:mo5SlL0V2d3a0uPsD9Rrndn0cHWpbNDheB4+Fm++z8k=
github.com/tomwright/dasel/v2 v2.8.1/go.mod h1:6bNDNAnmGEtGpuIvksuQwiNcAgQ87pmzndynsqTNglc=
github.com/tomwright/dasel/v3 v3.3.2 h1:KLFpJrGW5suih9FvGJDJ9HFv+YcyYjE/JfDGCfwxyq4=
github.com/tomwright/dasel/v3 v3.3.2/go.mod h1:0YJkmcgt+s40MGFIgLXfiAzm89BD079r8QGRaERwBls=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/neurosnap/sentences.v1 v1.0.7 h1:gpTUYnqthem4+o8kyTLiYIB05W+IvdQFYR29erfe8uU=
gopkg.in/neurosnap/sentences.v1 v1.0.7/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: internal/check/action.go
================================================
package check
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/d5/tengo/v2"
"github.com/d5/tengo/v2/stdlib"
"github.com/jdkato/twine/strcase"
"github.com/errata-ai/vale/v3/internal/core"
)
// Solution is a potential solution to an alert.
type Solution struct {
Suggestions []string `json:"suggestions"`
Error string `json:"error"`
}
type fixer func(core.Alert, *core.Config) ([]string, error)
var fixers = map[string]fixer{
"suggest": suggest,
"replace": replace,
"remove": remove,
"convert": convert,
"edit": edit,
}
// ParseAlert returns a slice of suggestions for the given Vale alert.
func ParseAlert(s string, cfg *core.Config) (Solution, error) {
body := core.Alert{}
resp := Solution{}
err := json.Unmarshal([]byte(s), &body)
if err != nil {
return Solution{}, err
}
suggestions, err := FixAlert(body, cfg)
if err != nil {
resp.Error = err.Error()
}
resp.Suggestions = suggestions
return resp, nil
}
func FixAlert(alert core.Alert, cfg *core.Config) ([]string, error) {
action := alert.Action.Name
if f, found := fixers[action]; found {
return f(alert, cfg)
}
return []string{}, fmt.Errorf("unknown action '%s'", action)
}
func suggest(alert core.Alert, cfg *core.Config) ([]string, error) {
if len(alert.Action.Params) == 0 {
return []string{}, errors.New("no parameters")
}
name := alert.Action.Params[0]
switch name {
case "spellings":
return spelling(alert, cfg)
default:
return script(name, alert, cfg)
}
}
func script(name string, alert core.Alert, cfg *core.Config) ([]string, error) {
var suggestions = []string{}
file := core.FindConfigAsset(cfg, name, core.ActionDir)
if file == "" {
return suggestions, fmt.Errorf("script '%s' not found", name)
}
source, err := os.ReadFile(file)
if err != nil {
return suggestions, err
}
script := tengo.NewScript(source)
script.SetImports(stdlib.GetModuleMap("text", "fmt", "math"))
err = script.Add("match", alert.Match)
if err != nil {
return suggestions, err
}
compiled, err := script.Compile()
if err != nil {
return suggestions, err
}
if err = compiled.Run(); err != nil {
return suggestions, err
}
computed := compiled.Get("suggestions").Array()
for _, s := range computed {
suggestions = append(suggestions, s.(string))
}
return suggestions, nil
}
func spelling(alert core.Alert, cfg *core.Config) ([]string, error) {
var suggestions = []string{}
name := strings.Split(alert.Check, ".")
path := filepath.Join(cfg.StylesPath(), name[0], name[1]+".yml")
mgr, err := NewManager(cfg)
if err != nil {
return suggestions, err
}
if _, ok := mgr.Rules()[alert.Check]; !ok {
err = mgr.AddRuleFromFile(alert.Check, path)
if err != nil {
return suggestions, err
}
}
rule, ok := mgr.Rules()[alert.Check].(Spelling)
if !ok {
return suggestions, fmt.Errorf("unknown check '%s'", alert.Check)
}
return rule.Suggest(alert.Match), nil
}
func replace(alert core.Alert, _ *core.Config) ([]string, error) {
return alert.Action.Params, nil
}
func remove(_ core.Alert, _ *core.Config) ([]string, error) {
return []string{""}, nil
}
func convert(alert core.Alert, _ *core.Config) ([]string, error) {
match := alert.Match
if alert.Action.Params[0] == "simple" {
match = strcase.Simple(match)
}
return []string{match}, nil
}
func edit(alert core.Alert, _ *core.Config) ([]string, error) {
match := alert.Match
if len(alert.Action.Params) == 0 {
return []string{}, errors.New("no parameters")
}
switch name := alert.Action.Params[0]; name {
case "regex":
if len(alert.Action.Params) != 3 {
return []string{}, errors.New("invalid number of parameters")
}
regex, err := regexp.Compile(alert.Action.Params[1])
if err != nil {
return []string{}, err
}
match = regex.ReplaceAllString(match, alert.Action.Params[2])
case "trim_right":
if len(alert.Action.Params) != 2 {
return []string{}, errors.New("invalid number of parameters")
}
match = strings.TrimRight(match, alert.Action.Params[1])
case "trim_left":
if len(alert.Action.Params) != 2 {
return []string{}, errors.New("invalid number of parameters")
}
match = strings.TrimLeft(match, alert.Action.Params[1])
case "trim":
if len(alert.Action.Params) != 2 {
return []string{}, errors.New("invalid number of parameters")
}
match = strings.Trim(match, alert.Action.Params[1])
case "truncate":
if len(alert.Action.Params) != 2 {
return []string{}, errors.New("invalid number of parameters")
}
match = strings.Split(match, alert.Action.Params[1])[0]
case "split":
if len(alert.Action.Params) != 3 {
return []string{}, errors.New("invalid number of parameters")
}
index, err := strconv.Atoi(alert.Action.Params[2])
if err != nil {
return []string{}, err
}
parts := strings.Split(match, alert.Action.Params[1])
if index >= len(parts) {
return []string{}, errors.New("index out of range")
}
match = parts[index]
}
return []string{strings.TrimSpace(match)}, nil
}
================================================
FILE: internal/check/capitalization.go
================================================
package check
import (
"github.com/errata-ai/regexp2"
"github.com/jdkato/twine/strcase"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/nlp"
)
// Capitalization checks the case of a string.
type Capitalization struct {
Definition `mapstructure:",squash"`
// `match` (`string`): $title, $sentence, $lower, $upper, or a pattern.
Match string
Check func(s string, re *regexp2.Regexp) (string, bool)
// `style` (`string`): AP or Chicago; only applies when match is set to
// $title.
Style string
// `exceptions` (`array`): An array of strings to be ignored.
Exceptions []string
// `indicators` (`array`): An array of suffixes that indicate the next
// token should be ignored.
Indicators []string
// `threshold` (`float`): The minimum proportion of words that must be
// (un)capitalized for a sentence to be considered correct.
Threshold float64
// `vocab` (`boolean`): If `true`, use the user's `Vocab` as a list of
// exceptions.
Vocab bool
// `prefix` (`string`): A prefix to be ignored when checking for
// capitalization.
Prefix string
exceptRe *regexp2.Regexp
}
// NewCapitalization creates a new `capitalization`-based rule.
func NewCapitalization(cfg *core.Config, generic baseCheck, path string) (Capitalization, error) {
rule := Capitalization{Vocab: true}
err := decodeRule(generic, &rule)
if err != nil {
return rule, readStructureError(err, path)
}
err = checkScopes(rule.Scope, path)
if err != nil {
return rule, err
}
re, err := updateExceptions(rule.Exceptions, cfg.AcceptedTokens, rule.Vocab)
if err != nil {
return rule, core.NewE201FromPosition(err.Error(), path, 1)
}
rule.exceptRe = re
// NOTE: This is OK since setting `Threshold` to 0 would mean that the rule
// would never trigger. In other words, we wouldn't want the default to be
// 0 because that would be equivalent to disabling the rule.
//
// Also, we chose a default of 0.8 because it matches the behavior of the
// original implementation (pre-threshold).
if rule.Threshold == 0 {
rule.Threshold = 0.8
}
if rule.Vocab {
rule.Exceptions = append(rule.Exceptions, cfg.AcceptedTokens...)
}
if rule.Match == "$title" {
var tc *strcase.TitleConverter
if rule.Style == "Chicago" {
tc = strcase.NewTitleConverter(
strcase.ChicagoStyle,
strcase.UsingVocab(rule.Exceptions),
strcase.UsingPrefix(rule.Prefix),
)
} else {
tc = strcase.NewTitleConverter(
strcase.APStyle,
strcase.UsingVocab(rule.Exceptions),
strcase.UsingPrefix(rule.Prefix),
)
}
rule.Check = func(s string, re *regexp2.Regexp) (string, bool) {
return title(s, re, tc, rule.Threshold)
}
} else if rule.Match == "$sentence" {
sc := strcase.NewSentenceConverter(
strcase.UsingVocab(rule.Exceptions),
strcase.UsingPrefix(rule.Prefix),
strcase.UsingIndicator(wasIndicator(rule.Indicators)),
)
rule.Check = func(s string, re *regexp2.Regexp) (string, bool) {
return sentence(s, re, sc, rule.Threshold)
}
} else if f, ok := varToFunc[rule.Match]; ok {
rule.Check = f
} else {
re2, errc := regexp2.CompileStd(rule.Match)
if errc != nil {
return rule, core.NewE201FromPosition(errc.Error(), path, 1)
}
rule.Check = func(s string, r *regexp2.Regexp) (string, bool) {
return re2.String(), re2.MatchStringStd(s) || isMatch(r, s)
}
}
return rule, nil
}
// Run checks the capitalization style of the provided text.
func (c Capitalization) Run(blk nlp.Block, _ *core.File, cfg *core.Config) ([]core.Alert, error) {
alerts := []core.Alert{}
expected, matched := c.Check(blk.Text, c.exceptRe)
if !matched {
action := c.Fields().Action
if action.Name == "replace" && len(action.Params) == 0 {
// We can only do this for non-regex case styles:
if c.Match == "$title" || c.Match == "$sentence" {
action.Params = []string{expected}
}
}
pos := []int{0, nlp.StrLen(blk.Text)}
a, err := makeAlert(c.Definition, pos, blk.Text, cfg)
if err != nil {
return alerts, err
}
a.Message, a.Description = formatMessages(c.Message,
c.Description, blk.Text, expected)
a.Action = action
alerts = append(alerts, a)
}
return alerts, nil
}
// Fields provides access to the internal rule definition.
func (c Capitalization) Fields() Definition {
return c.Definition
}
// Pattern is the internal regex pattern used by this rule.
func (c Capitalization) Pattern() string {
return ""
}
================================================
FILE: internal/check/conditional.go
================================================
package check
import (
"github.com/errata-ai/regexp2"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/nlp"
)
// Conditional ensures that the present of First ensures the present of Second.
type Conditional struct {
Definition `mapstructure:",squash"`
Exceptions []string
patterns []*regexp2.Regexp
First string
Second string
exceptRe *regexp2.Regexp
Ignorecase bool
Vocab bool
}
// NewConditional creates a new `conditional`-based rule.
func NewConditional(cfg *core.Config, generic baseCheck, path string) (Conditional, error) {
var expression []*regexp2.Regexp
rule := Conditional{Vocab: true}
err := decodeRule(generic, &rule)
if err != nil {
return rule, readStructureError(err, path)
}
err = checkScopes(rule.Scope, path)
if err != nil {
return rule, err
}
re, err := updateExceptions(rule.Exceptions, cfg.AcceptedTokens, rule.Vocab)
if err != nil {
return rule, core.NewE201FromPosition(err.Error(), path, 1)
}
rule.exceptRe = re
re, err = regexp2.CompileStd(rule.Second)
if err != nil {
return rule, core.NewE201FromPosition(err.Error(), path, 1)
}
expression = append(expression, re)
re, err = regexp2.CompileStd(rule.First)
if err != nil {
return rule, core.NewE201FromPosition(err.Error(), path, 1)
}
expression = append(expression, re)
// TODO: How do we support multiple patterns?
rule.patterns = expression
return rule, nil
}
// Run evaluates the given conditional statement.
func (c Conditional) Run(blk nlp.Block, f *core.File, cfg *core.Config) ([]core.Alert, error) {
alerts := []core.Alert{}
txt := blk.Text
// We first look for the consequent of the conditional statement.
// For example, if we're ensuring that abbreviations have been defined
// parenthetically, we'd have something like:
//
// "WHO" [antecedent], "World Health Organization (WHO)" [consequent]
//
// In other words: if "WHO" exists, it must also have a definition -- which
// we're currently looking for.
matches := c.patterns[0].FindAllStringSubmatch(txt, -1)
for _, mat := range matches {
if len(mat) > 1 {
// If we find one, we store it in a slice associated with this
// particular file.
for _, m := range mat[1:] {
if len(m) > 0 {
f.Sequences = append(f.Sequences, m)
}
}
}
}
// Now we look for the antecedent.
locs := c.patterns[1].FindAllStringIndex(txt, -1)
for _, loc := range locs {
s, err := re2Loc(txt, loc)
if err != nil {
return alerts, err
}
if !core.StringInSlice(s, f.Sequences) && !isMatch(c.exceptRe, s) {
// If we've found one (e.g., "WHO") and we haven't marked it as
// being defined previously, send an Alert.
a, erra := makeAlert(c.Definition, loc, txt, cfg)
if erra != nil {
return alerts, erra
}
alerts = append(alerts, a)
}
}
return alerts, nil
}
// Fields provides access to the internal rule definition.
func (c Conditional) Fields() Definition {
return c.Definition
}
// Pattern is the internal regex pattern used by this rule.
func (c Conditional) Pattern() string {
return ""
}
================================================
FILE: internal/check/consistency.go
================================================
package check
import (
"fmt"
"strings"
"github.com/errata-ai/regexp2"
"github.com/mitchellh/mapstructure"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/nlp"
)
type step struct {
pattern *regexp2.Regexp
subs []string
}
// Consistency ensures that the keys and values of Either don't both exist.
type Consistency struct {
Definition `mapstructure:",squash"`
steps []step
// `either` (`map`): A map of `option 1: option 2` pairs, of which only one
// may appear.
Either map[string]string
// `nonword` (`bool`): Removes the default word boundaries (`\b`).
Nonword bool
// `ignorecase` (`bool`): Makes all matches case-insensitive.
Ignorecase bool
}
// NewConsistency creates a new `consistency`-based rule.
func NewConsistency(cfg *core.Config, generic baseCheck, path string) (Consistency, error) {
var chkRE string
rule := Consistency{}
name, _ := generic["name"].(string)
err := mapstructure.WeakDecode(generic, &rule)
if err != nil {
return rule, readStructureError(err, path)
}
err = checkScopes(rule.Scope, path)
if err != nil {
return rule, err
}
regex := makeRegexp(
cfg.WordTemplate,
rule.Ignorecase,
func() bool { return !rule.Nonword },
func() string { return "" }, true)
chkKey := strings.Split(name, ".")[1]
count := 0
for v1, v2 := range rule.Either {
count += 2
subs := []string{
fmt.Sprintf("%s%d", chkKey, count),
fmt.Sprintf("%s%d", chkKey, count+1)}
chkRE = fmt.Sprintf("(?P<%s>%s)|(?P<%s>%s)", subs[0], v1, subs[1], v2)
chkRE = fmt.Sprintf(regex, chkRE)
re, errc := regexp2.CompileStd(chkRE)
if errc != nil {
return rule, core.NewE201FromPosition(errc.Error(), path, 1)
}
rule.Extends = name
rule.Name = fmt.Sprintf("%s.%s", name, v1)
rule.steps = append(rule.steps, step{pattern: re, subs: subs})
}
return rule, nil
}
// Run looks for inconsistent use of a user-defined regex.
func (o Consistency) Run(blk nlp.Block, f *core.File, cfg *core.Config) ([]core.Alert, error) {
alerts := []core.Alert{}
loc := []int{}
txt := blk.Text
for _, s := range o.steps {
matches := s.pattern.FindAllStringSubmatchIndex(txt, -1)
for _, submat := range matches {
for idx, mat := range submat {
if mat != -1 && idx > 0 && idx%2 == 0 {
loc = []int{mat, submat[idx+1]}
f.Sequences = append(
f.Sequences,
s.pattern.SubexpNames()[idx/2])
}
}
}
if matches != nil && core.AllStringsInSlice(s.subs, f.Sequences) {
o.Name = o.Extends
a, err := makeAlert(o.Definition, loc, txt, cfg)
if err != nil {
return alerts, err
}
alerts = append(alerts, a)
}
}
return alerts, nil
}
// Fields provides access to the internal rule definition.
func (o Consistency) Fields() Definition {
return o.Definition
}
// Pattern is the internal regex pattern used by this rule.
func (o Consistency) Pattern() string {
return ""
}
================================================
FILE: internal/check/definition.go
================================================
package check
import (
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"github.com/errata-ai/regexp2"
"github.com/mitchellh/mapstructure"
"gopkg.in/yaml.v2"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/nlp"
)
var inlineScopes = []string{"code", "link", "strong", "emphasis"}
// FilterEnv is the environment passed to the `--filter` flag.
type FilterEnv struct {
Rules []Definition
}
// Rule represents in individual writing construct to enforce.
type Rule interface {
Run(blk nlp.Block, file *core.File, cfg *core.Config) ([]core.Alert, error)
Fields() Definition
Pattern() string
}
// Definition holds the common attributes of rule definitions.
type Definition struct {
Action core.Action
Description string
Extends string
Level string
Limit int
Link string
Message string
Name string
Scope []string
Selector Selector
}
var defaultStyles = []string{"Vale"}
var extensionPoints = []string{
"capitalization",
"conditional",
"consistency",
"existence",
"occurrence",
"repetition",
"substitution",
"readability",
"spelling",
"sequence",
"metric",
"script",
}
var defaultRules = map[string]map[string]interface{}{
"Avoid": {
"extends": "existence",
"name": "Vale.Avoid",
"level": "error",
"message": "Avoid using '%s'.",
"scope": "text",
"ignorecase": false,
"tokens": []string{},
"path": "internal",
},
"Terms": {
"extends": "substitution",
"name": "Vale.Terms",
"level": "error",
"message": "Use '%s' instead of '%s'.",
"scope": "text",
"ignorecase": true,
"swap": map[string]string{},
"vocab": false,
"path": "internal",
},
"Repetition": {
"extends": "repetition",
"name": "Vale.Repetition",
"level": "error",
"message": "'%s' is repeated!",
"scope": "text",
"ignorecase": true,
"alpha": true,
"action": core.Action{
Name: "edit",
Params: []string{"truncate", " "},
},
"tokens": []string{`[^\s.!?]+`},
"path": "internal",
},
"Spelling": {
"extends": "spelling",
"name": "Vale.Spelling",
"message": "Did you really mean '%s'?",
"level": "error",
"scope": "text",
"action": core.Action{
Name: "suggest",
Params: []string{"spellings"},
},
"ignore": []interface{}{},
"path": "internal",
},
}
const (
ignoreCase = `(?i)`
wordTemplate = `(?m)\b(?:%s)\b`
nonwordTemplate = `(?m)(?:%s)`
tokenTemplate = `^(?:%s)$` //nolint:gosec
)
type baseCheck map[string]interface{}
func buildRule(cfg *core.Config, generic baseCheck) (Rule, error) {
path, ok := generic["path"].(string)
if !ok {
msg := fmt.Errorf("'%v' is not valid", generic)
return Existence{}, core.NewE100("buildRule: path", msg)
}
name, ok := generic["extends"].(string)
if !ok {
name = "unknown"
}
delete(generic, "path")
switch name {
case "existence":
return NewExistence(cfg, generic, path)
case "substitution":
return NewSubstitution(cfg, generic, path)
case "capitalization":
return NewCapitalization(cfg, generic, path)
case "occurrence":
return NewOccurrence(cfg, generic, path)
case "spelling":
return NewSpelling(cfg, generic, path)
case "repetition":
return NewRepetition(cfg, generic, path)
case "readability":
return NewReadability(cfg, generic, path)
case "conditional":
return NewConditional(cfg, generic, path)
case "consistency":
return NewConsistency(cfg, generic, path)
case "sequence":
return NewSequence(cfg, generic, path)
case "metric":
return NewMetric(cfg, generic, path)
case "script":
return NewScript(cfg, generic, path)
default:
return Existence{}, core.NewE201FromTarget(
fmt.Sprintf("'extends' key must be one of %v.", extensionPoints),
name,
path)
}
}
func formatMessages(msg string, desc string, subs ...string) (string, string) {
return core.FormatMessage(msg, subs...), core.FormatMessage(desc, subs...)
}
// NOTE: We need to do this because regexp2, the library we use for extended
// syntax, returns its locatons in *rune* offsets.
func re2Loc(s string, loc []int) (string, error) {
converted := []rune(s)
size := len(converted)
if loc[0] < 0 || loc[1] > size {
msg := fmt.Errorf("%d (%d:%d)", size, loc[0], loc[1])
return "", core.NewE100("re2loc: bounds", msg)
}
return string(converted[loc[0]:loc[1]]), nil
}
func makeAlert(chk Definition, loc []int, txt string, cfg *core.Config) (core.Alert, error) {
match, err := re2Loc(txt, loc)
if err != nil {
return core.Alert{}, err
}
a := core.Alert{
Check: chk.Name, Severity: chk.Level, Span: loc, Link: chk.Link,
Match: match, Action: chk.Action}
if chk.Action.Name != "" {
repl := match
fixed, fixError := FixAlert(a, cfg)
if fixError != nil {
return core.Alert{}, fixError
}
if len(fixed) == 1 {
repl = fixed[0]
} else if len(fixed) > 1 {
repl = core.ToSentence(fixed, "or")
}
a.Message, a.Description = formatMessages(chk.Message, chk.Description, match, repl)
} else {
a.Message, a.Description = formatMessages(chk.Message, chk.Description, match)
}
return a, nil
}
func parse(file []byte, path string) (map[string]interface{}, error) {
generic := map[string]interface{}{}
if err := yaml.Unmarshal(file, &generic); err != nil {
r := regexp.MustCompile(`yaml: line (\d+): (.+)`)
if r.MatchString(err.Error()) {
groups := r.FindStringSubmatch(err.Error())
i, erri := strconv.Atoi(groups[1])
if erri != nil {
return generic, core.NewE100("addCheck/Atoi", erri)
}
return generic, core.NewE201FromPosition(groups[2], path, i)
}
} else if err = validateDefinition(generic, path); err != nil {
return generic, err
}
return generic, nil
}
func validateDefinition(generic map[string]interface{}, path string) error {
if point, ok := generic["extends"]; !ok || point == nil {
return core.NewE201FromPosition(
"Missing the required 'extends' key.",
path,
1)
} else if !core.StringInSlice(point.(string), extensionPoints) {
key, _ := point.(string)
return core.NewE201FromTarget(
fmt.Sprintf("'extends' key must be one of %v.", extensionPoints),
key,
path)
}
if _, ok := generic["message"]; !ok {
return core.NewE201FromPosition(
"Missing the required 'message' key.",
path,
1)
}
if level, ok := generic["level"]; ok {
if level == nil || !core.StringInSlice(level.(string), core.AlertLevels) {
return core.NewE201FromTarget(
fmt.Sprintf("'level' must be one of %v", core.AlertLevels),
"level",
path)
}
}
if generic["code"] != nil && generic["code"].(bool) {
return core.NewE201FromTarget(
"`code` is deprecated; please use `scope: raw` instead.",
"code",
path)
}
return nil
}
func readStructureError(err error, path string) error {
r1 := regexp.MustCompile(`\* '(.+)' (.+)`)
r2 := regexp.MustCompile(`\* '(?:.*)' (.*): (\w+)`)
if r1.MatchString(err.Error()) {
groups := r1.FindStringSubmatch(err.Error())
return core.NewE201FromTarget(
groups[2],
strings.ToLower(groups[1]),
path)
} else if r2.MatchString(err.Error()) {
groups := r2.FindStringSubmatch(err.Error())
return core.NewE201FromTarget(
fmt.Sprintf("%s: '%s'", groups[1], groups[2]),
strings.ToLower(groups[2]),
path)
}
return core.NewE201FromPosition(err.Error(), path, 1)
}
func makeRegexp(
template string,
noCase bool,
word func() bool,
callback func() string,
shouldAppend bool,
) string {
regex := ""
if word() {
if template != "" {
regex += template
} else {
regex += wordTemplate
}
} else {
regex += nonwordTemplate
}
if shouldAppend {
regex += callback()
} else {
regex = callback() + regex
}
if noCase {
regex = ignoreCase + regex
}
return regex
}
func matchToken(expected, observed string, ignorecase bool) bool {
p := expected
if ignorecase {
p = ignoreCase + p
}
r, err := regexp2.CompileStd(fmt.Sprintf(tokenTemplate, p))
if core.IsPhrase(expected) || err != nil {
return expected == observed
}
return r.MatchStringStd(observed)
}
func updateExceptions(previous []string, current []string, vocab bool) (*regexp2.Regexp, error) {
if vocab {
previous = append(previous, current...)
}
// NOTE: This is required to ensure that we have greedy alternation.
sort.Slice(previous, func(p, q int) bool {
return len(previous[p]) > len(previous[q])
})
// NOTE: We need to add `(?-i)` to each term that doesn't already have it,
// otherwise any instance of the `(?i)` flag will be set for the entire
// expression.
for i, term := range previous {
if !strings.HasPrefix(term, "(?i)") {
previous[i] = fmt.Sprintf("(?-i)%s", term)
}
}
regex := makeRegexp(
"",
false,
func() bool { return true },
func() string { return "" },
true)
regex = fmt.Sprintf(regex, strings.Join(previous, "|"))
if len(previous) > 0 {
return regexp2.CompileStd(regex)
}
return ®exp2.Regexp{}, nil
}
func decodeRule(input interface{}, output interface{}) error {
config := mapstructure.DecoderConfig{
ErrorUnused: true,
Squash: true,
WeaklyTypedInput: true,
Result: output,
}
decoder, err := mapstructure.NewDecoder(&config)
if err != nil {
return err
}
return decoder.Decode(input)
}
func checkScopes(scopes []string, path string) error {
for _, scope := range scopes {
if strings.Contains(scope, "&") {
// FIXME: multi part ...
continue
}
// Negation ...
scope = strings.TrimPrefix(scope, "~")
// Specification ...
//
// TODO: check sub-scopes too?
scope = strings.Split(scope, ".")[0]
if core.StringInSlice(scope, inlineScopes) {
return core.NewE201FromTarget(
fmt.Sprintf("scope '%v' is no longer supported; use 'raw' instead.", scope),
"scope",
path)
}
// No spaces
if strings.Contains(scope, " ") {
return core.NewE201FromTarget(
fmt.Sprintf("scope '%v' contains spaces.", scope),
"scope",
path)
}
}
return nil
}
================================================
FILE: internal/check/doc.go
================================================
// Package check implements Vale's extension points.
package check
================================================
FILE: internal/check/existence.go
================================================
package check
import (
"fmt"
"strings"
"github.com/errata-ai/regexp2"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/nlp"
)
// Existence checks for the present of Tokens.
type Existence struct {
Definition `mapstructure:",squash"`
Raw []string
Tokens []string
// `exceptions` (`array`): An array of strings to be ignored.
Exceptions []string
exceptRe *regexp2.Regexp
pattern *regexp2.Regexp
Append bool
IgnoreCase bool
Nonword bool
Vocab bool
}
// NewExistence creates a new `Rule` that extends `Existence`.
func NewExistence(cfg *core.Config, generic baseCheck, path string) (Existence, error) {
rule := Existence{Vocab: true}
err := decodeRule(generic, &rule)
if err != nil {
return rule, readStructureError(err, path)
}
err = checkScopes(rule.Scope, path)
if err != nil {
return rule, err
}
re, err := updateExceptions(rule.Exceptions, cfg.AcceptedTokens, rule.Vocab)
if err != nil {
return rule, core.NewE201FromPosition(err.Error(), path, 1)
}
rule.exceptRe = re
regex := makeRegexp(
cfg.WordTemplate,
rule.IgnoreCase,
func() bool { return !rule.Nonword && len(rule.Tokens) > 0 },
func() string { return strings.Join(rule.Raw, "") },
rule.Append)
parsed := []string{}
for _, token := range rule.Tokens {
if strings.TrimSpace(token) != "" {
parsed = append(parsed, token)
}
}
regex = fmt.Sprintf(regex, strings.Join(parsed, "|"))
re, err = regexp2.CompileStd(regex)
if err != nil {
return rule, core.NewE201FromPosition(err.Error(), path, 1)
}
rule.pattern = re
return rule, nil
}
// Run executes the `existence`-based rule.
//
// This is simplest of the available extension points: it looks for any matches
// of its internal `pattern` (calculated from `NewExistence`) against the
// provided text.
func (e Existence) Run(blk nlp.Block, _ *core.File, cfg *core.Config) ([]core.Alert, error) {
alerts := []core.Alert{}
for _, loc := range e.pattern.FindAllStringIndex(blk.Text, -1) {
converted, err := re2Loc(blk.Text, loc)
if err != nil {
return alerts, err
}
observed := strings.TrimSpace(converted)
if !isMatch(e.exceptRe, observed) {
a, erra := makeAlert(e.Definition, loc, blk.Text, cfg)
if erra != nil {
return alerts, erra
}
alerts = append(alerts, a)
}
}
return alerts, nil
}
// Fields provides access to the internal rule definition.
func (e Existence) Fields() Definition {
return e.Definition
}
// Pattern is the internal regex pattern used by this rule.
func (e Existence) Pattern() string {
return e.pattern.String()
}
================================================
FILE: internal/check/existence_test.go
================================================
package check
import (
"testing"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/nlp"
)
func makeExistence(tokens []string) (*Existence, error) {
def := baseCheck{"tokens": tokens}
cfg, err := core.NewConfig(&core.CLIFlags{})
if err != nil {
return nil, err
}
rule, err := NewExistence(cfg, def, "")
if err != nil {
return nil, err
}
return &rule, nil
}
func TestExistence(t *testing.T) {
rule, err := makeExistence([]string{"test"})
if err != nil {
t.Fatal(err)
}
cfg, err := core.NewConfig(&core.CLIFlags{})
if err != nil {
t.Fatal(err)
}
file, err := core.NewFile("", cfg)
if err != nil {
t.Fatal(err)
}
alerts, _ := rule.Run(nlp.NewBlock("", "This is a test.", ""), file, cfg)
if len(alerts) != 1 {
t.Errorf("expected one alert, not %v", alerts)
}
}
func FuzzExistenceInit(f *testing.F) {
f.Add("hello")
f.Fuzz(func(_ *testing.T, s string) {
_, _ = makeExistence([]string{s})
})
}
func FuzzExistence(f *testing.F) {
rule, err := makeExistence([]string{"test"})
if err != nil {
f.Fatal(err)
}
cfg, err := core.NewConfig(&core.CLIFlags{})
if err != nil {
f.Fatal(err)
}
file, err := core.NewFile("", cfg)
if err != nil {
f.Fatal(err)
}
f.Add("hello")
f.Fuzz(func(_ *testing.T, s string) {
_, _ = rule.Run(nlp.NewBlock("", s, ""), file, cfg)
})
}
================================================
FILE: internal/check/filter.go
================================================
package check
import (
"fmt"
"os"
"strings"
"github.com/expr-lang/expr"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/system"
)
func filter(mgr *Manager) (map[string]Rule, error) {
var filter string
stringOrPath := mgr.Config.Flags.Filter
if stringOrPath == "" {
return mgr.rules, nil
}
if system.FileExists(stringOrPath) {
// Case 1: The user has provided a valid path to a filter file.
b, err := os.ReadFile(stringOrPath)
if err != nil {
return nil, fmt.Errorf("failed to read filter '%s': %w", stringOrPath, err)
}
filter = string(b)
} else if found := core.FindAsset(mgr.Config, stringOrPath); found != "" {
// Case 2: The user has referenced a filter stored on the `StylesPath`.
b, err := os.ReadFile(found)
if err != nil {
return nil, fmt.Errorf("failed to read filter '%s': %w", found, err)
}
filter = string(b)
} else {
// Case 3: Assume the user has provided a string.
filter = stringOrPath
}
// .Name, .Level -> override
// .Scope, .Message, .Description, .Extends, .Link
//
// The idea here should be simple: we read the ini file and apply overrides
// (where needed) from the user-given filter. The order is always:
//
// ini -> filter
//
// The key is that the *filter* always has the last say -- in terms of what
// rules run and at what level.
//
// NOTE: This means that filtered results can only ever be a *subset* of
// the would-be results since we're filtering on checks loaded based on the
// ini config.
env := FilterEnv{}
for _, rule := range mgr.rules {
env.Rules = append(env.Rules, rule.Fields())
}
code := fmt.Sprintf(`filter(Rules, {%s})`, filter)
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
return mgr.rules, err
}
output, err := expr.Run(program, env)
if err != nil {
return mgr.rules, err
}
filtered := map[string]Rule{}
for _, entry := range output.([]interface{}) {
rule, _ := entry.(Definition)
name := rule.Name
if strings.Count(name, ".") > 1 {
// TODO: See lint.go#249.
list := strings.Split(name, ".")
name = strings.Join([]string{list[0], list[1]}, ".")
}
// NOTE: We can't simply assume that what the filter returns should be
// run -- it depends on the *intent* of the filter.
//
// If the filter *only* sets `.Level`, then, for example, the output
// could contain rules that match the new level but are disabled in the
// `.vale.ini`.
//
// TODO: If checking for the existence of, e.g., `.Level` enough?
// Should we use `program.Constants`?
if strings.Contains(code, ".Level") {
lvl := core.LevelToInt[rule.Level]
if lvl < mgr.Config.MinAlertLevel {
mgr.Config.MinAlertLevel = lvl
}
}
/*
if strings.Contains(code, ".Name") {
mgr.Config.GChecks[name] = true
for _, v := range mgr.Config.SChecks {
v[name] = true
}
}*/
filtered[name] = mgr.rules[name]
}
return filtered, nil
}
================================================
FILE: internal/check/manager.go
================================================
package check
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"golang.org/x/exp/maps"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/nlp"
"github.com/errata-ai/vale/v3/internal/system"
)
// Manager controls the loading and validating of the check extension points.
type Manager struct {
Config *core.Config
scopes map[string]struct{}
rules map[string]Rule
styles []string
needsTagging bool
}
// NewManager creates a new Manager and loads the rule definitions (that is,
// extended checks) specified by configuration.
func NewManager(config *core.Config) (*Manager, error) {
var path string
mgr := Manager{
Config: config,
rules: make(map[string]Rule),
scopes: make(map[string]struct{}),
}
// TODO: Should we only load these if we're using them?
err := mgr.loadDefaultRules()
if err != nil {
return &mgr, err
}
// Load our styles ...
err = mgr.loadStyles(mgr.Config.Styles)
if err != nil {
return &mgr, err
}
for _, chk := range mgr.Config.Checks {
// Load any remaining individual rules.
if !strings.Contains(chk, ".") {
// A rule must be associated with a style (i.e., "Style[.]Rule").
continue
}
parts := strings.Split(chk, ".")
if !mgr.hasStyle(parts[0]) {
// If this rule isn't part of an already-loaded style, we load it
// individually.
fName := parts[1] + ".yml"
for _, p := range mgr.Config.SearchPaths() {
path = filepath.Join(p, parts[0], fName)
if !system.FileExists(path) {
continue
}
if err = mgr.addRuleFromSource(fName, path); err != nil {
return &mgr, err
}
}
}
}
mgr.rules, err = filter(&mgr)
return &mgr, err
}
// AddRule adds the given rule to the manager.
func (mgr *Manager) AddRule(name string, rule Rule) error {
if _, found := mgr.rules[name]; !found {
mgr.rules[name] = rule
return nil
}
return fmt.Errorf("the rule '%s' has already been added", name)
}
// AddRuleFromFile adds the given rule to the manager.
func (mgr *Manager) AddRuleFromFile(name, path string) error {
content, err := os.ReadFile(path)
if err != nil {
return core.NewE100("ReadFile", err)
}
return mgr.addCheck(content, name, path)
}
// Rules are all of the Manager's compiled `Rule`s.
func (mgr *Manager) Rules() map[string]Rule {
return mgr.rules
}
// HasScope returns `true` if the manager has a rule that applies to `scope`.
func (mgr *Manager) HasScope(scope string) bool {
_, found := mgr.scopes[scope]
return found
}
// NeedsTagging indicates if POS tagging is needed.
func (mgr *Manager) NeedsTagging() bool {
return mgr.needsTagging
}
// AssignNLP determines what NLP tasks a file needs.
func (mgr *Manager) AssignNLP(f *core.File) nlp.Info {
return nlp.Info{
Scope: f.RealExt,
Segmentation: mgr.HasScope("sentence"),
Splitting: mgr.HasScope("paragraph"),
Tagging: mgr.NeedsTagging(),
Endpoint: f.NLP.Endpoint,
Lang: f.NLP.Lang,
}
}
func (mgr *Manager) addStyle(path string) error {
return system.Walk(path, func(fp string, info fs.FileInfo, err error) error {
if err != nil {
return err
} else if info.IsDir() {
return nil
}
return mgr.addRuleFromSource(info.Name(), fp)
})
}
func (mgr *Manager) addRuleFromSource(name, path string) error {
if strings.HasSuffix(name, ".yml") {
f, err := os.ReadFile(path)
if err != nil {
return core.NewE201FromPosition(err.Error(), path, 1)
}
style := filepath.Base(filepath.Dir(path))
chkName := style + "." + strings.Split(name, ".")[0]
if _, ok := mgr.rules[chkName]; !ok {
if err = mgr.addCheck(f, chkName, path); err != nil {
return err
}
}
}
return nil
}
func (mgr *Manager) addCheck(file []byte, chkName, path string) error {
// Load the rule definition.
generic, err := parse(file, path)
if err != nil {
return err
}
// Set default values, if necessary.
generic["name"] = chkName
generic["path"] = path
if level, ok := mgr.Config.RuleToLevel[chkName]; ok {
generic["level"] = level
} else if _, ok = generic["level"]; !ok {
generic["level"] = "warning"
}
if scope, ok := generic["scope"]; scope == nil || !ok {
generic["scope"] = []string{"text"}
}
rule, err := buildRule(mgr.Config, generic)
if err != nil {
return err
}
for _, s := range rule.Fields().Scope {
base := strings.Split(s, ".")[0]
mgr.scopes[base] = struct{}{}
}
if rule.Fields().Extends == "sequence" {
mgr.needsTagging = true
}
if pos, ok := generic["pos"]; ok && pos != "" {
mgr.needsTagging = true
}
return mgr.AddRule(chkName, rule)
}
func (mgr *Manager) loadDefaultRules() error {
if !mgr.needsStyle("Vale") {
return nil
}
for _, style := range defaultStyles {
if core.StringInSlice(style, mgr.styles) {
return fmt.Errorf("'%v' collides with built-in style", style)
}
}
repetition := defaultRules["Repetition"]
if level, ok := mgr.Config.RuleToLevel["Vale.Repetition"]; ok {
repetition["level"] = level
}
repetition["path"] = "internal"
rule, err := buildRule(mgr.Config, repetition)
if err != nil {
return err
}
mgr.rules["Vale.Repetition"] = rule
spelling := defaultRules["Spelling"]
if level, ok := mgr.Config.RuleToLevel["Vale.Spelling"]; ok {
spelling["level"] = level
}
spelling["path"] = "internal"
rule, err = buildRule(mgr.Config, spelling)
if err != nil {
return err
}
mgr.rules["Vale.Spelling"] = rule
// TODO: where should this go?
mgr.loadVocabRules()
return nil
}
func (mgr *Manager) loadStyles(styles []string) error {
var found []string
var need []string
for _, baseDir := range mgr.Config.SearchPaths() {
for _, style := range styles {
p := filepath.Join(baseDir, style)
if mgr.hasStyle(style) {
// We've already loaded this style.
continue
} else if has := system.IsDir(p); !has {
need = append(need, style)
continue
} else if err := mgr.addStyle(p); err != nil {
return err
}
found = append(found, style)
}
}
for _, s := range need {
if !core.StringInSlice(s, found) {
return core.NewE100(
"loadStyles",
errors.New("style '"+s+"' does not exist on StylesPath"))
}
}
mgr.styles = append(mgr.styles, found...)
return nil
}
func (mgr *Manager) loadVocabRules() {
if len(mgr.Config.AcceptedTokens) > 0 {
vocab := defaultRules["Terms"]
for _, term := range mgr.Config.AcceptedTokens {
vocab["swap"].(map[string]string)[strings.ToLower(term)] = term
}
if level, ok := mgr.Config.RuleToLevel["Vale.Terms"]; ok {
vocab["level"] = level
}
rule, _ := buildRule(mgr.Config, vocab)
mgr.rules["Vale.Terms"] = rule
}
if len(mgr.Config.RejectedTokens) > 0 {
avoid := defaultRules["Avoid"]
for _, term := range mgr.Config.RejectedTokens {
avoid["tokens"] = append(avoid["tokens"].([]string), term)
}
if level, ok := mgr.Config.RuleToLevel["Vale.Avoid"]; ok {
avoid["level"] = level
}
rule, _ := buildRule(mgr.Config, avoid)
mgr.rules["Vale.Avoid"] = rule
}
}
func (mgr *Manager) hasStyle(name string) bool {
styles := append(mgr.styles, defaultStyles...) //nolint:gocritic
return core.StringInSlice(name, styles)
}
func (mgr *Manager) needsStyle(name string) bool {
cfg := mgr.Config
if core.StringInSlice(name, cfg.GBaseStyles) {
return true
}
for _, s := range maps.Keys(cfg.GChecks) {
if strings.HasPrefix(s, name) {
return true
}
}
for _, s := range cfg.SBaseStyles {
if core.StringInSlice(name, s) {
return true
}
}
for _, s := range cfg.SChecks {
for _, chk := range maps.Keys(s) {
if strings.HasPrefix(chk, name) {
return true
}
}
}
return false
}
================================================
FILE: internal/check/manager_test.go
================================================
package check
import (
"testing"
)
/*var checktests = []struct {
check string
msg string
}{
{"NoExtends.yml", "YAML.NoExtends: missing extension point!"},
{"NoMsg.yml", "YAML.NoMsg: missing message!"},
}
func TestAddCheck(t *testing.T) {
cfg, err := config.New()
if err != nil {
panic(err)
}
mgr := Manager{
AllChecks: make(map[string]Check),
Config: cfg,
Scopes: make(map[string]struct{})}
for _, tt := range checktests {
path, err := filepath.Abs(filepath.Join("../fixtures/YAML", tt.check))
if err != nil {
panic(err)
}
s := mgr.loadCheck(tt.check, path)
if s.Error() != tt.msg {
t.Errorf("%q != %q", s.Error(), tt.msg)
}
}
}*/
var msgtests = []struct {
in string
args []string
out string
}{
{"Avoid using '%s'", []string{"foo", "bar"}, "Avoid using 'foo'"},
{"Avoid using 'foo'", []string{"foo", "bar"}, "Avoid using 'foo'"},
{"Use '%s', not '%s'", []string{"foo", "bar"}, "Use 'foo', not 'bar'"},
}
func TestFormatMessage(t *testing.T) {
for _, tt := range msgtests {
s, _ := formatMessages(tt.in, tt.in, tt.args...)
if s != tt.out {
t.Errorf("(%q, %v) => %q != %q", tt.in, tt.args, s, tt.out)
}
}
}
================================================
FILE: internal/check/metric.go
================================================
package check
import (
"context"
"fmt"
"regexp"
"strings"
"github.com/d5/tengo/v2"
"github.com/d5/tengo/v2/stdlib"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/nlp"
)
var boilerplate = `math := import("math"); __res__ := (%s)`
var variables = []string{
"pre", "list", "blockquote", "heading_h1", "heading_h2", "heading_h3",
"heading_h4", "heading_h5", "heading_h6"}
var headings = regexp.MustCompile(`heading\.(h[1-6])`)
// Metric implements arbitrary, readability-like formulas.
type Metric struct {
Definition `mapstructure:",squash"`
// `metric` (`string`): the formula to be dynamically evaluated.
//
// Variables: # of words, # of sentences, etc.
Formula string
Condition string
path string
}
// NewMetric creates a new `metric`-based rule.
func NewMetric(_ *core.Config, generic baseCheck, path string) (Metric, error) {
rule := Metric{}
err := decodeRule(generic, &rule)
if err != nil {
return rule, readStructureError(err, path)
}
rule.path = path
rule.Definition.Scope = []string{"summary"}
rule.Formula = headings.ReplaceAllString(rule.Formula, "heading_$1")
return rule, nil
}
// Run calculates the readability level of the given text.
func (o Metric) Run(_ nlp.Block, f *core.File, _ *core.Config) ([]core.Alert, error) {
alerts := []core.Alert{}
ctx := context.Background()
parameters, err := f.ComputeMetrics()
if err != nil {
return alerts, err
} else if len(parameters) == 0 {
// empty file.
return alerts, nil
}
for _, k := range variables {
if _, ok := parameters[k]; !ok {
// If a variable wasn't found in the given file, we set its value
// to 0.0.
parameters[k] = 0.0
}
}
// The actual result of our formula.
//
// We need this to allow showing the result in a rule's message.
res, err := evalMath(ctx, o.Formula, parameters)
if err != nil {
return alerts, core.NewE201FromTarget(err.Error(), "formula", o.path)
}
// The binary result of our formula:
eqb := fmt.Sprintf("%f %s", res, o.Condition)
match, err := evalMath(ctx, eqb, parameters)
if err != nil {
return alerts, core.NewE201FromTarget(err.Error(), "condition", o.path)
}
if match.(bool) {
a := core.Alert{Check: o.Name, Severity: o.Level, Span: []int{1, 1},
Link: o.Link}
a.Message, a.Description = formatMessages(
o.Message, o.Description, fmt.Sprintf("%.2f", res))
alerts = append(alerts, a)
}
return alerts, nil
}
// Fields provides access to the internal rule definition.
func (o Metric) Fields() Definition {
return o.Definition
}
// Pattern is the internal regex pattern used by this rule.
func (o Metric) Pattern() string {
return o.Formula
}
func evalMath(
ctx context.Context,
expr string,
params map[string]interface{},
) (interface{}, error) {
expr = strings.TrimSpace(expr)
if expr == "" {
return nil, fmt.Errorf("empty expression")
}
script := tengo.NewScript([]byte(fmt.Sprintf(boilerplate, expr)))
script.SetImports(stdlib.GetModuleMap("math"))
for pk, pv := range params {
err := script.Add(pk, pv)
if err != nil {
return nil, fmt.Errorf("script add: %w", err)
}
}
compiled, err := script.RunContext(ctx)
if err != nil {
return nil, fmt.Errorf("script run: %w", err)
}
return compiled.Get("__res__").Value(), nil
}
================================================
FILE: internal/check/occurrence.go
================================================
package check
import (
"strconv"
"strings"
"github.com/errata-ai/regexp2"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/nlp"
)
// Occurrence counts the number of times Token appears.
type Occurrence struct {
Definition `mapstructure:",squash"`
Token string
Max int
Min int
pattern *regexp2.Regexp
Ignorecase bool
}
// NewOccurrence creates a new `occurrence`-based rule.
func NewOccurrence(_ *core.Config, generic baseCheck, path string) (Occurrence, error) {
rule := Occurrence{}
err := decodeRule(generic, &rule)
if err != nil {
return rule, readStructureError(err, path)
}
err = checkScopes(rule.Scope, path)
if err != nil {
return rule, err
}
regex := ""
if rule.Ignorecase {
regex += ignoreCase
}
regex += `(?:` + rule.Token + `)`
re, err := regexp2.CompileStd(regex)
if err != nil {
return rule, core.NewE201FromPosition(err.Error(), path, 1)
}
rule.pattern = re
return rule, nil
}
// Run checks the number of occurrences of a user-defined regex against a
// certain threshold.
func (o Occurrence) Run(blk nlp.Block, _ *core.File, cfg *core.Config) ([]core.Alert, error) {
var a core.Alert
var err error
var alerts []core.Alert
txt := blk.Text
locs := o.pattern.FindAllStringIndex(txt, -1)
occurrences := len(locs)
if (o.Max > 0 && occurrences > o.Max) || (o.Min > 0 && occurrences < o.Min) {
if occurrences == 0 {
// NOTE: We might not have a location to report -- i.e., by
// definition, having zero instances of a token may break a rule.
//
// In a case like this, the check essentially becomes
// document-scoped (like `readability`), so we mark the issue at
// the first line.
a = core.Alert{
Check: o.Name, Severity: o.Level, Span: []int{1, 1},
Link: o.Link}
} else {
span := []int{}
// We look for the first non-code match.
//
// Previously, we would just use the first match, but this could
// lead to false positives if the first match was in a code-like
// token.
//
// We also can't use the entire scope (`txt`) without risking
// having to fall back to string matching.
for _, loc := range locs {
m, rErr := re2Loc(txt, loc)
if rErr != nil || strings.TrimSpace(m) == "" {
continue
} else if !core.IsCode(m) {
span = loc
break
}
}
// If we can't find a non-code match, we return early.
//
// The alternative here is to use `scope: raw`.
if len(span) == 0 {
return alerts, nil
}
a, err = makeAlert(o.Definition, span, txt, cfg)
if err != nil {
return alerts, err
}
}
a.Message, a.Description = formatMessages(o.Message, o.Description,
strconv.Itoa(occurrences))
alerts = append(alerts, a)
}
return alerts, nil
}
// Fields provides access to the internal rule definition.
func (o Occurrence) Fields() Definition {
return o.Definition
}
// Pattern is the internal regex pattern used by this rule.
func (o Occurrence) Pattern() string {
return o.pattern.String()
}
================================================
FILE: internal/check/readability.go
================================================
package check
import (
"fmt"
"github.com/jdkato/twine/summarize"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/nlp"
)
// Readability checks the reading grade level of text.
type Readability struct {
Definition `mapstructure:",squash"`
// `metrics` (`array`): One or more of Gunning Fog, Coleman-Liau,
// Flesch-Kincaid, SMOG, and Automated Readability.
Metrics []string
// `grade` (`float`): The highest acceptable score.
Grade float64
}
// NewReadability creates a new `readability`-based rule.
func NewReadability(_ *core.Config, generic baseCheck, path string) (Readability, error) {
rule := Readability{}
err := decodeRule(generic, &rule)
if err != nil {
return rule, readStructureError(err, path)
}
if core.AllStringsInSlice(rule.Metrics, readabilityMetrics) {
// NOTE: This is the only extension point that doesn't support scoping.
// The reason for this is that we need to split on sentences to
// calculate readability, which means that specifying a scope smaller
// than a paragraph or including non-block level content (i.e.,
// headings, list items or table cells) doesn't make sense.
rule.Definition.Scope = []string{"summary"}
}
return rule, nil
}
// Run calculates the readability level of the given text.
func (o Readability) Run(blk nlp.Block, _ *core.File, _ *core.Config) ([]core.Alert, error) {
var grade float64
var alerts []core.Alert
doc := summarize.NewDocument(blk.Text)
if core.StringInSlice("SMOG", o.Metrics) {
grade += doc.SMOG()
}
if core.StringInSlice("Gunning Fog", o.Metrics) {
grade += doc.GunningFog()
}
if core.StringInSlice("Coleman-Liau", o.Metrics) {
grade += doc.ColemanLiau()
}
if core.StringInSlice("Flesch-Kincaid", o.Metrics) {
grade += doc.FleschKincaid()
}
if core.StringInSlice("Automated Readability", o.Metrics) {
grade += doc.AutomatedReadability()
}
grade /= float64(len(o.Metrics))
if grade > o.Grade {
a := core.Alert{Check: o.Name, Severity: o.Level,
Span: []int{1, 1}, Link: o.Link}
a.Message, a.Description = formatMessages(o.Message, o.Description,
fmt.Sprintf("%.2f", grade))
alerts = append(alerts, a)
}
return alerts, nil
}
// Fields provides access to the internal rule definition.
func (o Readability) Fields() Definition {
return o.Definition
}
// Pattern is the internal regex pattern used by this rule.
func (o Readability) Pattern() string {
return ""
}
================================================
FILE: internal/check/repetition.go
================================================
package check
import (
"strings"
"github.com/errata-ai/regexp2"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/nlp"
)
// Repetition looks for repeated uses of Tokens.
type Repetition struct {
Definition `mapstructure:",squash"`
Tokens []string
Max int
Ignorecase bool
Alpha bool
Vocab bool
Exceptions []string
exceptRe *regexp2.Regexp
pattern *regexp2.Regexp
}
// NewRepetition creates a new `repetition`-based rule.
func NewRepetition(cfg *core.Config, generic baseCheck, path string) (Repetition, error) {
rule := Repetition{Vocab: true}
err := decodeRule(generic, &rule)
if err != nil {
return rule, readStructureError(err, path)
}
err = checkScopes(rule.Scope, path)
if err != nil {
return rule, err
}
re, err := updateExceptions(rule.Exceptions, cfg.AcceptedTokens, rule.Vocab)
if err != nil {
return rule, core.NewE201FromPosition(err.Error(), path, 1)
}
rule.exceptRe = re
regex := ""
if rule.Ignorecase {
regex += ignoreCase
}
regex += `(` + strings.Join(rule.Tokens, "|") + `)`
made, err := regexp2.CompileStd(regex)
if err != nil {
return rule, core.NewE201FromPosition(err.Error(), path, 1)
}
rule.pattern = made
return rule, nil
}
// Run executes the `repetition`-based rule.
//
// The rule looks for repeated matches of its regex -- such as "this this".
func (o Repetition) Run(blk nlp.Block, _ *core.File, cfg *core.Config) ([]core.Alert, error) {
var curr, prev string
var hit bool
var ploc []int
var count int
var alerts []core.Alert
txt := blk.Text
for _, loc := range o.pattern.FindAllStringIndex(txt, -1) {
converted, err := re2Loc(txt, loc)
if err != nil {
return alerts, err
}
curr = strings.TrimSpace(converted)
if o.Ignorecase {
hit = strings.EqualFold(curr, prev) && curr != ""
} else {
hit = curr == prev && curr != ""
}
hit = hit && (!o.Alpha || core.IsLetter(curr))
if hit {
count++
}
if hit && count > o.Max {
pos := []int{ploc[0], loc[1]}
converted, err = re2Loc(txt, pos)
if err != nil {
return alerts, err
}
if sents := nlp.SentenceTokenizer.Segment(converted); len(sents) == 1 {
// If we have more than one sentence, we're likely looking at
// a false positive:
//
// I almost forgot about that. That is important.
//
// All plans except a Personal plan can use Redis. Redis ...
if !isMatch(o.exceptRe, converted) {
floc := []int{ploc[0], loc[1]}
a, erra := makeAlert(o.Definition, floc, txt, cfg)
if erra != nil {
return alerts, erra
}
a.Message, a.Description = formatMessages(o.Message,
o.Description, curr)
alerts = append(alerts, a)
count = 0
}
}
}
ploc = loc
prev = curr
}
return alerts, nil
}
// Fields provides access to the internal rule definition.
func (o Repetition) Fields() Definition {
return o.Definition
}
// Pattern is the internal regex pattern used by this rule.
func (o Repetition) Pattern() string {
return o.pattern.String()
}
================================================
FILE: internal/check/scope.go
================================================
package check
import (
"strings"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/nlp"
)
// A Selector represents a named section of text.
type Selector struct {
Value []string // e.g., text.comment.line.py
Negated bool
}
type Scope struct {
Selectors map[string][]Selector
}
func NewSelector(value []string) Selector {
negated := false
parts := []string{}
for i, m := range value {
m = strings.TrimSpace(m)
if i == 0 && strings.HasPrefix(m, "~") {
m = strings.TrimPrefix(m, "~")
negated = true
}
parts = append(parts, m)
}
return Selector{Value: parts, Negated: negated}
}
func NewScope(value []string) Scope {
scope := map[string][]Selector{}
for _, v := range value {
selectors := []Selector{}
for _, part := range strings.Split(v, "&") {
selectors = append(selectors, NewSelector(strings.Split(part, ".")))
}
scope[v] = selectors
}
return Scope{Selectors: scope}
}
// Macthes the scope `s` matches `s2`.
func (s Scope) Matches(blk nlp.Block) bool {
candidate := NewSelector(strings.Split(blk.Scope, "."))
parent := NewSelector(strings.Split(blk.Parent, "."))
for _, sel := range s.Selectors {
if s.partMatches(candidate, parent, sel) {
return true
}
}
return false
}
func (s Scope) partMatches(target, parent Selector, options []Selector) bool {
for _, part := range options {
tm := target.Contains(part)
pm := parent.Contains(part)
if part.Negated && !pm {
if target.Has("raw") || target.Has("summary") {
// This can't apply to sized scopes.
return false
}
} else if (!part.Negated && !tm) || (part.Negated && pm) {
return false
}
}
return true
}
// Sections splits a Selector into its parts -- e.g., text.comment.line.py ->
// []string{"text", "comment", "line", "py"}.
func (s *Selector) Sections() []string {
parts := []string{}
for _, m := range s.Value {
parts = append(parts, strings.Split(m, ".")...)
}
return parts
}
// Contains determines if all if sel's sections are in s.
func (s *Selector) Contains(sel Selector) bool {
return core.AllStringsInSlice(sel.Sections(), s.Sections())
}
// ContainsString determines if all if sel's sections are in s.
func (s *Selector) ContainsString(scope []string) bool {
for _, option := range scope {
sel := Selector{Value: []string{option}}
if !s.Contains(sel) {
return false
}
}
return true
}
// Equal determines if sel == s.
func (s *Selector) Equal(sel Selector) bool {
if len(s.Value) == len(sel.Value) {
for i, v := range s.Value {
if sel.Value[i] != v {
return false
}
}
return true
}
return false
}
// Has determines if s has a part equal to scope.
func (s *Selector) Has(scope string) bool {
return core.StringInSlice(scope, s.Sections())
}
================================================
FILE: internal/check/scope_test.go
================================================
package check
import (
"testing"
"github.com/errata-ai/vale/v3/internal/core"
)
func TestSelectors(t *testing.T) {
s1 := Selector{Value: []string{"text.comment.line.py"}}
s2 := Selector{Value: []string{"text.comment"}}
// s3 := Selector{Value: "text.comment.line.rb"}
sec := []string{"text", "comment", "line", "py"}
if !core.AllStringsInSlice(sec, s1.Sections()) {
t.Errorf("expected = %v, got = %v", sec, s1.Sections())
}
if s2.Has("py") {
t.Errorf("expected `false`, got `true`")
}
for _, part := range s1.Sections() {
if !s1.Has(part) {
t.Errorf("expected `true`, got `false`")
}
}
}
================================================
FILE: internal/check/script.go
================================================
package check
import (
"os"
"strings"
"github.com/d5/tengo/v2"
"github.com/d5/tengo/v2/stdlib"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/nlp"
)
// Script is Tango-based script.
//
// see https://github.com/d5/tengo.
type Script struct {
Definition `mapstructure:",squash"`
Script string
path string
}
// NewScript creates a new `script`-based rule.
func NewScript(cfg *core.Config, generic baseCheck, path string) (Script, error) {
rule := Script{}
err := decodeRule(generic, &rule)
if err != nil {
return rule, readStructureError(err, path)
}
if strings.HasSuffix(rule.Script, ".tengo") {
file := core.FindConfigAsset(cfg, rule.Script, core.ScriptDir)
b, scriptErr := os.ReadFile(file)
if scriptErr != nil {
return rule, core.NewE201FromTarget(
scriptErr.Error(), rule.Script, path)
}
rule.Script = string(b)
}
rule.path = path
return rule, nil
}
// Run executes the given script and returns its Alerts.
func (s Script) Run(blk nlp.Block, _ *core.File, _ *core.Config) ([]core.Alert, error) {
var alerts []core.Alert
script := tengo.NewScript([]byte(s.Script))
// NOTE: We don't want to enable the`os` module because of the security
// implications?
//
// See #495, for example.
script.SetImports(stdlib.GetModuleMap("text", "fmt", "math"))
err := script.Add("scope", blk.Text)
if err != nil {
return alerts, core.NewE201FromTarget(err.Error(), "script", s.path)
}
compiled, err := script.Compile()
if err != nil {
return alerts, core.NewE201FromTarget(err.Error(), "script", s.path)
}
if err = compiled.Run(); err != nil {
return alerts, core.NewE201FromTarget(err.Error(), "script", s.path)
}
for _, match := range parseMatches(compiled.Get("matches").Array()) {
matchText := blk.Text[match["begin"].(int):match["end"].(int)]
matchLoc := []int{match["begin"].(int), match["end"].(int)}
// NOTE: We can't call `makeAlert` here because `script`-based rules
// don't use our custom regexp2 library, which means the offsets
// (`re2loc`) will be off.
a := core.Alert{
Check: s.Name,
Severity: s.Level,
Span: matchLoc,
Link: s.Link,
Match: matchText,
Action: s.Action,
HasByteOffsets: true}
if matchMsg, ok := match["message"].(string); ok {
a.Message, a.Description = formatMessages(matchMsg, s.Description, matchText)
} else {
a.Message, a.Description = formatMessages(s.Message, s.Description, matchText)
}
alerts = append(alerts, a)
}
return alerts, nil
}
func parseMatches(a []interface{}) []map[string]interface{} {
matches := []map[string]interface{}{}
for _, i := range a {
m, _ := i.(map[string]interface{})
match := map[string]interface{}{
"begin": int(m["begin"].(int64)),
"end": int(m["end"].(int64)),
}
if msg, ok := m["message"].(string); ok {
match["message"] = msg
}
matches = append(matches, match)
}
return matches
}
// Fields provides access to the internal rule definition.
func (s Script) Fields() Definition {
return s.Definition
}
// Pattern is the internal regex pattern used by this rule.
func (s Script) Pattern() string {
return s.Script
}
================================================
FILE: internal/check/sequence.go
================================================
package check
import (
"fmt"
"strings"
"github.com/errata-ai/regexp2"
"github.com/jdkato/twine/nlp/tag"
"github.com/mitchellh/mapstructure"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/nlp"
)
// NLPToken represents a token of text with NLP-related attributes.
type NLPToken struct {
Pattern string
Tag string
Skip int
re *regexp2.Regexp
Negate bool
optional bool
start bool
end bool
}
// Sequence looks for a user-defined sequence of tokens.
type Sequence struct {
Definition `mapstructure:",squash"`
Tokens []NLPToken
Ignorecase bool
needsTagging bool
}
// NewSequence creates a new rule from the provided `baseCheck`.
func NewSequence(cfg *core.Config, generic baseCheck, path string) (Sequence, error) {
rule := Sequence{}
err := makeTokens(&rule, generic)
if err != nil {
return rule, readStructureError(err, path)
}
err = decodeRule(generic, &rule)
if err != nil {
return rule, readStructureError(err, path)
}
err = checkScopes(rule.Scope, path)
if err != nil {
return rule, err
}
for i, token := range rule.Tokens {
if !rule.needsTagging && token.Tag != "" {
rule.needsTagging = true
}
if token.Pattern != "" {
regex := makeRegexp(
cfg.WordTemplate,
rule.Ignorecase,
func() bool { return false },
func() string { return "" },
false)
regex = fmt.Sprintf(regex, token.Pattern)
re, errc := regexp2.CompileStd(regex)
if errc != nil {
return rule, core.NewE201FromPosition(errc.Error(), path, 1)
}
rule.Tokens[i].re = re
}
}
rule.Definition.Scope = []string{"sentence"}
return rule, nil
}
// Fields provides access to the rule definition.
func (s Sequence) Fields() Definition {
return s.Definition
}
// Pattern is the internal regex pattern used by this rule.
func (s Sequence) Pattern() string {
return ""
}
func makeTokens(s *Sequence, generic baseCheck) error {
for _, token := range generic["tokens"].([]interface{}) {
tok := NLPToken{}
if err := mapstructure.WeakDecode(token, &tok); err != nil {
return err
}
tok.optional = true
for i := tok.Skip; i > 0; i-- {
tok.start = false
if i == tok.Skip {
tok.start = true
}
s.Tokens = append(s.Tokens, tok)
}
if tok.Pattern != "" || tok.Tag != "" {
tok.optional = false
tok.end = true
s.Tokens = append(s.Tokens, tok)
}
}
delete(generic, "tokens")
return nil
}
func tokensMatch(token NLPToken, word tag.Token) bool {
failedTag, err := regexp2.MatchString(token.Tag, word.Tag)
if err != nil {
// FIXME: return the error instead ...
panic(err)
}
failedTag = failedTag == token.Negate
failedTok := token.re != nil && token.re.MatchStringStd(word.Text) == token.Negate
if (token.Pattern == "" && failedTag) ||
(token.Tag == "" && failedTok) ||
(token.Tag != "" && token.Pattern != "") && (failedTag || failedTok) {
return false
}
return true
}
func sequenceMatches(idx int, chk Sequence, target NLPToken, words []tag.Token, history []int) ([]string, int) {
var text []string
toks := chk.Tokens
sizeT := len(toks)
sizeW := len(words)
index := 0
for jdx, tok := range words {
if tokensMatch(target, tok) && !core.IntInSlice(jdx, history) {
index = jdx
// We've found our context.
//
// The *first* token with a `pattern` becomes the anchor of our
// search. From there, we must check both its left- and right-hand
// sides to ensure the sequence matches.
if idx > 0 {
// Check the left-end of the sequence:
//
// If the anchor is the first token, then there's no left-hand
// side to check -- hence, `idx > 0`.
for i := 1; idx-i >= 0; i++ {
if jdx-i < 0 {
return []string{}, index
}
tok := toks[idx-i]
word := words[jdx-i]
text = append([]string{word.Text}, text...)
// NOTE: We have to perform this conversion because the token slice is made
// with the right-hand orientation in mind. For example,
//
// optional (start), optional, required (end) -> required, optional, optional
//
// (from right to left).
if tok.Skip > 0 {
tok.optional = (tok.optional || tok.end) && !tok.start
}
mat := tokensMatch(tok, word)
if !mat && !tok.optional {
return []string{}, index
} else if mat && tok.optional {
break
}
}
}
if idx < sizeT {
// Check the right-end of the sequence
//
// If the anchor is the last token, then there's no right-hand
// side to check.
for i := 0; idx+i < sizeT; i++ {
if jdx+i >= sizeW {
return []string{}, index
}
tok := toks[idx+i]
word := words[jdx+i]
text = append(text, word.Text)
mat := tokensMatch(tok, word)
if !mat && !tok.optional {
return []string{}, index
} else if mat && tok.optional {
break
}
}
}
break
}
}
return text, index
}
func stepsToString(steps []string) string {
var sb strings.Builder
for i, step := range steps {
switch {
case step == "." || step == "," || step == ":" || step == ";" || step == "!" || step == "?" || step == "'" || step == `"` || step == ")":
// No space before punctuation or closing parenthesis
sb.WriteString(step)
case step == "(":
// No space before or after an opening parenthesis
if i > 0 && sb.Len() > 0 {
lastChar := sb.String()[sb.Len()-1]
if lastChar != ' ' {
sb.WriteString(" ")
}
}
sb.WriteString(step)
case strings.HasPrefix(step, "'"):
// If the step starts with an apostrophe, attach it without space
sb.WriteString(step)
default:
// Otherwise, add space before the word
if sb.Len() > 0 {
sb.WriteString(" ")
}
sb.WriteString(step)
}
}
return strings.TrimSpace(sb.String())
}
// Run looks for the user-defined sequence of tokens.
func (s Sequence) Run(blk nlp.Block, f *core.File, _ *core.Config) ([]core.Alert, error) {
var alerts []core.Alert
var offset []string
var history []int
// This is *always* sentence-scoped.
words := nlp.TextToTokens(blk.Text, &f.NLP)
txt := blk.Text
for idx, tok := range s.Tokens {
if !tok.Negate && tok.Pattern != "" {
// We're looking for our "anchor" ...
for _, loc := range tok.re.FindAllStringIndex(txt, -1) {
// These are all possible violations in `txt`:
steps, index := sequenceMatches(idx, s, tok, words, history)
history = append(history, index)
if len(steps) > 0 {
seq := stepsToString(steps)
ssp := strings.Index(txt, seq)
a := core.Alert{
Check: s.Name, Severity: s.Level, Link: s.Link,
Span: []int{ssp, ssp + len(seq)}, Hide: false,
Match: seq, Action: s.Action}
a.Message, a.Description = formatMessages(s.Message,
s.Description, steps...)
a.Offset = offset
alerts = append(alerts, a)
offset = []string{}
} else {
converted, err := re2Loc(txt, loc)
if err != nil {
return alerts, err
}
offset = append(offset, converted)
}
}
break
}
}
return alerts, nil
}
================================================
FILE: internal/check/spelling.go
================================================
package check
import (
"errors"
"os"
"path/filepath"
"reflect"
"regexp"
"strings"
"github.com/errata-ai/regexp2"
"github.com/mitchellh/mapstructure"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/nlp"
"github.com/errata-ai/vale/v3/internal/spell"
"github.com/errata-ai/vale/v3/internal/system"
)
var defaultFilters = []*regexp.Regexp{
regexp.MustCompile(`[A-Z]{1}[a-z]+[A-Z]+\w+`),
regexp.MustCompile(`[A-Z]+$`),
regexp.MustCompile(`[^a-zA-Z_']`),
}
// Spelling checks text against a Hunspell dictionary.
type Spelling struct {
Definition `mapstructure:",squash"`
Filters []*regexp.Regexp
Ignore []string
Exceptions []string
Dictionaries []string
Aff string
Dic string
Dicpath string
Threshold int
exceptRe *regexp2.Regexp
gs *spell.Checker
Custom bool
Append bool
}
func addFilters(s *Spelling, generic baseCheck, _ *core.Config) error {
if generic["filters"] != nil {
// We pre-compile user-provided filters for efficiency.
//
// NOTE: This makes a big difference: ~50s -> ~13s.
for _, filter := range generic["filters"].([]interface{}) {
pat, err := regexp.Compile(filter.(string))
if err != nil {
return err
}
s.Filters = append(s.Filters, pat)
}
delete(generic, "filters")
}
return nil
}
func addExceptions(s *Spelling, generic baseCheck, cfg *core.Config) error { //nolint:unparam
if generic["ignore"] != nil {
// Backwards compatibility: we need to be able to accept a single
// or an array.
if reflect.TypeOf(generic["ignore"]).String() == "string" {
s.Ignore = append(s.Ignore, generic["ignore"].(string))
} else {
for _, ignore := range generic["ignore"].([]interface{}) {
s.Ignore = append(s.Ignore, ignore.(string))
}
}
delete(generic, "ignore")
}
for _, term := range cfg.AcceptedTokens {
// NOTE: This is used to ensure that we are excluding whole words
// rather than substrings.
//
// The assumption is that, for spell checking, we don't want to
// flag words that are part of a larger word.
if !strings.HasPrefix(term, "\b") && !strings.HasSuffix(term, "\b") {
term = `\b` + term + `\b`
}
s.Exceptions = append(s.Exceptions, term)
s.exceptRe = regexp2.MustCompileStd(
ignoreCase + strings.Join(s.Exceptions, "|"))
}
return nil
}
// NewSpelling creates a new `spelling`-based rule.
func NewSpelling(cfg *core.Config, generic baseCheck, path string) (Spelling, error) {
var model *spell.Checker
rule := Spelling{}
name, _ := generic["name"].(string)
err := addFilters(&rule, generic, cfg)
if err != nil {
return rule, readStructureError(err, path)
}
err = addExceptions(&rule, generic, cfg)
if err != nil {
return rule, readStructureError(err, path)
}
err = mapstructure.WeakDecode(generic, &rule)
if err != nil {
return rule, readStructureError(err, path)
}
model, err = makeSpeller(&rule, cfg, path)
if err != nil {
return rule, core.NewE201FromPosition(err.Error(), path, 1)
}
if name == "Vale.Spelling" {
// NOTE: For `Vale.Spelling`, there's no way to define specific
// ignore files, so we just check the default `config/ignore`
// directory.
//
// We **can't** add vocabularies here because `AddWordListFile`
// doesn't support regex.
ignored, readErr := core.IgnoreFiles(cfg.StylesPath())
if readErr != nil {
return rule, readErr
}
for _, file := range ignored {
if err = model.AddWordListFile(file); err != nil {
return rule, err
}
}
} else {
for _, ignore := range rule.Ignore {
fullPath, _ := filepath.Abs(ignore)
// There are a few cases we need to consider:
paths := []string{
// 1. An absolute path (similar to $DICPATH)
fullPath,
// 2. Relative to StylesPath
filepath.Join(cfg.StylesPath(), ignore),
// 3. Relative to config/ignore
filepath.Join(cfg.StylesPath(), core.IgnoreDir, ignore),
}
for _, p := range paths {
if err = model.AddWordListFile(p); err != nil && system.FileExists(p) {
return rule, err
}
}
}
}
if !rule.Custom {
rule.Filters = append(rule.Filters, defaultFilters...)
}
rule.gs = model
return rule, nil
}
// Run performs spell-checking on the provided text.
func (s Spelling) Run(blk nlp.Block, _ *core.File, _ *core.Config) ([]core.Alert, error) {
var alerts []core.Alert
txt := blk.Text
// This ensures that we respect `.aff` entries like `ICONV ’ '`,
// allowing us to avoid false positives.
//
// See https://github.com/errata-ai/vale/v2/issues/148.
txt = s.gs.Convert(txt)
OUTER:
for _, word := range nlp.WordTokenizer.Tokenize(txt) {
for _, filter := range s.Filters {
if filter.MatchString(word) {
continue OUTER
}
}
if !s.gs.Spell(word) && !isMatch(s.exceptRe, word) {
offset := strings.Index(txt, word)
loc := []int{offset, offset + len(word)}
a := core.Alert{Check: s.Name, Severity: s.Level, Span: loc,
Link: s.Link, Match: word, Action: s.Action}
a.Message, a.Description = formatMessages(s.Message,
s.Description, word)
alerts = append(alerts, a)
}
}
return alerts, nil
}
// Fields provides access to the internal rule definition.
func (s Spelling) Fields() Definition {
return s.Definition
}
// Pattern is the internal regex pattern used by this rule.
func (s Spelling) Pattern() string {
return ""
}
// Pattern is the internal regex pattern used by this rule.
func (s Spelling) Suggest(word string) []string {
return s.gs.Suggest(word)
}
func makeSpeller(s *Spelling, cfg *core.Config, rulePath string) (*spell.Checker, error) {
var options []spell.CheckerOption
var found bool
affloc := core.FindAsset(cfg, s.Aff)
dicloc := core.FindAsset(cfg, s.Dic)
if system.FileExists(affloc) && system.FileExists(dicloc) {
return spell.NewChecker(spell.UsingDictionaryByPath(dicloc, affloc))
}
options = append(options, spell.WithDefault(s.Append))
if s.Dicpath != "" {
cwd, _ := os.Getwd()
// There are a few cases we need to consider:
paths := []string{
// 1. An absolute path (similar to $DICPATH)
s.Dicpath,
// 2. Relative to StylesPath
filepath.Join(cfg.StylesPath(), s.Dicpath),
// 4. Relative to cwd
filepath.Join(cwd, s.Dicpath),
}
for _, p := range paths {
if system.IsDir(p) {
options = append(options, spell.WithPath(p))
found = true
break
}
}
if !found {
return nil, errors.New("unable to resolve dicpath")
}
} else {
options = append(options, spell.WithPath(
filepath.Join(cfg.StylesPath(), core.DictDir)))
}
if len(s.Dictionaries) > 0 {
for _, name := range s.Dictionaries {
options = append(options, spell.UsingDictionary(name))
}
return spell.NewChecker(options...)
}
if rulePath == "internal" {
// NOTE: New in v3.0 -- if we aren't given a `dicpath` or specific
// dictionaries, we use the default one.
options = append(options, spell.WithDefaultPath(
filepath.Join(cfg.StylesPath(), core.DictDir)))
}
return spell.NewChecker(options...)
}
================================================
FILE: internal/check/substitution.go
================================================
package check
import (
"fmt"
"sort"
"strings"
"golang.org/x/exp/maps"
"github.com/errata-ai/regexp2"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/nlp"
)
// Substitution switches the values of Swap for its keys.
type Substitution struct {
Definition `mapstructure:",squash"`
Exceptions []string
repl []string
Swap map[string]string
exceptRe *regexp2.Regexp
pattern *regexp2.Regexp
Ignorecase bool
Nonword bool
Vocab bool
Capitalize bool
msgMap []string
// Deprecated
POS string
}
// NewSubstitution creates a new `substitution`-based rule.
func NewSubstitution(cfg *core.Config, generic baseCheck, path string) (Substitution, error) {
rule := Substitution{Vocab: true}
err := decodeRule(generic, &rule)
if err != nil {
return rule, readStructureError(err, path)
}
err = checkScopes(rule.Scope, path)
if err != nil {
return rule, err
}
tokens := ""
re, err := updateExceptions(rule.Exceptions, cfg.AcceptedTokens, rule.Vocab)
if err != nil {
return rule, core.NewE201FromPosition(err.Error(), path, 1)
}
rule.exceptRe = re
regex := makeRegexp(
cfg.WordTemplate,
rule.Ignorecase,
func() bool { return !rule.Nonword },
func() string { return "" }, true)
terms := maps.Keys(rule.Swap)
sort.Slice(terms, func(p, q int) bool {
return len(terms[p]) > len(terms[q])
})
replacements := []string{}
for _, regexstr := range terms {
rule.msgMap = append(rule.msgMap, regexstr)
replacement := rule.Swap[regexstr]
opens := strings.Count(regexstr, "(")
if opens != strings.Count(regexstr, "(?")+strings.Count(regexstr, `\(`) {
// We have a capture group, so we need to make it non-capturing.
regexstr, err = convertCaptureGroups(regexstr)
if err != nil {
return rule, core.NewE201FromTarget(err.Error(), regexstr, path)
}
}
tokens += `(` + regexstr + `)|`
replacements = append(replacements, replacement)
}
regex = fmt.Sprintf(regex, strings.TrimRight(tokens, "|"))
re, err = regexp2.CompileStd(regex)
if err != nil {
return rule, core.NewE201FromPosition(err.Error(), path, 1)
}
rule.pattern = re
rule.repl = replacements
return rule, nil
}
// Run executes the `substitution`-based rule.
//
// The rule looks for one pattern and then suggests a replacement.
func (s Substitution) Run(blk nlp.Block, _ *core.File, cfg *core.Config) ([]core.Alert, error) {
var alerts []core.Alert
txt := blk.Text
// Leave early if we can to avoid calling `FindAllStringSubmatchIndex`
// unnecessarily.
if !s.pattern.MatchStringStd(txt) {
return alerts, nil
}
for _, submat := range s.pattern.FindAllStringSubmatchIndex(txt, -1) {
for idx, mat := range submat {
if mat != -1 && idx > 0 && idx%2 == 0 {
loc := []int{mat, submat[idx+1]}
converted, err := re2Loc(txt, loc)
if err != nil {
return alerts, err
}
observed := converted
expected, msgErr := subMsg(s, (idx/2)-1, observed)
if msgErr != nil {
return alerts, msgErr
}
same := matchToken(expected, observed, false)
if !same && !isMatch(s.exceptRe, observed) {
action := s.Fields().Action
if action.Name == "replace" && len(action.Params) == 0 {
action.Params = getOptions(expected)
if s.Capitalize && observed == core.CapFirst(observed) {
cased := []string{}
for _, param := range action.Params {
cased = append(cased, core.CapFirst(param))
}
action.Params = cased
}
expected = core.ToSentence(action.Params, "or")
// NOTE: For backwards-compatibility, we need to ensure
// that we don't double quote.
s.Message = convertMessage(s.Message)
}
a, aerr := makeAlert(s.Definition, loc, txt, cfg)
if aerr != nil {
return alerts, aerr
}
a.Message, a.Description = formatMessages(s.Message,
s.Description, expected, observed)
a.Action = action
alerts = append(alerts, a)
}
}
}
}
return alerts, nil
}
// Fields provides access to the internal rule definition.
func (s Substitution) Fields() Definition {
return s.Definition
}
// Pattern is the internal regex pattern used by this rule.
func (s Substitution) Pattern() string {
return s.pattern.String()
}
func convertMessage(s string) string {
for _, spec := range []string{"'%s'", "\"%s\""} {
if strings.Count(s, spec) == 2 {
s = strings.Replace(s, spec, "%s", 1)
}
}
return s
}
func convertCaptureGroups(msg string) (string, error) {
captureOpen := regexp2.MustCompileStd(`(?<!\\)\((?!\?)`)
return captureOpen.Replace(msg, "(?:", -1, -1)
}
func subMsg(s Substitution, index int, observed string) (string, error) {
// Based on the current capture group (`idx`), we can determine
// the associated replacement string by using the `repl` slice:
expected := s.repl[index]
if s.Capitalize && observed == core.CapFirst(observed) {
expected = core.CapFirst(expected)
}
// TODO: Why do we need to check for this?
//
// This feels like a bug in `regexp2`.
hasIndex := regexp2.MustCompileStd(`\$\d+`)
if !hasIndex.MatchStringStd(expected) {
return expected, nil
}
msg := s.msgMap[index]
if s.Ignorecase {
msg = `(?i)` + msg
}
msgRe := regexp2.MustCompileStd(msg)
return msgRe.Replace(observed, expected, -1, -1)
}
// getOptions returns a slice of options from a match.
//
// For example, given the match "a|b|c", this function will return
// []string{"a", "b", "c"}.
//
// This allows the user to specify multiple options for a single match.
//
// https://vale.sh/docs/checks/substitution#multiple-suggestions
func getOptions(match string) []string {
options := []string{}
// We want to ignore any escaped pipes, so make a temporary substitution:
//
// TODO: Add support for `.Split` in `regexp2`.
temp := strings.ReplaceAll(match, `\|`, "PIPE")
for _, option := range strings.Split(temp, "|") {
if option != "" {
options = append(options, strings.ReplaceAll(option, "PIPE", `|`))
}
}
return options
}
================================================
FILE: internal/check/substitution_test.go
================================================
package check
import (
"testing"
"github.com/errata-ai/vale/v3/internal/core"
"github.com/errata-ai/vale/v3/internal/nlp"
)
func makeSubstitution(def baseCheck) (*Substitution, error) {
cfg, err := core.NewConfig(&core.CLIFlags{})
if err != nil {
return nil, err
}
rule, err := NewSubstitution(cfg, def, "")
if err != nil {
return nil, err
}
return &rule, nil
}
func TestConvertGroups(t *testing.T) {
converted, err := convertCaptureGroups("change in(?: )?to the (.*) directory")
if err != nil {
t.Fatal(err)
}
expected := "change in(?: )?to the (?:.*) directory"
if converted != expected {
t.Fatalf("Expected '%s', got '%s'", expected, converted)
}
}
func TestIsDeterministic(t *testing.T) {
swap := map[string]interface{}{
"extends": "substitution",
"name": "Vale.Terms",
"level": "error",
"message": "Use '%s' instead of '%s'.",
"scope": "text",
"ignorecase": true,
"swap": map[string]string{
"emnify iot supernetwork": "emnify IoT SuperNetwork",
"emnify": "emnify",
},
}
text := "EMnify IoT SuperNetwork"
for i := 0; i < 100; i++ {
rule, err := makeSubstitution(swap)
if err != nil {
t.Fatal(err)
}
actual, err := rule.Run(nlp.NewBlock(text, text, "text"), &core.File{}, &core.Config{})
if err != nil {
t.Fatal(err)
}
if len(actual) != 1 {
t.Fatalf("expected 1 alert, found %d", len(actual))
} else if actual[0].Match != "EMnify IoT SuperNetwork" {
t.Fatalf("Loop %d: expected 'EMnify IoT SuperNetwork', found '%s'", i, actual[0].Match)
}
}
}
func TestRegex(t *testing.T) {
swap := map[string]interface{}{
"extends": "substitution",
"name": "Vale.Terms",
"level": "error",
"message": "Use '%s' instead of '%s'.",
"scope": "text",
"ignorecase": true,
"swap": map[string]string{
`(?:foo|bar)`: "sub",
},
}
text := "foo"
rule, err := makeSubstitution(swap)
if err != nil {
t.Fatal(err)
}
actual, err := rule.Run(nlp.NewBlock(text, text, "text"), &core.File{}, &core.Config{})
if err != nil {
t.Fatal(err)
}
expected := "Use 'sub' instead of 'foo'."
message := actual[0].Message
if message != expected {
t.Fatalf("Expected message `%s`, got `%s`", expected, message)
}
}
func TestRegexEscapedParens(t *testing.T) {
swap := map[string]interface{}{
"extends": "substitution",
"name": "Vale.Terms",
"level": "error",
"message": "Use '%s' instead of '%s'.",
"scope": "text",
"ignorecase": true,
"swap": map[string]string{
`(?!\()(?:foo|bar)(?!\))?`: "sub",
},
}
text := "(foo)"
rule, err := makeSubstitution(swap)
if err != nil {
t.Fatal(err)
}
actual, err := rule.Run(nlp.NewBlock(text, text, "text"), &core.File{}, &core.Config{})
if err != nil {
t.Fatal(err)
}
expected := "Use 'sub' instead of 'foo'."
message := actual[0].Message
if message != expected {
t.Fatalf("Expected message `%s`, got `%s`", expected, message)
}
}
func TestOptions(t *testing.T) {
cases := map[string][]string{
"foo|bar": {"foo", "bar"},
"foo|bar|baz": {"foo", "bar", "baz"},
"|foo|": {"foo"},
`\|foo\|`: {"|foo|"},
`\|foo\||bar`: {"|foo|", "bar"},
"foo|bar|": {"foo", "bar"},
"foo|": {"foo"},
"|": {},
`\|`: {"|"},
}
for pattern, expected := range cases {
actual := getOptions(pattern)
if len(actual) != len(expected) {
t.Fatalf("Expected %d options, got %v", len(expected), actual)
}
for i, opt := range expected {
if actual[i] != opt {
t.Fatalf("Expected '%s', got '%s'", opt, actual[i])
}
}
}
}
================================================
FILE: internal/check/variables.go
================================================
package check
import (
"strings"
"github.com/errata-ai/regexp2"
"github.com/jdkato/twine/strcase"
"github.com/errata-ai/vale/v3/internal/core"
)
var reNumberList = regexp2.MustCompileStd(`\d+\.`)
var varToFunc = map[string]func(string, *regexp2.Regexp) (string, bool){
"$lower": lower,
"$upper": upper,
}
var readabilityMetrics = []string{
"Gunning Fog",
"Coleman-Liau",
"Flesch-Kincaid",
"SMOG",
"Automated Readability",
}
func wasIndicator(indicators []string) strcase.IndicatorFunc {
return func(word string, idx int) bool {
if core.HasAnySuffix(word, indicators) {
return true
} else if idx == 0 && reNumberList.MatchStringStd(word) {
return true
}
return false
}
}
func isMatch(r *regexp2.Regexp, s string) bool {
// TODO: `r.String() != ""`?
//
// Should we ensure that empty regexes == `nil`?
if r == nil || r.String() == "" {
return false
}
match, err := r.MatchString(s)
if err != nil {
return false
}
return match
}
func lower(s string, re *regexp2.Regexp) (string, bool)
gitextract_021vlxez/
├── .gitattributes
├── .github/
│ ├── CODE_OF_CONDUCT.md
│ ├── CONTRIBUTING.md
│ ├── ISSUE_TEMPLATE/
│ │ ├── 0_feature_request.yml
│ │ └── 1_bug_report.yml
│ └── workflows/
│ ├── codeql.yml
│ ├── golangci-lint.yml
│ └── main.yml
├── .gitignore
├── .gitlab-ci.yml
├── .golangci.yml
├── .goreleaser.yml
├── .pre-commit-hooks.yaml
├── .vale.ini
├── .well-known/
│ └── funding-manifest-urls
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── appveyor.yml
├── cmd/
│ └── vale/
│ ├── api.go
│ ├── color.go
│ ├── command.go
│ ├── common.go
│ ├── custom.go
│ ├── error.go
│ ├── flag.go
│ ├── funcs.go
│ ├── info.go
│ ├── json.go
│ ├── library.go
│ ├── line.go
│ ├── main.go
│ ├── main_unix.go
│ ├── main_windows.go
│ ├── native.go
│ ├── pkg_test.go
│ ├── sync.go
│ └── util.go
├── go.mod
├── go.sum
├── internal/
│ ├── check/
│ │ ├── action.go
│ │ ├── capitalization.go
│ │ ├── conditional.go
│ │ ├── consistency.go
│ │ ├── definition.go
│ │ ├── doc.go
│ │ ├── existence.go
│ │ ├── existence_test.go
│ │ ├── filter.go
│ │ ├── manager.go
│ │ ├── manager_test.go
│ │ ├── metric.go
│ │ ├── occurrence.go
│ │ ├── readability.go
│ │ ├── repetition.go
│ │ ├── scope.go
│ │ ├── scope_test.go
│ │ ├── script.go
│ │ ├── sequence.go
│ │ ├── spelling.go
│ │ ├── substitution.go
│ │ ├── substitution_test.go
│ │ ├── variables.go
│ │ └── variables_test.go
│ ├── core/
│ │ ├── alert.go
│ │ ├── config.go
│ │ ├── config_test.go
│ │ ├── doc.go
│ │ ├── error.go
│ │ ├── file.go
│ │ ├── format.go
│ │ ├── ini.go
│ │ ├── ini_test.go
│ │ ├── location.go
│ │ ├── markup.go
│ │ ├── source.go
│ │ ├── source_test.go
│ │ ├── util.go
│ │ ├── util_test.go
│ │ └── view.go
│ ├── glob/
│ │ ├── glob.go
│ │ └── glob_test.go
│ ├── lint/
│ │ ├── adoc.go
│ │ ├── ast.go
│ │ ├── code/
│ │ │ ├── c.go
│ │ │ ├── comments.go
│ │ │ ├── comments_test.go
│ │ │ ├── cpp.go
│ │ │ ├── css.go
│ │ │ ├── go.go
│ │ │ ├── java.go
│ │ │ ├── jl.go
│ │ │ ├── js.go
│ │ │ ├── lang.go
│ │ │ ├── proto.go
│ │ │ ├── py.go
│ │ │ ├── query.go
│ │ │ ├── rb.go
│ │ │ ├── rs.go
│ │ │ ├── ts.go
│ │ │ ├── tsx.go
│ │ │ ├── util.go
│ │ │ └── yaml.go
│ │ ├── code.go
│ │ ├── data.go
│ │ ├── dita.go
│ │ ├── doc.go
│ │ ├── fragment.go
│ │ ├── html.go
│ │ ├── html_test.go
│ │ ├── lint.go
│ │ ├── lint_test.go
│ │ ├── md.go
│ │ ├── mdx.go
│ │ ├── metadata.go
│ │ ├── org.go
│ │ ├── rst.go
│ │ ├── util.go
│ │ ├── walk.go
│ │ ├── xml.go
│ │ └── xml_test.go
│ ├── nlp/
│ │ ├── doc.go
│ │ ├── http.go
│ │ ├── prose.go
│ │ ├── provider.go
│ │ ├── tokenize.go
│ │ ├── tokenize_test.go
│ │ ├── util.go
│ │ └── var.go
│ ├── spell/
│ │ ├── LICENSE
│ │ ├── aff.go
│ │ ├── aff_test.go
│ │ ├── data/
│ │ │ ├── en_US-web.aff
│ │ │ └── en_US-web.dic
│ │ ├── doc.go
│ │ ├── gospell.go
│ │ ├── multi.go
│ │ └── words.go
│ └── system/
│ ├── cmd.go
│ ├── dir.go
│ ├── file.go
│ ├── path.go
│ ├── path_test.go
│ ├── system.go
│ ├── walk.go
│ └── zip.go
└── testdata/
├── Gemfile
├── comments/
│ ├── in/
│ │ ├── 0.go
│ │ ├── 1.rs
│ │ ├── 2.py
│ │ ├── 3.cpp
│ │ ├── 4.js
│ │ └── 5.yml
│ └── out/
│ ├── 0.json
│ ├── 1.json
│ ├── 2.json
│ ├── 3.json
│ ├── 4.json
│ └── 5.json
├── features/
│ ├── CLI.feature
│ ├── checks.feature
│ ├── comments.feature
│ ├── config.feature
│ ├── fragments.feature
│ ├── frontmatter.feature
│ ├── lint.feature
│ ├── misc.feature
│ ├── patterns.feature
│ ├── pkg.feature
│ ├── scopes.feature
│ ├── steps.rb
│ ├── styles.feature
│ ├── support/
│ │ └── env.rb
│ └── views.feature
├── fixtures/
│ ├── XSL/
│ │ └── docbook-xsl-snapshot/
│ │ ├── VERSION.xsl
│ │ ├── build.xml
│ │ ├── catalog.xml
│ │ ├── common/
│ │ │ ├── addns.xsl
│ │ │ ├── af.xml
│ │ │ ├── am.xml
│ │ │ ├── ar.xml
│ │ │ ├── as.xml
│ │ │ ├── ast.xml
│ │ │ ├── autoidx-kimber.xsl
│ │ │ ├── autoidx-kosek.xsl
│ │ │ ├── az.xml
│ │ │ ├── bg.xml
│ │ │ ├── bn.xml
│ │ │ ├── bn_in.xml
│ │ │ ├── bs.xml
│ │ │ ├── build.xml
│ │ │ ├── ca.xml
│ │ │ ├── charmap.xml
│ │ │ ├── charmap.xsl
│ │ │ ├── common.xml
│ │ │ ├── common.xsl
│ │ │ ├── cs.xml
│ │ │ ├── cy.xml
│ │ │ ├── da.xml
│ │ │ ├── de.xml
│ │ │ ├── el.xml
│ │ │ ├── en.xml
│ │ │ ├── entities.ent
│ │ │ ├── eo.xml
│ │ │ ├── es.xml
│ │ │ ├── et.xml
│ │ │ ├── eu.xml
│ │ │ ├── fa.xml
│ │ │ ├── fi.xml
│ │ │ ├── fr.xml
│ │ │ ├── ga.xml
│ │ │ ├── gentext.xsl
│ │ │ ├── gl.xml
│ │ │ ├── gu.xml
│ │ │ ├── he.xml
│ │ │ ├── hi.xml
│ │ │ ├── hr.xml
│ │ │ ├── hu.xml
│ │ │ ├── id.xml
│ │ │ ├── insertfile.xsl
│ │ │ ├── is.xml
│ │ │ ├── it.xml
│ │ │ ├── ja.xml
│ │ │ ├── ka.xml
│ │ │ ├── kn.xml
│ │ │ ├── ko.xml
│ │ │ ├── ky.xml
│ │ │ ├── l10n.dtd
│ │ │ ├── l10n.xml
│ │ │ ├── l10n.xsl
│ │ │ ├── la.xml
│ │ │ ├── labels.xsl
│ │ │ ├── lt.xml
│ │ │ ├── lv.xml
│ │ │ ├── ml.xml
│ │ │ ├── mn.xml
│ │ │ ├── mr.xml
│ │ │ ├── nb.xml
│ │ │ ├── nds.xml
│ │ │ ├── nl.xml
│ │ │ ├── nn.xml
│ │ │ ├── olink.xsl
│ │ │ ├── or.xml
│ │ │ ├── pa.xml
│ │ │ ├── pi.xml
│ │ │ ├── pi.xsl
│ │ │ ├── pl.xml
│ │ │ ├── pt.xml
│ │ │ ├── pt_br.xml
│ │ │ ├── refentry.xml
│ │ │ ├── refentry.xsl
│ │ │ ├── ro.xml
│ │ │ ├── ru.xml
│ │ │ ├── sk.xml
│ │ │ ├── sl.xml
│ │ │ ├── sq.xml
│ │ │ ├── sr.xml
│ │ │ ├── sr_Latn.xml
│ │ │ ├── stripns.xsl
│ │ │ ├── subtitles.xsl
│ │ │ ├── sv.xml
│ │ │ ├── ta.xml
│ │ │ ├── table.xsl
│ │ │ ├── targetdatabase.dtd
│ │ │ ├── targets.xsl
│ │ │ ├── te.xml
│ │ │ ├── th.xml
│ │ │ ├── titles.xsl
│ │ │ ├── tl.xml
│ │ │ ├── tr.xml
│ │ │ ├── uk.xml
│ │ │ ├── ur.xml
│ │ │ ├── utility.xml
│ │ │ ├── utility.xsl
│ │ │ ├── vi.xml
│ │ │ ├── xh.xml
│ │ │ ├── zh.xml
│ │ │ ├── zh_cn.xml
│ │ │ └── zh_tw.xml
│ │ ├── html/
│ │ │ ├── admon.xsl
│ │ │ ├── annotations.xsl
│ │ │ ├── autoidx-kimber.xsl
│ │ │ ├── autoidx-kosek.xsl
│ │ │ ├── autoidx-ng.xsl
│ │ │ ├── autoidx.xsl
│ │ │ ├── autotoc.xsl
│ │ │ ├── biblio-iso690.xsl
│ │ │ ├── biblio.xsl
│ │ │ ├── block.xsl
│ │ │ ├── build.xml
│ │ │ ├── callout.xsl
│ │ │ ├── changebars.xsl
│ │ │ ├── chunk-changebars.xsl
│ │ │ ├── chunk-code.xsl
│ │ │ ├── chunk-common.xsl
│ │ │ ├── chunk.xsl
│ │ │ ├── chunker.xsl
│ │ │ ├── chunkfast.xsl
│ │ │ ├── chunktoc.xsl
│ │ │ ├── component.xsl
│ │ │ ├── division.xsl
│ │ │ ├── docbook.css.xml
│ │ │ ├── docbook.xsl
│ │ │ ├── ebnf.xsl
│ │ │ ├── footnote.xsl
│ │ │ ├── formal.xsl
│ │ │ ├── glossary.xsl
│ │ │ ├── graphics.xsl
│ │ │ ├── highlight.xsl
│ │ │ ├── html-rtf.xsl
│ │ │ ├── html.xsl
│ │ │ ├── htmltbl.xsl
│ │ │ ├── index.xsl
│ │ │ ├── info.xsl
│ │ │ ├── inline.xsl
│ │ │ ├── its.xsl
│ │ │ ├── keywords.xsl
│ │ │ ├── lists.xsl
│ │ │ ├── maketoc.xsl
│ │ │ ├── manifest.xsl
│ │ │ ├── math.xsl
│ │ │ ├── oldchunker.xsl
│ │ │ ├── onechunk.xsl
│ │ │ ├── param.xml
│ │ │ ├── param.xsl
│ │ │ ├── pi.xml
│ │ │ ├── pi.xsl
│ │ │ ├── profile-chunk-code.xsl
│ │ │ ├── profile-chunk.xsl
│ │ │ ├── profile-docbook.xsl
│ │ │ ├── profile-onechunk.xsl
│ │ │ ├── publishers.xsl
│ │ │ ├── qandaset.xsl
│ │ │ ├── refentry.xsl
│ │ │ ├── sections.xsl
│ │ │ ├── synop.xsl
│ │ │ ├── table.xsl
│ │ │ ├── task.xsl
│ │ │ ├── titlepage.templates.xml
│ │ │ ├── titlepage.templates.xsl
│ │ │ ├── titlepage.xsl
│ │ │ ├── toc.xsl
│ │ │ ├── verbatim.xsl
│ │ │ └── xref.xsl
│ │ └── lib/
│ │ ├── build.xml
│ │ └── lib.xsl
│ ├── YAML/
│ │ ├── NoExtends.yml
│ │ └── NoMsg.yml
│ ├── actions/
│ │ ├── .vale.ini
│ │ ├── script.json
│ │ └── spell.json
│ ├── benchmarks/
│ │ ├── bench.md
│ │ └── bench.rst
│ ├── checks/
│ │ ├── Capitalization/
│ │ │ ├── .vale.ini
│ │ │ ├── test.md
│ │ │ ├── test2.md
│ │ │ ├── test3.md
│ │ │ └── test4.md
│ │ ├── Conditional/
│ │ │ ├── .vale.ini
│ │ │ └── test.md
│ │ ├── Existence/
│ │ │ ├── .vale.ini
│ │ │ ├── test.md
│ │ │ └── test2.md
│ │ ├── Metric/
│ │ │ ├── .vale.ini
│ │ │ ├── frontmatter.md
│ │ │ └── test.md
│ │ ├── Occurrence/
│ │ │ ├── .vale.ini
│ │ │ ├── test.md
│ │ │ ├── test2.md
│ │ │ └── test3.md
│ │ ├── Repetition/
│ │ │ ├── _vale
│ │ │ ├── test.md
│ │ │ └── text.rst
│ │ ├── Script/
│ │ │ ├── .vale.ini
│ │ │ └── test.md
│ │ ├── SentenceCase/
│ │ │ ├── .vale.ini
│ │ │ ├── test.html
│ │ │ └── test.md
│ │ ├── Sequence/
│ │ │ ├── .vale.ini
│ │ │ ├── test.adoc
│ │ │ ├── test.md
│ │ │ └── test.txt
│ │ ├── Spelling/
│ │ │ ├── .vale.ini
│ │ │ ├── test.md
│ │ │ └── test.org
│ │ └── Substitution/
│ │ ├── .vale.ini
│ │ ├── test.md
│ │ └── test2.md
│ ├── comments/
│ │ ├── .vale.ini
│ │ ├── test.adoc
│ │ ├── test.md
│ │ ├── test.mdx
│ │ ├── test.org
│ │ ├── test.rst
│ │ └── test2.mdx
│ ├── configs/
│ │ ├── .vale.ini
│ │ ├── ini/
│ │ │ ├── .vale.ini
│ │ │ └── styles/
│ │ │ ├── Joblint/
│ │ │ │ ├── Acronyms.yml
│ │ │ │ ├── Benefits.yml
│ │ │ │ ├── Bro.yml
│ │ │ │ ├── Competitive.yml
│ │ │ │ ├── Derogatory.yml
│ │ │ │ ├── DevEnv.yml
│ │ │ │ ├── DumbTitles.yml
│ │ │ │ ├── Gendered.yml
│ │ │ │ ├── Hair.yml
│ │ │ │ ├── LegacyTech.yml
│ │ │ │ ├── Meritocracy.yml
│ │ │ │ ├── Profanity.yml
│ │ │ │ ├── README.md
│ │ │ │ ├── Reassure.yml
│ │ │ │ ├── Sexualised.yml
│ │ │ │ ├── Starter.yml
│ │ │ │ ├── TechTerms.yml
│ │ │ │ ├── Visionary.yml
│ │ │ │ └── meta.json
│ │ │ └── proselint/
│ │ │ ├── Airlinese.yml
│ │ │ ├── AnimalLabels.yml
│ │ │ ├── Annotations.yml
│ │ │ ├── Apologizing.yml
│ │ │ ├── Archaisms.yml
│ │ │ ├── But.yml
│ │ │ ├── Cliches.yml
│ │ │ ├── CorporateSpeak.yml
│ │ │ ├── Currency.yml
│ │ │ ├── Cursing.yml
│ │ │ ├── DateCase.yml
│ │ │ ├── DateMidnight.yml
│ │ │ ├── DateRedundancy.yml
│ │ │ ├── DateSpacing.yml
│ │ │ ├── DenizenLabels.yml
│ │ │ ├── Diacritical.yml
│ │ │ ├── GenderBias.yml
│ │ │ ├── GroupTerms.yml
│ │ │ ├── Hedging.yml
│ │ │ ├── Hyperbole.yml
│ │ │ ├── Illusions.yml
│ │ │ ├── Jargon.yml
│ │ │ ├── LGBTOffensive.yml
│ │ │ ├── LGBTTerms.yml
│ │ │ ├── Malapropisms.yml
│ │ │ ├── Needless.yml
│ │ │ ├── Nonwords.yml
│ │ │ ├── Oxymorons.yml
│ │ │ ├── P-Value.yml
│ │ │ ├── RASSyndrome.yml
│ │ │ ├── README.md
│ │ │ ├── Skunked.yml
│ │ │ ├── Spelling.yml
│ │ │ ├── Typography.yml
│ │ │ ├── Uncomparables.yml
│ │ │ ├── Very.yml
│ │ │ └── meta.json
│ │ └── test.md
│ ├── filters/
│ │ ├── .vale.ini
│ │ └── test.md
│ ├── folders/
│ │ └── .vale.ini
│ ├── formats/
│ │ ├── .vale.ini
│ │ ├── adoc/
│ │ │ ├── .vale.ini
│ │ │ ├── styles/
│ │ │ │ └── Test/
│ │ │ │ ├── Rule.yml
│ │ │ │ └── Rule2.yml
│ │ │ ├── test.adoc
│ │ │ ├── test2.adoc
│ │ │ └── test3.adoc
│ │ ├── rst/
│ │ │ ├── .vale.ini
│ │ │ ├── styles/
│ │ │ │ └── Test/
│ │ │ │ └── Rule.yml
│ │ │ └── test.rst
│ │ ├── subdir1/
│ │ │ ├── test.hs
│ │ │ └── test.rs
│ │ ├── subdir2/
│ │ │ └── test.lua
│ │ ├── subdir3/
│ │ │ ├── .vale.ini
│ │ │ └── test.mdx
│ │ ├── test.adoc
│ │ ├── test.cc
│ │ ├── test.clj
│ │ ├── test.css
│ │ ├── test.dita
│ │ ├── test.hs
│ │ ├── test.java
│ │ ├── test.jl
│ │ ├── test.json
│ │ ├── test.jsx
│ │ ├── test.lua
│ │ ├── test.md
│ │ ├── test.mdx
│ │ ├── test.org
│ │ ├── test.php
│ │ ├── test.proto
│ │ ├── test.ps1
│ │ ├── test.py
│ │ ├── test.r
│ │ ├── test.rb
│ │ ├── test.rs
│ │ ├── test.rst
│ │ ├── test.ts
│ │ ├── test.txt
│ │ ├── test.xml
│ │ ├── test.xyz
│ │ └── test.yml
│ ├── fragments/
│ │ ├── .vale.ini
│ │ ├── styles/
│ │ │ └── config/
│ │ │ └── vocabularies/
│ │ │ └── Base/
│ │ │ ├── accept.txt
│ │ │ └── reject.txt
│ │ ├── test.cc
│ │ ├── test.go
│ │ ├── test.js
│ │ ├── test.py
│ │ ├── test.rb
│ │ ├── test.rs
│ │ ├── test2.py
│ │ └── test2.rs
│ ├── frontmatter/
│ │ ├── .vale.ini
│ │ ├── test.adoc
│ │ ├── test.md
│ │ ├── test.rst
│ │ ├── test2.md
│ │ ├── test2.mdx
│ │ ├── test3.md
│ │ └── test3.mdx
│ ├── glob/
│ │ ├── .vale.ini
│ │ └── content/
│ │ ├── a.md
│ │ ├── b/
│ │ │ └── b.md
│ │ └── c.md
│ ├── i18n/
│ │ ├── .vale.ini
│ │ ├── ru.adoc
│ │ └── zh.md
│ ├── misc/
│ │ ├── duplicates/
│ │ │ ├── .vale
│ │ │ ├── test.adoc
│ │ │ ├── test.md
│ │ │ └── test2.md
│ │ ├── filesystem/
│ │ │ └── projects/
│ │ │ ├── .vale.ini
│ │ │ ├── ini/
│ │ │ │ ├── .vale.ini
│ │ │ │ └── styles/
│ │ │ │ ├── Microsoft/
│ │ │ │ │ └── Headings.yml
│ │ │ │ └── Vocab/
│ │ │ │ └── Basic/
│ │ │ │ ├── accept.txt
│ │ │ │ ├── reject.txt
│ │ │ │ └── vocab.txt
│ │ │ └── test.md
│ │ ├── ids/
│ │ │ ├── .vale.ini
│ │ │ ├── test.adoc
│ │ │ └── test2.adoc
│ │ ├── infostring/
│ │ │ ├── .vale.ini
│ │ │ └── test.md
│ │ ├── line-endings/
│ │ │ ├── .vale
│ │ │ ├── CR.md
│ │ │ └── CRLF.md
│ │ ├── markup/
│ │ │ ├── .vale.ini
│ │ │ └── test.md
│ │ ├── one/
│ │ │ └── two/
│ │ │ └── three/
│ │ │ ├── four/
│ │ │ │ └── test.yml
│ │ │ └── test.yml
│ │ ├── symlinks/
│ │ │ ├── .vale.ini
│ │ │ ├── Symlinked/
│ │ │ │ └── Code.yml
│ │ │ ├── styles/
│ │ │ │ └── .gitkeep
│ │ │ └── test.md
│ │ └── unicode/
│ │ ├── .vale
│ │ ├── test.py
│ │ ├── test.rst
│ │ └── test.txt
│ ├── patterns/
│ │ ├── .vale.ini
│ │ ├── test.adoc
│ │ ├── test.html
│ │ ├── test.md
│ │ ├── test.mdx
│ │ ├── test.org
│ │ ├── test.py
│ │ ├── test.rst
│ │ ├── test2.html
│ │ ├── test2.rst
│ │ ├── test3.html
│ │ └── test4.html
│ ├── pkg/
│ │ ├── complete/
│ │ │ ├── .gitignore
│ │ │ ├── .vale.ini
│ │ │ ├── styles/
│ │ │ │ └── .gitkeep
│ │ │ └── test.md
│ │ ├── config/
│ │ │ ├── .gitignore
│ │ │ ├── .vale.ini
│ │ │ ├── styles/
│ │ │ │ └── .gitkeep
│ │ │ └── test.md
│ │ └── style/
│ │ ├── .gitignore
│ │ ├── .vale.ini
│ │ ├── styles/
│ │ │ └── .gitkeep
│ │ └── test.md
│ ├── plugins/
│ │ ├── .vale.ini
│ │ └── test.md
│ ├── scopes/
│ │ ├── attr/
│ │ │ ├── .vale.ini
│ │ │ ├── test.adoc
│ │ │ ├── test.html
│ │ │ ├── test.md
│ │ │ └── test.rst
│ │ ├── blockquote/
│ │ │ ├── .vale.ini
│ │ │ ├── test.adoc
│ │ │ ├── test.md
│ │ │ └── test.rst
│ │ ├── heading/
│ │ │ ├── _vale
│ │ │ ├── test.adoc
│ │ │ ├── test.dita
│ │ │ ├── test.html
│ │ │ ├── test.md
│ │ │ ├── test.rst
│ │ │ └── test.xml
│ │ ├── link/
│ │ │ ├── .vale.ini
│ │ │ ├── test.adoc
│ │ │ ├── test.md
│ │ │ ├── test.rst
│ │ │ └── vale
│ │ ├── list/
│ │ │ ├── _vale
│ │ │ ├── test.adoc
│ │ │ ├── test.html
│ │ │ ├── test.md
│ │ │ └── test.rst
│ │ ├── multi/
│ │ │ ├── .vale.ini
│ │ │ └── test.md
│ │ ├── raw/
│ │ │ ├── .vale.ini
│ │ │ ├── test.md
│ │ │ └── test.py
│ │ ├── rules/
│ │ │ ├── Alt.yml
│ │ │ ├── And.yml
│ │ │ ├── Code.yml
│ │ │ ├── Fence.yml
│ │ │ ├── H2.yml
│ │ │ ├── H3.yml
│ │ │ ├── HN.yml
│ │ │ ├── Heading.yml
│ │ │ ├── Link.yml
│ │ │ ├── List.yml
│ │ │ ├── MinH2.yml
│ │ │ ├── Negated.yml
│ │ │ ├── Para.yml
│ │ │ ├── Quote.yml
│ │ │ ├── Raw.yml
│ │ │ ├── Sentence.yml
│ │ │ ├── Strong.yml
│ │ │ ├── Table.yml
│ │ │ └── Title.yml
│ │ ├── sentence/
│ │ │ ├── .vale.ini
│ │ │ └── test.md
│ │ ├── skip/
│ │ │ ├── .vale.ini
│ │ │ ├── test.md
│ │ │ └── test.rst
│ │ └── table/
│ │ ├── _vale
│ │ ├── test.adoc
│ │ ├── test.html
│ │ ├── test.md
│ │ └── test.rst
│ ├── sections/
│ │ ├── .vale.ini
│ │ └── test.md
│ ├── spelling/
│ │ ├── .vale.ini
│ │ ├── test.adoc
│ │ ├── test.dic.md
│ │ ├── test.html
│ │ ├── test.md
│ │ ├── test.rst
│ │ └── test.txt
│ ├── spellingv3/
│ │ ├── .vale.ini
│ │ ├── test.adoc
│ │ ├── test.dic.md
│ │ ├── test.html
│ │ ├── test.md
│ │ └── test.rst
│ ├── styles/
│ │ ├── Readability/
│ │ │ ├── .vale.ini
│ │ │ ├── test.md
│ │ │ ├── test2.md
│ │ │ └── test3.md
│ │ ├── Scripts/
│ │ │ ├── .vale.ini
│ │ │ └── test.md
│ │ ├── demo/
│ │ │ ├── _vale
│ │ │ ├── test.adoc
│ │ │ ├── test.html
│ │ │ ├── test.md
│ │ │ ├── test.mdx
│ │ │ ├── test.rst
│ │ │ └── vocab.txt
│ │ ├── proselint/
│ │ │ ├── _vale
│ │ │ └── test.md
│ │ └── write-good/
│ │ ├── .vale
│ │ ├── test.cc
│ │ ├── test.md
│ │ └── test.txt
│ ├── templates/
│ │ ├── .vale.ini
│ │ ├── test.md
│ │ └── test2.md
│ ├── views/
│ │ ├── .vale.ini
│ │ ├── API.yml
│ │ ├── Petstore.yaml
│ │ ├── Rule.yml
│ │ ├── ansible.yml
│ │ ├── github-workflow.json
│ │ ├── newline.yml
│ │ ├── test.java
│ │ └── test.py
│ └── vocab/
│ ├── Basic/
│ │ ├── .vale.ini
│ │ └── test.md
│ ├── Multi/
│ │ ├── .vale.ini
│ │ └── test.md
│ └── styles/
│ └── config/
│ ├── ignorefiles/
│ │ └── vocab.txt
│ └── vocabularies/
│ ├── Basic/
│ │ ├── accept.txt
│ │ └── reject.txt
│ └── Second/
│ ├── accept.txt
│ └── reject.txt
├── pkg/
│ └── write-good/
│ ├── Cliches.yml
│ ├── E-Prime.yml
│ ├── Illusions.yml
│ ├── Passive.yml
│ ├── README.md
│ ├── So.yml
│ ├── ThereIs.yml
│ ├── TooWordy.yml
│ ├── Weasel.yml
│ └── meta.json
└── styles/
├── Bugs/
│ ├── EmptyReplace.yml
│ ├── KeepCase.yml
│ ├── MatchCase.yml
│ ├── Newline.yml
│ ├── SameCase.yml
│ ├── TermCase.yml
│ ├── URLCtx.yml
│ └── WrongExp.yml
├── Checks/
│ ├── MetricUndefined.yml
│ ├── MetricValue.yml
│ ├── MetricWords.yml
│ ├── MultiCapture.yml
│ └── ScriptRE.yml
├── Joblint/
│ ├── Acronyms.yml
│ ├── Benefits.yml
│ ├── Bro.yml
│ ├── Competitive.yml
│ ├── Derogatory.yml
│ ├── DevEnv.yml
│ ├── DumbTitles.yml
│ ├── Gendered.yml
│ ├── Hair.yml
│ ├── LegacyTech.yml
│ ├── Meritocracy.yml
│ ├── Profanity.yml
│ ├── README.md
│ ├── Reassure.yml
│ ├── Sexualised.yml
│ ├── Starter.yml
│ ├── TechTerms.yml
│ ├── Visionary.yml
│ └── meta.json
├── LanguageTool/
│ ├── AMBIG.yml
│ ├── APOS_ARE.yml
│ ├── ARE_USING.yml
│ ├── HYPHEN.yml
│ ├── Metadata.yml
│ ├── OF_ALL_TIMES.yml
│ └── WOULD_BE_JJ_VB.yml
├── Limit/
│ └── Rule.yml
├── Markup/
│ ├── ID.yml
│ ├── Repetition.yml
│ └── SentSpacing.yml
├── Meta/
│ └── Title.yml
├── README.md
├── RU/
│ └── RUSwap.yml
├── Readability/
│ ├── AutomatedReadability.yml
│ ├── ColemanLiau.yml
│ ├── FleschKincaid.yml
│ ├── FleschReadingEase.yml
│ ├── GunningFog.yml
│ ├── LIX.yml
│ └── SMOG.yml
├── Scopes/
│ ├── Code.yml
│ └── Titles.yml
├── Scripts/
│ ├── CustomMsg.yml
│ └── Test.yml
├── Spelling/
│ ├── GB.yml
│ ├── Ignore.yml
│ ├── Ignores.yml
│ ├── Test.yml
│ └── ignore/
│ ├── base.txt
│ └── second.txt
├── ZH/
│ └── Simple.yml
├── config/
│ ├── actions/
│ │ └── CamelToSnake.tengo
│ ├── dictionaries/
│ │ ├── en-GB.aff
│ │ ├── en-GB.dic
│ │ ├── en_US.aff
│ │ ├── en_US.dic
│ │ ├── en_medical.aff
│ │ ├── en_medical.dic
│ │ ├── test.aff
│ │ └── test.dic
│ ├── filters/
│ │ ├── extends.expr
│ │ ├── levels.expr
│ │ ├── min.expr
│ │ └── scope.expr
│ ├── ignore/
│ │ ├── base.txt
│ │ └── tech/
│ │ └── second.txt
│ ├── scripts/
│ │ └── NewSection.tengo
│ ├── templates/
│ │ ├── cli.tmpl
│ │ ├── collate.tmpl
│ │ ├── gitlab.tmpl
│ │ └── line.tmpl
│ ├── views/
│ │ ├── Ansible.yml
│ │ ├── GitHubActions.yml
│ │ ├── NewLine.yml
│ │ ├── OpenAPI.yml
│ │ ├── Petstore.yml
│ │ ├── Python.yml
│ │ ├── Strings.yml
│ │ └── Vale.yml
│ └── vocabularies/
│ ├── Cap/
│ │ ├── accept.txt
│ │ └── reject.txt
│ └── Rep/
│ ├── accept.txt
│ └── reject.txt
├── demo/
│ ├── Abbreviations.yml
│ ├── Cap.yml
│ ├── CapSub.yml
│ ├── CharCount.yml
│ ├── Code.yml
│ ├── CommasPerSentence.yml
│ ├── Contractions.yml
│ ├── CustomCap.yml
│ ├── Ending-Preposition.yml
│ ├── Fancy.yml
│ ├── Filters.yml
│ ├── HeadingStartsWithCapital.yml
│ ├── Hyphen.yml
│ ├── LookAround.yml
│ ├── Meet-up.yml
│ ├── Meetup.yml
│ ├── MinCount.yml
│ ├── ParagraphLength.yml
│ ├── Raw.yml
│ ├── Reading.yml
│ ├── ScopedHeading.yml
│ ├── SentenceCase.yml
│ ├── SentenceCaseAny.yml
│ ├── SentenceLength.yml
│ ├── Smart.yml
│ ├── Spacing.yml
│ ├── Spellcheck.yml
│ ├── Spelling.yml
│ ├── Terms.yml
│ └── ZeroOccurrence.yml
├── proselint/
│ ├── Airlinese.yml
│ ├── AnimalLabels.yml
│ ├── Annotations.yml
│ ├── Apologizing.yml
│ ├── Archaisms.yml
│ ├── But.yml
│ ├── Cliches.yml
│ ├── CorporateSpeak.yml
│ ├── Currency.yml
│ ├── Cursing.yml
│ ├── DateCase.yml
│ ├── DateMidnight.yml
│ ├── DateRedundancy.yml
│ ├── DateSpacing.yml
│ ├── DenizenLabels.yml
│ ├── Diacritical.yml
│ ├── GenderBias.yml
│ ├── GroupTerms.yml
│ ├── Hedging.yml
│ ├── Hyperbole.yml
│ ├── Illusions.yml
│ ├── Jargon.yml
│ ├── LGBTOffensive.yml
│ ├── LGBTTerms.yml
│ ├── Malapropisms.yml
│ ├── Needless.yml
│ ├── Nonwords.yml
│ ├── Oxymorons.yml
│ ├── P-Value.yml
│ ├── RASSyndrome.yml
│ ├── README.md
│ ├── Skunked.yml
│ ├── Spelling.yml
│ ├── Typography.yml
│ ├── Uncomparables.yml
│ ├── Very.yml
│ └── meta.json
├── vale/
│ ├── Annotations.yml
│ ├── Editorializing.yml
│ ├── Hedging.yml
│ ├── Litotes.yml
│ ├── Redundancy.yml
│ ├── Spacing.yml
│ └── Uncomparables.yml
└── write-good/
├── Cliches.yml
├── E-Prime.yml
├── Illusions.yml
├── Passive.yml
├── README.md
├── So.yml
├── ThereIs.yml
├── TooWordy.yml
├── We.yml
├── Weasel.yml
└── meta.json
SYMBOL INDEX (803 symbols across 137 files)
FILE: cmd/vale/api.go
type Style (line 17) | type Style struct
type Meta (line 35) | type Meta struct
function init (line 50) | func init() {
function fetch (line 58) | func fetch(src, dst string) error {
function install (line 87) | func install(args []string, flags *core.CLIFlags) error {
FILE: cmd/vale/color.go
function PrintVerboseAlerts (line 15) | func PrintVerboseAlerts(linted []*core.File, wrap bool) bool {
function printVerboseAlert (line 52) | func printVerboseAlert(f *core.File, wrap bool) (int, int, int) {
FILE: cmd/vale/command.go
type TaggedWord (line 20) | type TaggedWord struct
type CompiledRule (line 27) | type CompiledRule struct
function fix (line 63) | func fix(args []string, flags *core.CLIFlags) error {
function sync (line 90) | func sync(_ []string, flags *core.CLIFlags) error {
function printConfig (line 132) | func printConfig(_ []string, flags *core.CLIFlags) error {
function printMetrics (line 141) | func printMetrics(args []string, _ *core.CLIFlags) error {
function runTag (line 171) | func runTag(args []string, _ *core.CLIFlags) error {
function compileRule (line 187) | func compileRule(args []string, _ *core.CLIFlags) error {
function runRule (line 214) | func runRule(args []string, _ *core.CLIFlags) error {
function printVars (line 247) | func printVars(_ []string, flags *core.CLIFlags) error {
function printDirs (line 272) | func printDirs(_ []string, flags *core.CLIFlags) error {
function transform (line 314) | func transform(args []string, flags *core.CLIFlags) error {
function pathInfo (line 345) | func pathInfo(_ []string, flags *core.CLIFlags) error {
function loadConfigs (line 357) | func loadConfigs(args []string, _ *core.CLIFlags) error {
FILE: cmd/vale/common.go
function PrintAlerts (line 10) | func PrintAlerts(linted []*core.File, config *core.Config) (bool, error) {
FILE: cmd/vale/custom.go
type ProcessedFile (line 15) | type ProcessedFile struct
type Data (line 21) | type Data struct
function PrintCustomAlerts (line 27) | func PrintCustomAlerts(linted []*core.File, cfg *core.Config) (bool, err...
FILE: cmd/vale/error.go
type valeError (line 16) | type valeError struct
function parseError (line 27) | func parseError(err error) (valeError, error) {
function ShowError (line 61) | func ShowError(err error, style string, out io.Writer) {
FILE: cmd/vale/flag.go
function init (line 19) | func init() {
FILE: cmd/vale/funcs.go
function newBorderlessTable (line 17) | func newBorderlessTable(w io.Writer, wrap int) *tablewriter.Table {
function init (line 31) | func init() {
FILE: cmd/vale/info.go
function PrintIntro (line 78) | func PrintIntro() {
function toFlag (line 83) | func toFlag(name string) string {
function init (line 90) | func init() {
FILE: cmd/vale/json.go
function PrintJSONAlerts (line 10) | func PrintJSONAlerts(linted []*core.File) bool {
FILE: cmd/vale/library.go
function getLibrary (line 7) | func getLibrary(_ string) ([]Style, error) {
function inLibrary (line 20) | func inLibrary(name, path string) string {
FILE: cmd/vale/line.go
function PrintLineAlerts (line 13) | func PrintLineAlerts(linted []*core.File, relative bool) bool {
FILE: cmd/vale/main.go
function stat (line 18) | func stat() bool {
function looksLikeStdin (line 26) | func looksLikeStdin(s string) int {
function doLint (line 36) | func doLint(args []string, l *lint.Linter, glob string) ([]*core.File, e...
function handleError (line 80) | func handleError(err error) {
function main (line 85) | func main() {
FILE: cmd/vale/main_unix.go
function unsetManifestRegistry (line 6) | func unsetManifestRegistry(_ string) error {
function setManifestRegistry (line 10) | func setManifestRegistry(_, _ string) error {
FILE: cmd/vale/main_windows.go
function unsetManifestRegistry (line 12) | func unsetManifestRegistry(browser string) error {
function setManifestRegistry (line 30) | func setManifestRegistry(browser, manifestPath string) error {
FILE: cmd/vale/native.go
constant nativeHostName (line 19) | nativeHostName = "sh.vale.native"
constant releaseURL (line 20) | releaseURL = "https://github.com/errata-ai/vale-native/releases/download...
type manifest (line 40) | type manifest struct
function getNativeConfig (line 58) | func getNativeConfig() (string, error) {
function getExecName (line 89) | func getExecName(name string) string {
function getManifestDirs (line 96) | func getManifestDirs() (map[string]string, error) {
function getLocation (line 124) | func getLocation(browser string) (map[string]string, error) {
function writeNativeConfig (line 154) | func writeNativeConfig() (string, error) {
function installNativeHostUnix (line 177) | func installNativeHostUnix(manifestData []byte, manifestFile string) err...
function installNativeHostWindows (line 185) | func installNativeHostWindows(manifestData []byte, manifestFile, browser...
function getLatestHostRelease (line 212) | func getLatestHostRelease() (string, error) {
function hostDownloadURL (line 230) | func hostDownloadURL() (string, error) {
function installHost (line 239) | func installHost(manifestJSON []byte, manifestFile, browser string) error {
function installNativeHost (line 252) | func installNativeHost(args []string, _ *core.CLIFlags) error { //nolint...
function uninstallNativeHost (line 345) | func uninstallNativeHost(args []string, _ *core.CLIFlags) error {
FILE: cmd/vale/pkg_test.go
function mockPath (line 15) | func mockPath() (string, error) {
function TestNoPkgFound (line 30) | func TestNoPkgFound(t *testing.T) {
function TestLibrary (line 47) | func TestLibrary(t *testing.T) {
function TestLocalZip (line 67) | func TestLocalZip(t *testing.T) {
function TestLocalDir (line 92) | func TestLocalDir(t *testing.T) {
function TestLocalComplete (line 117) | func TestLocalComplete(t *testing.T) { //nolint:dupl
function TestLocalOnlyStyles (line 153) | func TestLocalOnlyStyles(t *testing.T) { //nolint:dupl
function TestV3Pkg (line 189) | func TestV3Pkg(t *testing.T) {
FILE: cmd/vale/sync.go
function initPath (line 15) | func initPath(cfg *core.Config) error {
function readPkg (line 35) | func readPkg(pkg, path string, idx int) error {
function loadPkg (line 45) | func loadPkg(name, urlOrPath, styles string, index int) error {
function loadLocalPkg (line 55) | func loadLocalPkg(name, pkgPath, styles string, index int) error {
function loadLocalZipPkg (line 59) | func loadLocalZipPkg(name, pkgPath, styles string, index int) error {
function download (line 72) | func download(name, url, styles string, index int) error {
function installPkg (line 88) | func installPkg(dir, name, styles string, index int) error {
function moveDir (line 144) | func moveDir(oldPath, newPath string) error {
function moveAsset (line 161) | func moveAsset(name, oldPath, newPath string) error {
FILE: cmd/vale/util.go
type Response (line 15) | type Response struct
function progressError (line 21) | func progressError(context string, err error, p *pterm.ProgressbarPrinte...
function pluralize (line 26) | func pluralize(s string, n int) string {
function getJSON (line 33) | func getJSON(data interface{}) string {
function fetchJSON (line 41) | func fetchJSON(url string) ([]byte, error) {
function printJSON (line 50) | func printJSON(t interface{}) error {
function sendResponse (line 61) | func sendResponse(msg string, err error) error {
function toCodeStyle (line 69) | func toCodeStyle(s string) string {
FILE: internal/check/action.go
type Solution (line 21) | type Solution struct
type fixer (line 26) | type fixer
function ParseAlert (line 37) | func ParseAlert(s string, cfg *core.Config) (Solution, error) {
function FixAlert (line 55) | func FixAlert(alert core.Alert, cfg *core.Config) ([]string, error) {
function suggest (line 63) | func suggest(alert core.Alert, cfg *core.Config) ([]string, error) {
function script (line 77) | func script(name string, alert core.Alert, cfg *core.Config) ([]string, ...
function spelling (line 115) | func spelling(alert core.Alert, cfg *core.Config) ([]string, error) {
function replace (line 141) | func replace(alert core.Alert, _ *core.Config) ([]string, error) {
function remove (line 145) | func remove(_ core.Alert, _ *core.Config) ([]string, error) {
function convert (line 149) | func convert(alert core.Alert, _ *core.Config) ([]string, error) {
function edit (line 157) | func edit(alert core.Alert, _ *core.Config) ([]string, error) {
FILE: internal/check/capitalization.go
type Capitalization (line 12) | type Capitalization struct
method Run (line 115) | func (c Capitalization) Run(blk nlp.Block, _ *core.File, cfg *core.Con...
method Fields (line 145) | func (c Capitalization) Fields() Definition {
method Pattern (line 150) | func (c Capitalization) Pattern() string {
function NewCapitalization (line 39) | func NewCapitalization(cfg *core.Config, generic baseCheck, path string)...
FILE: internal/check/conditional.go
type Conditional (line 11) | type Conditional struct
method Run (line 61) | func (c Conditional) Run(blk nlp.Block, f *core.File, cfg *core.Config...
method Fields (line 109) | func (c Conditional) Fields() Definition {
method Pattern (line 114) | func (c Conditional) Pattern() string {
function NewConditional (line 23) | func NewConditional(cfg *core.Config, generic baseCheck, path string) (C...
FILE: internal/check/consistency.go
type step (line 14) | type step struct
type Consistency (line 20) | type Consistency struct
method Run (line 81) | func (o Consistency) Run(blk nlp.Block, f *core.File, cfg *core.Config...
method Fields (line 115) | func (o Consistency) Fields() Definition {
method Pattern (line 120) | func (o Consistency) Pattern() string {
function NewConsistency (line 33) | func NewConsistency(cfg *core.Config, generic baseCheck, path string) (C...
FILE: internal/check/definition.go
type FilterEnv (line 21) | type FilterEnv struct
type Rule (line 26) | type Rule interface
type Definition (line 33) | type Definition struct
constant ignoreCase (line 114) | ignoreCase = `(?i)`
constant wordTemplate (line 115) | wordTemplate = `(?m)\b(?:%s)\b`
constant nonwordTemplate (line 116) | nonwordTemplate = `(?m)(?:%s)`
constant tokenTemplate (line 117) | tokenTemplate = `^(?:%s)$`
type baseCheck (line 120) | type baseCheck
function buildRule (line 122) | func buildRule(cfg *core.Config, generic baseCheck) (Rule, error) {
function formatMessages (line 168) | func formatMessages(msg string, desc string, subs ...string) (string, st...
function re2Loc (line 174) | func re2Loc(s string, loc []int) (string, error) {
function makeAlert (line 186) | func makeAlert(chk Definition, loc []int, txt string, cfg *core.Config) ...
function parse (line 217) | func parse(file []byte, path string) (map[string]interface{}, error) {
function validateDefinition (line 237) | func validateDefinition(generic map[string]interface{}, path string) err...
function readStructureError (line 277) | func readStructureError(err error, path string) error {
function makeRegexp (line 296) | func makeRegexp(
function matchToken (line 328) | func matchToken(expected, observed string, ignorecase bool) bool {
function updateExceptions (line 341) | func updateExceptions(previous []string, current []string, vocab bool) (...
function decodeRule (line 375) | func decodeRule(input interface{}, output interface{}) error {
function checkScopes (line 391) | func checkScopes(scopes []string, path string) error {
FILE: internal/check/existence.go
type Existence (line 14) | type Existence struct
method Run (line 77) | func (e Existence) Run(blk nlp.Block, _ *core.File, cfg *core.Config) ...
method Fields (line 100) | func (e Existence) Fields() Definition {
method Pattern (line 105) | func (e Existence) Pattern() string {
function NewExistence (line 29) | func NewExistence(cfg *core.Config, generic baseCheck, path string) (Exi...
FILE: internal/check/existence_test.go
function makeExistence (line 10) | func makeExistence(tokens []string) (*Existence, error) {
function TestExistence (line 26) | func TestExistence(t *testing.T) {
function FuzzExistenceInit (line 48) | func FuzzExistenceInit(f *testing.F) {
function FuzzExistence (line 55) | func FuzzExistence(f *testing.F) {
FILE: internal/check/filter.go
function filter (line 14) | func filter(mgr *Manager) (map[string]Rule, error) {
FILE: internal/check/manager.go
type Manager (line 19) | type Manager struct
method AddRule (line 80) | func (mgr *Manager) AddRule(name string, rule Rule) error {
method AddRuleFromFile (line 89) | func (mgr *Manager) AddRuleFromFile(name, path string) error {
method Rules (line 98) | func (mgr *Manager) Rules() map[string]Rule {
method HasScope (line 103) | func (mgr *Manager) HasScope(scope string) bool {
method NeedsTagging (line 109) | func (mgr *Manager) NeedsTagging() bool {
method AssignNLP (line 114) | func (mgr *Manager) AssignNLP(f *core.File) nlp.Info {
method addStyle (line 125) | func (mgr *Manager) addStyle(path string) error {
method addRuleFromSource (line 136) | func (mgr *Manager) addRuleFromSource(name, path string) error {
method addCheck (line 154) | func (mgr *Manager) addCheck(file []byte, chkName, path string) error {
method loadDefaultRules (line 195) | func (mgr *Manager) loadDefaultRules() error {
method loadStyles (line 236) | func (mgr *Manager) loadStyles(styles []string) error {
method loadVocabRules (line 268) | func (mgr *Manager) loadVocabRules() {
method hasStyle (line 294) | func (mgr *Manager) hasStyle(name string) bool {
method needsStyle (line 299) | func (mgr *Manager) needsStyle(name string) bool {
function NewManager (line 30) | func NewManager(config *core.Config) (*Manager, error) {
FILE: internal/check/manager_test.go
function TestFormatMessage (line 48) | func TestFormatMessage(t *testing.T) {
FILE: internal/check/metric.go
type Metric (line 23) | type Metric struct
method Run (line 51) | func (o Metric) Run(_ nlp.Block, f *core.File, _ *core.Config) ([]core...
method Fields (line 99) | func (o Metric) Fields() Definition {
method Pattern (line 104) | func (o Metric) Pattern() string {
function NewMetric (line 35) | func NewMetric(_ *core.Config, generic baseCheck, path string) (Metric, ...
function evalMath (line 108) | func evalMath(
FILE: internal/check/occurrence.go
type Occurrence (line 14) | type Occurrence struct
method Run (line 54) | func (o Occurrence) Run(blk nlp.Block, _ *core.File, cfg *core.Config)...
method Fields (line 117) | func (o Occurrence) Fields() Definition {
method Pattern (line 122) | func (o Occurrence) Pattern() string {
function NewOccurrence (line 24) | func NewOccurrence(_ *core.Config, generic baseCheck, path string) (Occu...
FILE: internal/check/readability.go
type Readability (line 13) | type Readability struct
method Run (line 44) | func (o Readability) Run(blk nlp.Block, _ *core.File, _ *core.Config) ...
method Fields (line 79) | func (o Readability) Fields() Definition {
method Pattern (line 84) | func (o Readability) Pattern() string {
function NewReadability (line 23) | func NewReadability(_ *core.Config, generic baseCheck, path string) (Rea...
FILE: internal/check/repetition.go
type Repetition (line 13) | type Repetition struct
method Run (line 64) | func (o Repetition) Run(blk nlp.Block, _ *core.File, cfg *core.Config)...
method Fields (line 128) | func (o Repetition) Fields() Definition {
method Pattern (line 133) | func (o Repetition) Pattern() string {
function NewRepetition (line 27) | func NewRepetition(cfg *core.Config, generic baseCheck, path string) (Re...
FILE: internal/check/scope.go
type Selector (line 11) | type Selector struct
method Sections (line 80) | func (s *Selector) Sections() []string {
method Contains (line 89) | func (s *Selector) Contains(sel Selector) bool {
method ContainsString (line 94) | func (s *Selector) ContainsString(scope []string) bool {
method Equal (line 105) | func (s *Selector) Equal(sel Selector) bool {
method Has (line 118) | func (s *Selector) Has(scope string) bool {
type Scope (line 16) | type Scope struct
method Matches (line 49) | func (s Scope) Matches(blk nlp.Block) bool {
method partMatches (line 62) | func (s Scope) partMatches(target, parent Selector, options []Selector...
function NewSelector (line 20) | func NewSelector(value []string) Selector {
function NewScope (line 36) | func NewScope(value []string) Scope {
FILE: internal/check/scope_test.go
function TestSelectors (line 9) | func TestSelectors(t *testing.T) {
FILE: internal/check/script.go
type Script (line 17) | type Script struct
method Run (line 50) | func (s Script) Run(blk nlp.Block, _ *core.File, _ *core.Config) ([]co...
method Fields (line 120) | func (s Script) Fields() Definition {
method Pattern (line 125) | func (s Script) Pattern() string {
function NewScript (line 25) | func NewScript(cfg *core.Config, generic baseCheck, path string) (Script...
function parseMatches (line 101) | func parseMatches(a []interface{}) []map[string]interface{} {
FILE: internal/check/sequence.go
type NLPToken (line 16) | type NLPToken struct
type Sequence (line 28) | type Sequence struct
method Fields (line 81) | func (s Sequence) Fields() Definition {
method Pattern (line 86) | func (s Sequence) Pattern() string {
method Run (line 247) | func (s Sequence) Run(blk nlp.Block, f *core.File, _ *core.Config) ([]...
function NewSequence (line 36) | func NewSequence(cfg *core.Config, generic baseCheck, path string) (Sequ...
function makeTokens (line 90) | func makeTokens(s *Sequence, generic baseCheck) error {
function tokensMatch (line 117) | func tokensMatch(token NLPToken, word tag.Token) bool {
function sequenceMatches (line 136) | func sequenceMatches(idx int, chk Sequence, target NLPToken, words []tag...
function stepsToString (line 214) | func stepsToString(steps []string) string {
FILE: internal/check/spelling.go
type Spelling (line 27) | type Spelling struct
method Run (line 166) | func (s Spelling) Run(blk nlp.Block, _ *core.File, _ *core.Config) ([]...
method Fields (line 202) | func (s Spelling) Fields() Definition {
method Pattern (line 207) | func (s Spelling) Pattern() string {
method Suggest (line 212) | func (s Spelling) Suggest(word string) []string {
function addFilters (line 43) | func addFilters(s *Spelling, generic baseCheck, _ *core.Config) error {
function addExceptions (line 60) | func addExceptions(s *Spelling, generic baseCheck, cfg *core.Config) err...
function NewSpelling (line 92) | func NewSpelling(cfg *core.Config, generic baseCheck, path string) (Spel...
function makeSpeller (line 216) | func makeSpeller(s *Spelling, cfg *core.Config, rulePath string) (*spell...
FILE: internal/check/substitution.go
type Substitution (line 16) | type Substitution struct
method Run (line 96) | func (s Substitution) Run(blk nlp.Block, _ *core.File, cfg *core.Confi...
method Fields (line 160) | func (s Substitution) Fields() Definition {
method Pattern (line 165) | func (s Substitution) Pattern() string {
function NewSubstitution (line 34) | func NewSubstitution(cfg *core.Config, generic baseCheck, path string) (...
function convertMessage (line 169) | func convertMessage(s string) string {
function convertCaptureGroups (line 178) | func convertCaptureGroups(msg string) (string, error) {
function subMsg (line 183) | func subMsg(s Substitution, index int, observed string) (string, error) {
function getOptions (line 216) | func getOptions(match string) []string {
FILE: internal/check/substitution_test.go
function makeSubstitution (line 10) | func makeSubstitution(def baseCheck) (*Substitution, error) {
function TestConvertGroups (line 24) | func TestConvertGroups(t *testing.T) {
function TestIsDeterministic (line 36) | func TestIsDeterministic(t *testing.T) {
function TestRegex (line 70) | func TestRegex(t *testing.T) {
function TestRegexEscapedParens (line 100) | func TestRegexEscapedParens(t *testing.T) {
function TestOptions (line 130) | func TestOptions(t *testing.T) {
FILE: internal/check/variables.go
function wasIndicator (line 27) | func wasIndicator(indicators []string) strcase.IndicatorFunc {
function isMatch (line 38) | func isMatch(r *regexp2.Regexp, s string) bool {
function lower (line 52) | func lower(s string, re *regexp2.Regexp) (string, bool) {
function upper (line 57) | func upper(s string, re *regexp2.Regexp) (string, bool) {
function title (line 62) | func title(s string, except *regexp2.Regexp, tc *strcase.TitleConverter,...
function sentence (line 100) | func sentence(s string, except *regexp2.Regexp, sc *strcase.SentenceConv...
FILE: internal/check/variables_test.go
type changeCase (line 9) | type changeCase struct
function TestSentence (line 15) | func TestSentence(t *testing.T) {
FILE: internal/core/alert.go
type Action (line 14) | type Action struct
type Alert (line 20) | type Alert struct
function FormatAlert (line 37) | func FormatAlert(a *Alert, limit int, level, name string) {
type ByPosition (line 49) | type ByPosition
method Len (line 51) | func (a ByPosition) Len() int { return len(a) }
method Swap (line 52) | func (a ByPosition) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
method Less (line 53) | func (a ByPosition) Less(i, j int) bool {
type ByName (line 63) | type ByName
method Len (line 65) | func (a ByName) Len() int { return len(a) }
method Swap (line 66) | func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
method Less (line 67) | func (a ByName) Less(i, j int) bool {
FILE: internal/core/config.go
function FindConfigAsset (line 80) | func FindConfigAsset(cfg *Config, name, dir string) string {
function FindAsset (line 86) | func FindAsset(cfg *Config, path string) string {
function IgnoreFiles (line 111) | func IgnoreFiles(stylesPath string) ([]string, error) {
function getConfigAsset (line 118) | func getConfigAsset(target string, paths, dirs []string) string {
function DefaultConfig (line 137) | func DefaultConfig() (string, error) {
function DefaultStylesPath (line 149) | func DefaultStylesPath() (string, error) {
type CLIFlags (line 165) | type CLIFlags struct
type Config (line 189) | type Config struct
method AddConfigFile (line 262) | func (c *Config) AddConfigFile(name string) {
method AddStylesPath (line 269) | func (c *Config) AddStylesPath(path string) {
method ConfigFile (line 279) | func (c *Config) ConfigFile() string {
method Root (line 287) | func (c *Config) Root() (string, error) {
method StylesPath (line 308) | func (c *Config) StylesPath() string {
method SearchPaths (line 315) | func (c *Config) SearchPaths() []string {
method AddWordListFile (line 324) | func (c *Config) AddWordListFile(name string, accept bool) error {
method addWordList (line 333) | func (c *Config) addWordList(r io.Reader, accept bool) error {
method String (line 348) | func (c *Config) String() string {
function NewConfig (line 232) | func NewConfig(flags *CLIFlags) (*Config, error) {
function GetPackages (line 354) | func GetPackages(src string) ([]string, error) {
function pipeConfig (line 366) | func pipeConfig(cfg *Config) ([]string, error) {
function MockLoad (line 390) | func MockLoad(project, local string, cfg *Config) error {
FILE: internal/core/config_test.go
function TestInitCfg (line 13) | func TestInitCfg(t *testing.T) {
function TestGetIgnores (line 28) | func TestGetIgnores(t *testing.T) {
function TestFindAsset (line 40) | func TestFindAsset(t *testing.T) {
function TestFindAssetDefault (line 60) | func TestFindAssetDefault(t *testing.T) {
function TestFallbackToDefault (line 95) | func TestFallbackToDefault(t *testing.T) {
FILE: internal/core/error.go
type lineError (line 15) | type lineError struct
type errorCondition (line 21) | type errorCondition
function annotate (line 23) | func annotate(file []byte, target string, finder errorCondition) (lineEr...
function NewError (line 74) | func NewError(code, title, msg string) error {
function NewE100 (line 88) | func NewE100(context string, err error) error {
function NewE201 (line 99) | func NewE201(msg, value, path string, finder errorCondition) error {
function NewE201FromTarget (line 123) | func NewE201FromTarget(msg, value, file string) error {
function NewE201FromPosition (line 134) | func NewE201FromPosition(msg, file string, goal int) error {
FILE: internal/core/file.go
type File (line 27) | type File struct
method SortedAlerts (line 147) | func (f *File) SortedAlerts() []Alert {
method ComputeMetrics (line 153) | func (f *File) ComputeMetrics() (map[string]interface{}, error) {
method FindLoc (line 182) | func (f *File) FindLoc(ctx, s string, pad, count int, a Alert) (int, [...
method assignLoc (line 223) | func (f *File) assignLoc(ctx string, blk nlp.Block, pad int, a Alert) ...
method SetText (line 282) | func (f *File) SetText(s string) {
method SetNormedExt (line 289) | func (f *File) SetNormedExt(ext string) {
method AddAlert (line 294) | func (f *File) AddAlert(a Alert, blk nlp.Block, lines, pad int, lookup...
method UpdateComments (line 353) | func (f *File) UpdateComments(comment string) {
method QueryComments (line 385) | func (f *File) QueryComments(check string) bool {
method ResetComments (line 401) | func (f *File) ResetComments() {
function NewFile (line 52) | func NewFile(src string, config *Config) (*File, error) {
function locFromByteOffset (line 259) | func locFromByteOffset(ctx string, begin, end, pad int) (int, []int) {
FILE: internal/core/format.go
function FormatFromExt (line 99) | func FormatFromExt(path string, mapping map[string]string) (string, stri...
function getFormat (line 128) | func getFormat(ext string) string {
function GetNormedExt (line 138) | func GetNormedExt(ext string) string {
FILE: internal/core/ini.go
function mergeValues (line 23) | func mergeValues(shadows []string) []string {
function loadVocab (line 34) | func loadVocab(root string, cfg *Config) error {
function validateLevel (line 65) | func validateLevel(key, val string, cfg *Config) bool {
function expandPaths (line 238) | func expandPaths(file *ini.File, source interface{}) {
function shadowMerge (line 261) | func shadowMerge(primary *ini.File, secondary *ini.File) {
function shadowLoad (line 284) | func shadowLoad(source interface{}, others ...interface{}) (*ini.File, e...
function processSources (line 312) | func processSources(cfg *Config, sources []string) (*ini.File, error) {
function processConfig (line 340) | func processConfig(uCfg *ini.File, cfg *Config, dry bool) (*ini.File, er...
FILE: internal/core/ini_test.go
function Test_processConfig_commentDelimiters (line 10) | func Test_processConfig_commentDelimiters(t *testing.T) {
function Test_processConfig_commentDelimiters_error (line 61) | func Test_processConfig_commentDelimiters_error(t *testing.T) {
function Test_processConfig_transform (line 119) | func Test_processConfig_transform(t *testing.T) {
function Test_processConfig_transform_abs (line 153) | func Test_processConfig_transform_abs(t *testing.T) {
FILE: internal/core/location.go
function initialPosition (line 12) | func initialPosition(ctx, txt string, a Alert) (int, string) {
function guessLocation (line 70) | func guessLocation(ctx, sub, match string) (int, string) {
function allStringsInString (line 92) | func allStringsInString(subs []string, s string) bool {
FILE: internal/core/markup.go
function stripMarkdown (line 60) | func stripMarkdown(s string) string {
FILE: internal/core/source.go
type ConfigSrc (line 18) | type ConfigSrc
constant FileSrc (line 21) | FileSrc ConfigSrc = iota
constant StringSrc (line 22) | StringSrc
function ReadPipeline (line 31) | func ReadPipeline(flags *CLIFlags, dry bool) (*Config, error) {
function from (line 63) | func from(provider ConfigSrc, src string, cfg *Config, dry bool) (*ini.F...
function FromFile (line 76) | func FromFile(cfg *Config, dry bool) (*ini.File, error) {
function FromString (line 81) | func FromString(src string, cfg *Config, dry bool) (*ini.File, error) {
function validateFlags (line 85) | func validateFlags(cfg *Config) error {
function loadStdin (line 94) | func loadStdin(src string, cfg *Config, dry bool) (*ini.File, error) {
function loadINI (line 102) | func loadINI(cfg *Config, dry bool) (*ini.File, error) {
function loadConfig (line 170) | func loadConfig(names []string) (string, error) {
FILE: internal/core/source_test.go
function TestNoBaseConfig (line 13) | func TestNoBaseConfig(t *testing.T) {
function TestFlagBase (line 37) | func TestFlagBase(t *testing.T) {
function TestEnvBase (line 61) | func TestEnvBase(t *testing.T) {
FILE: internal/core/util.go
function CapFirst (line 24) | func CapFirst(s string) string {
function Sanitize (line 32) | func Sanitize(txt string) string {
function StripANSI (line 38) | func StripANSI(s string) string {
function WhitespaceToSpace (line 43) | func WhitespaceToSpace(msg string) string {
function ShouldIgnoreDirectory (line 48) | func ShouldIgnoreDirectory(directoryPath string) bool {
function ToSentence (line 63) | func ToSentence(words []string, andOrOr string) string {
function IsLetter (line 85) | func IsLetter(s string) bool {
function IsCode (line 95) | func IsCode(s string) bool {
function IsPhrase (line 107) | func IsPhrase(s string) bool {
function InRange (line 117) | func InRange(n int, r []int) bool {
function CondSprintf (line 122) | func CondSprintf(format string, v ...interface{}) string {
function FormatMessage (line 129) | func FormatMessage(msg string, subs ...string) string {
function Substitute (line 134) | func Substitute(src, sub string, char rune) (string, bool) {
function StringsToInterface (line 149) | func StringsToInterface(strings []string) []interface{} {
function Indent (line 158) | func Indent(text, indent string) string {
function StringInSlice (line 174) | func StringInSlice(a string, slice []string) bool {
function IntInSlice (line 184) | func IntInSlice(a int, slice []int) bool {
function AllStringsInSlice (line 194) | func AllStringsInSlice(strings []string, slice []string) bool {
function SplitLines (line 204) | func SplitLines(data []byte, atEOF bool) (adv int, token []byte, err err...
function TextToContext (line 224) | func TextToContext(text string, meta *nlp.Info) []nlp.TaggedWord {
function ReplaceAllStringSubmatchFunc (line 250) | func ReplaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl fu...
function HasAnySuffix (line 271) | func HasAnySuffix(s string, suffixes []string) bool {
function UniqueStrings (line 282) | func UniqueStrings(slice []string) []string {
FILE: internal/core/util_test.go
function TestFormatFromExt (line 11) | func TestFormatFromExt(t *testing.T) {
function TestPrepText (line 29) | func TestPrepText(t *testing.T) {
function TestPhrase (line 41) | func TestPhrase(t *testing.T) {
function TestNormalizePath (line 58) | func TestNormalizePath(t *testing.T) {
function TestShouldIgnoreDirectory (line 92) | func TestShouldIgnoreDirectory(t *testing.T) {
FILE: internal/core/view.go
type Scope (line 23) | type Scope struct
type View (line 38) | type View struct
method Apply (line 77) | func (b *View) Apply(f *File) ([]ScopedValues, error) {
type ScopedValues (line 44) | type ScopedValues struct
function NewView (line 51) | func NewView(path string) (*View, error) {
function selectStrings (line 100) | func selectStrings(value DaselValue, expr string) ([]string, error) {
function selectStringsV2 (line 127) | func selectStringsV2(value DaselValue, expr string) ([]string, error) {
function normalize (line 142) | func normalize(v any) any {
function fileToValue (line 167) | func fileToValue(f *File) (DaselValue, error) {
FILE: internal/glob/glob.go
type Glob (line 12) | type Glob struct
method Match (line 18) | func (g Glob) Match(query string) bool {
method MatchAny (line 32) | func (g Glob) MatchAny(query []string) bool {
function NewGlob (line 42) | func NewGlob(pat string) (Glob, error) {
function Compile (line 56) | func Compile(pat string) (Glob, error) {
FILE: internal/glob/glob_test.go
type globTest (line 8) | type globTest struct
function TestGlob (line 35) | func TestGlob(t *testing.T) {
FILE: internal/lint/adoc.go
method lintADoc (line 37) | func (l *Linter) lintADoc(f *core.File) error {
function callAdoc (line 95) | func callAdoc(text, exe string, attrs map[string]string) (string, error) {
function parseAttributes (line 104) | func parseAttributes(attrs map[string]string) []string {
FILE: internal/lint/ast.go
method lintHTMLTokens (line 34) | func (l *Linter) lintHTMLTokens(f *core.File, raw []byte, offset int) er...
method lintScope (line 139) | func (l *Linter) lintScope(f *core.File, state *walker, txt string) error {
method lintSizedScopes (line 164) | func (l *Linter) lintSizedScopes(f *core.File) error {
method lintTags (line 183) | func (l *Linter) lintTags(f *core.File, state *walker, tok html.Token) e...
function checkClasses (line 200) | func checkClasses(attr string, ignore []string) bool {
function shouldBeSkipped (line 212) | func shouldBeSkipped(tagHistory []string, ext string) bool {
function clean (line 225) | func clean(txt, attr string, skip, inline bool) (string, bool) {
function getAttribute (line 242) | func getAttribute(tok html.Token, key string) string {
FILE: internal/lint/code.go
function updateQueries (line 16) | func updateQueries(f *core.File, views map[string]*core.View) ([]core.Sc...
method lintCode (line 31) | func (l *Linter) lintCode(f *core.File) error {
method lintCodeOld (line 81) | func (l *Linter) lintCodeOld(f *core.File) error {
FILE: internal/lint/code/c.go
function C (line 10) | func C() *Language {
FILE: internal/lint/code/comments.go
type Comment (line 14) | type Comment struct
function doneMerging (line 24) | func doneMerging(curr, prev Comment) bool {
function addSourceLine (line 35) | func addSourceLine(line string, atEnd bool) string {
function coalesce (line 51) | func coalesce(comments []Comment) []Comment {
function GetComments (line 100) | func GetComments(source []byte, lang *Language) ([]Comment, error) {
FILE: internal/lint/code/comments_test.go
function TestComments (line 14) | func TestComments(t *testing.T) {
FILE: internal/lint/code/cpp.go
function Cpp (line 10) | func Cpp() *Language {
FILE: internal/lint/code/css.go
function CSS (line 10) | func CSS() *Language {
FILE: internal/lint/code/go.go
function Go (line 10) | func Go() *Language {
FILE: internal/lint/code/java.go
function Java (line 10) | func Java() *Language {
FILE: internal/lint/code/jl.go
function Julia (line 10) | func Julia() *Language {
FILE: internal/lint/code/js.go
function JavaScript (line 10) | func JavaScript() *Language {
FILE: internal/lint/code/lang.go
type padding (line 11) | type padding
type Language (line 16) | type Language struct
function GetLanguageFromExt (line 25) | func GetLanguageFromExt(ext string) (*Language, error) {
FILE: internal/lint/code/proto.go
function Protobuf (line 10) | func Protobuf() *Language {
FILE: internal/lint/code/py.go
function Python (line 10) | func Python() *Language {
FILE: internal/lint/code/query.go
type QueryEngine (line 10) | type QueryEngine struct
method run (line 29) | func (qe *QueryEngine) run(meta string, q *sitter.Query, source []byte...
function NewQueryEngine (line 16) | func NewQueryEngine(tree *sitter.Tree, lang *Language) *QueryEngine {
FILE: internal/lint/code/rb.go
function Ruby (line 10) | func Ruby() *Language {
FILE: internal/lint/code/rs.go
function Rust (line 10) | func Rust() *Language {
FILE: internal/lint/code/ts.go
function TypeScript (line 10) | func TypeScript() *Language {
FILE: internal/lint/code/tsx.go
function Tsx (line 10) | func Tsx() *Language {
FILE: internal/lint/code/util.go
function toJSON (line 9) | func toJSON(comments []Comment) string {
function cStyle (line 14) | func cStyle(s string) int {
function computePadding (line 18) | func computePadding(s string, makers []string) int {
FILE: internal/lint/code/yaml.go
function YAML (line 10) | func YAML() *Language {
FILE: internal/lint/data.go
method lintData (line 11) | func (l *Linter) lintData(f *core.File) error {
method lintScopedValues (line 31) | func (l *Linter) lintScopedValues(f *core.File, values []core.ScopedValu...
FILE: internal/lint/dita.go
method lintDITA (line 15) | func (l Linter) lintDITA(file *core.File) error {
FILE: internal/lint/fragment.go
function findLine (line 11) | func findLine(s string, line int) string {
function leadingSpaces (line 19) | func leadingSpaces(line string, offset int) int {
function adjustAlerts (line 31) | func adjustAlerts(alerts []core.Alert, last int, comment code.Comment, l...
method lintFragments (line 51) | func (l *Linter) lintFragments(f *core.File) error {
FILE: internal/lint/html.go
method lintHTML (line 20) | func (l *Linter) lintHTML(f *core.File) error {
type extensionConfig (line 27) | type extensionConfig struct
function applyBlockPatterns (line 39) | func applyBlockPatterns(c *core.Config, exts extensionConfig, content st...
function applyInlinePatterns (line 91) | func applyInlinePatterns(c *core.Config, exts extensionConfig, content s...
function applyCommentPatterns (line 128) | func applyCommentPatterns(c *core.Config, exts extensionConfig, content ...
function applyPatterns (line 150) | func applyPatterns(c *core.Config, exts extensionConfig, content string)...
method lintTxtToHTML (line 169) | func (l *Linter) lintTxtToHTML(f *core.File) error {
FILE: internal/lint/html_test.go
function Test_applyPatterns (line 10) | func Test_applyPatterns(t *testing.T) {
function Test_applyPatterns_errors (line 103) | func Test_applyPatterns_errors(t *testing.T) {
FILE: internal/lint/lint.go
type Linter (line 20) | type Linter struct
method Transform (line 56) | func (l *Linter) Transform(f *core.File) (string, error) {
method LintString (line 66) | func (l *Linter) LintString(src string) ([]*core.File, error) {
method SetMetaScope (line 75) | func (l *Linter) SetMetaScope(scope string) {
method Lint (line 84) | func (l *Linter) Lint(input []string, pat string) ([]*core.File, error) {
method lintFiles (line 118) | func (l *Linter) lintFiles(done <-chan core.File, root string) (<-chan...
method lintFile (line 168) | func (l *Linter) lintFile(src string) lintResult {
method lintProse (line 235) | func (l *Linter) lintProse(f *core.File, blk nlp.Block, lines int) err...
method lintTxt (line 258) | func (l *Linter) lintTxt(f *core.File) error {
method lintLines (line 263) | func (l *Linter) lintLines(f *core.File) error {
method lintBlock (line 268) | func (l *Linter) lintBlock(f *core.File, blk nlp.Block, lines, pad int...
method shouldRun (line 293) | func (l *Linter) shouldRun(name string, f *core.File, chk check.Rule, ...
method match (line 340) | func (l *Linter) match(s string) bool {
method skip (line 347) | func (l *Linter) skip(old string) bool {
type lintResult (line 29) | type lintResult struct
function NewLinter (line 35) | func NewLinter(cfg *core.Config) (*Linter, error) {
FILE: internal/lint/lint_test.go
function TestSymlinkFixture (line 15) | func TestSymlinkFixture(t *testing.T) {
function TestGenderBias (line 68) | func TestGenderBias(t *testing.T) {
function initLinter (line 106) | func initLinter() (*Linter, error) {
function benchmarkLint (line 119) | func benchmarkLint(b *testing.B, path string) {
function BenchmarkLintRST (line 140) | func BenchmarkLintRST(b *testing.B) {
function BenchmarkLintMD (line 144) | func BenchmarkLintMD(b *testing.B) {
FILE: internal/lint/md.go
method lintMarkdown (line 36) | func (l Linter) lintMarkdown(f *core.File) error {
function prepMarkdown (line 57) | func prepMarkdown(content string) string {
FILE: internal/lint/mdx.go
method lintMDX (line 10) | func (l Linter) lintMDX(f *core.File) error {
FILE: internal/lint/metadata.go
method lintMetadata (line 15) | func (l Linter) lintMetadata(f *core.File) error {
function extractFrontMatter (line 54) | func extractFrontMatter(file, body string) (string, error) {
FILE: internal/lint/org.go
type ExtendedHTMLWriter (line 22) | type ExtendedHTMLWriter struct
method WriteComment (line 26) | func (w *ExtendedHTMLWriter) WriteComment(n org.Comment) {
method lintOrg (line 32) | func (l Linter) lintOrg(f *core.File) error {
FILE: internal/lint/rst.go
method lintRST (line 33) | func (l *Linter) lintRST(f *core.File) error {
function callRst (line 66) | func callRst(text, lib, _ string) (string, error) {
FILE: internal/lint/util.go
function findBestLineBySubstring (line 12) | func findBestLineBySubstring(s, sub string) (int, string) {
function findLineBySubstring (line 38) | func findLineBySubstring(s, sub string, seen map[string]int) (int, strin...
function adjustPos (line 74) | func adjustPos(alerts []core.Alert, last, line, padding int, v, rv strin...
FILE: internal/lint/walk.go
type walker (line 16) | type walker struct
method sub (line 51) | func (w *walker) sub(sub string, char rune) bool {
method update (line 57) | func (w *walker) update(txt string, tokt html.TokenType) {
method reset (line 71) | func (w *walker) reset() {
method getCtx (line 79) | func (w *walker) getCtx() string {
method append (line 83) | func (w *walker) append(text string) {
method addTag (line 93) | func (w *walker) addTag(tag string) {
method setCls (line 98) | func (w *walker) setCls(tag string, cls bool) {
method addCls (line 106) | func (w *walker) addCls(tag string, start bool) {
method canClose (line 116) | func (w *walker) canClose() bool {
method close (line 120) | func (w *walker) close() {
method block (line 126) | func (w *walker) block(text, scope string) nlp.Block {
method walk (line 137) | func (w *walker) walk() (html.TokenType, html.Token, string) {
method replaceToks (line 143) | func (w *walker) replaceToks(tok html.Token) {
method advance (line 168) | func (w *walker) advance(text string) int {
method lastTag (line 189) | func (w *walker) lastTag() string {
method isNestedList (line 213) | func (w *walker) isNestedList() bool {
function newWalker (line 42) | func newWalker(f *core.File, raw []byte, offset int) *walker {
function string2ByteSlice (line 224) | func string2ByteSlice(str string) []byte {
function byteSlice2String (line 231) | func byteSlice2String(bs []byte) string {
function subInplace (line 238) | func subInplace(ctx, sub string, char rune) (string, bool) {
FILE: internal/lint/xml.go
method lintXML (line 23) | func (l Linter) lintXML(file *core.File) error {
FILE: internal/lint/xml_test.go
function TestXSLTArgsNotPolluted (line 7) | func TestXSLTArgsNotPolluted(t *testing.T) {
FILE: internal/nlp/http.go
type SegmentResult (line 12) | type SegmentResult struct
type TagResult (line 16) | type TagResult struct
function post (line 20) | func post(url string) ([]byte, error) {
function doSegment (line 37) | func doSegment(text, lang, apiURL string) (SegmentResult, error) {
function pos (line 56) | func pos(text, lang, apiURL string) (TagResult, error) {
FILE: internal/nlp/prose.go
type TaggedWord (line 12) | type TaggedWord struct
function doTag (line 31) | func doTag(words []string) []tag.Token {
function textToWords (line 39) | func textToWords(text string, nlp bool) []string {
function TextToTokens (line 56) | func TextToTokens(text string, nlp *Info) []tag.Token {
FILE: internal/nlp/provider.go
type segmenter (line 7) | type segmenter
type Block (line 10) | type Block struct
function NewBlock (line 19) | func NewBlock(ctx, txt, sel string) Block {
function NewBlockWithParent (line 24) | func NewBlockWithParent(ctx, txt, sel, parent string) Block {
function NewLinedBlock (line 38) | func NewLinedBlock(ctx, txt, sel string, line int) Block {
type Info (line 55) | type Info struct
method Compute (line 70) | func (n *Info) Compute(block *Block) ([]Block, error) {
method doNLP (line 86) | func (n *Info) doNLP(blk *Block, seg segmenter) ([]Block, error) {
FILE: internal/nlp/tokenize.go
type TokenTester (line 10) | type TokenTester
type Tokenizer (line 12) | type Tokenizer interface
type IterTokenizer (line 17) | type IterTokenizer struct
method isSpecial (line 113) | func (t *IterTokenizer) isSpecial(token string) bool {
method doSplit (line 118) | func (t *IterTokenizer) doSplit(token string) []string {
method Tokenize (line 154) | func (t *IterTokenizer) Tokenize(text string) []string {
type TokenizerOptFunc (line 28) | type TokenizerOptFunc
function UsingIsUnsplittable (line 31) | func UsingIsUnsplittable(x TokenTester) TokenizerOptFunc {
function UsingSpecialRE (line 38) | func UsingSpecialRE(x *regexp.Regexp) TokenizerOptFunc {
function UsingSanitizer (line 45) | func UsingSanitizer(x *strings.Replacer) TokenizerOptFunc {
function UsingSuffixes (line 52) | func UsingSuffixes(x []string) TokenizerOptFunc {
function UsingPrefixes (line 59) | func UsingPrefixes(x []string) TokenizerOptFunc {
function UsingEmoticons (line 66) | func UsingEmoticons(x map[string]int) TokenizerOptFunc {
function UsingContractions (line 73) | func UsingContractions(x []string) TokenizerOptFunc {
function UsingSplitCases (line 80) | func UsingSplitCases(x []string) TokenizerOptFunc {
function NewIterTokenizer (line 87) | func NewIterTokenizer(opts ...TokenizerOptFunc) *IterTokenizer {
function addToken (line 106) | func addToken(s string, toks []string) []string {
FILE: internal/nlp/tokenize_test.go
type Word2Tok (line 7) | type Word2Tok struct
function TestToks (line 31) | func TestToks(t *testing.T) {
FILE: internal/nlp/util.go
function StrLen (line 10) | func StrLen(s string) int {
function hasAnyPrefix (line 14) | func hasAnyPrefix(s string, prefixes []string) bool {
function hasAnySuffix (line 24) | func hasAnySuffix(s string, suffixes []string) bool {
function hasAnyIndex (line 34) | func hasAnyIndex(s string, suffixes []string) int {
function allNonLetter (line 45) | func allNonLetter(s string) bool {
FILE: internal/spell/aff.go
type affixType (line 13) | type affixType
constant Prefix (line 17) | Prefix affixType = iota
constant Suffix (line 18) | Suffix
type affix (line 22) | type affix struct
method expand (line 29) | func (a affix) expand(word string, out []string) []string {
type rule (line 49) | type rule struct
type dictConfig (line 57) | type dictConfig struct
method parseFlags (line 79) | func (a dictConfig) parseFlags(flagStr string) []string {
method expand (line 101) | func (a dictConfig) expand(wordAffix string, out []string) ([]string, ...
function isCrossProduct (line 175) | func isCrossProduct(val string) (bool, error) {
function newDictConfig (line 186) | func newDictConfig(file io.Reader) (*dictConfig, error) { //nolint:funlen
FILE: internal/spell/aff_test.go
function TestParseFlagsASCII (line 8) | func TestParseFlagsASCII(t *testing.T) {
function TestParseFlagsNum (line 16) | func TestParseFlagsNum(t *testing.T) {
function TestParseFlagsLong (line 24) | func TestParseFlagsLong(t *testing.T) {
function TestParseFlagsUTF8 (line 32) | func TestParseFlagsUTF8(t *testing.T) {
function TestFlagNumAffixParsing (line 40) | func TestFlagNumAffixParsing(t *testing.T) {
function TestFlagNumExpand (line 79) | func TestFlagNumExpand(t *testing.T) {
function TestFlagNumGoSpellReader (line 112) | func TestFlagNumGoSpellReader(t *testing.T) {
function TestASCIFlagBackwardCompatibility (line 156) | func TestASCIFlagBackwardCompatibility(t *testing.T) {
FILE: internal/spell/gospell.go
type wordMatch (line 16) | type wordMatch struct
type goSpell (line 21) | type goSpell struct
method inputConversion (line 37) | func (s *goSpell) inputConversion(raw []byte) string {
method addWordRaw (line 48) | func (s *goSpell) addWordRaw(word string) bool {
method addWordListFile (line 59) | func (s *goSpell) addWordListFile(name string) ([]string, error) {
method addWordList (line 75) | func (s *goSpell) addWordList(r io.Reader) ([]string, error) {
method keys (line 93) | func (s *goSpell) keys() []string {
method suggest (line 105) | func (s *goSpell) suggest(word string) []wordMatch {
method spell (line 130) | func (s *goSpell) spell(word string) bool {
type dictionary (line 29) | type dictionary struct
function newGoSpellReader (line 176) | func newGoSpellReader(aff, dic io.Reader) (*goSpell, error) {
function newGoSpell (line 251) | func newGoSpell(affFile, dicFile string) (*goSpell, error) {
FILE: internal/spell/multi.go
type Options (line 28) | type Options struct
type CheckerOption (line 38) | type CheckerOption
function WithPath (line 41) | func WithPath(path string) CheckerOption {
function WithDefault (line 48) | func WithDefault(load bool) CheckerOption {
function WithDefaultPath (line 56) | func WithDefaultPath(path string) CheckerOption {
function UsingDictionary (line 63) | func UsingDictionary(name string) CheckerOption {
function UsingDictionaryByPath (line 71) | func UsingDictionaryByPath(dic, aff string) CheckerOption {
type Checker (line 78) | type Checker struct
method Spell (line 139) | func (m *Checker) Spell(word string) bool {
method Suggest (line 149) | func (m *Checker) Suggest(word string) []string {
method Dict (line 171) | func (m *Checker) Dict(i int) map[string]struct{} {
method Convert (line 176) | func (m *Checker) Convert(s string) string {
method AddWordListFile (line 184) | func (m *Checker) AddWordListFile(name string) error {
method readAsset (line 194) | func (m *Checker) readAsset(name string) (string, error) {
method loadDic (line 222) | func (m *Checker) loadDic(name string) error {
function NewChecker (line 85) | func NewChecker(options ...CheckerOption) (*Checker, error) {
FILE: internal/spell/words.go
type splitter (line 25) | type splitter struct
function newSplitter (line 34) | func newSplitter(chars string) *splitter {
function isNumber (line 43) | func isNumber(s string) bool {
function isNumberBinary (line 47) | func isNumberBinary(s string) bool {
function isNumberUnits (line 55) | func isNumberUnits(s string) string {
function isNumberHex (line 70) | func isNumberHex(s string) bool {
function isHash (line 74) | func isHash(s string) bool {
FILE: internal/system/cmd.go
function ExecuteWithInput (line 11) | func ExecuteWithInput(exe, text string, args ...string) (string, error) {
FILE: internal/system/dir.go
function Mkdir (line 6) | func Mkdir(dir string) error {
function IsDir (line 11) | func IsDir(filename string) bool {
FILE: internal/system/file.go
function FileExists (line 10) | func FileExists(filename string) bool {
function FileNameWithoutExt (line 16) | func FileNameWithoutExt(fileName string) string {
function ReplaceFileExt (line 26) | func ReplaceFileExt(fp string, formats map[string]string) string {
FILE: internal/system/path.go
function AbsPath (line 11) | func AbsPath(path string) string {
function Which (line 23) | func Which(cmds []string) string {
function NormalizePath (line 34) | func NormalizePath(path string) string {
function DeterminePath (line 57) | func DeterminePath(configPath string, keyPath string) string {
FILE: internal/system/path_test.go
function TestDeterminePath (line 8) | func TestDeterminePath(t *testing.T) {
FILE: internal/system/system.go
function Name (line 10) | func Name() string {
function IsMac (line 15) | func IsMac() bool {
function IsWindows (line 20) | func IsWindows() bool {
function IsLinux (line 25) | func IsLinux() bool {
function IsUnix (line 30) | func IsUnix() bool {
function PlatformAndArch (line 35) | func PlatformAndArch() string {
FILE: internal/system/walk.go
function walk (line 8) | func walk(filename string, linkDirname string, walkFn filepath.WalkFunc)...
function Walk (line 36) | func Walk(path string, walkFn filepath.WalkFunc) error {
FILE: internal/system/zip.go
function Unarchive (line 13) | func Unarchive(src, dest string) error {
FILE: testdata/comments/in/0.go
function Println (line 17) | func Println(a ...interface{}) (n int, err error) {
function main (line 21) | func main() { // foo bar
FILE: testdata/comments/in/1.rs
type Person (line 16) | pub struct Person {
method new (line 36) | pub fn new(name: &str) -> Person {
method hello (line 45) | pub fn hello(&self) {
function main (line 50) | fn main() {
FILE: testdata/comments/in/2.py
function FIXME (line 4) | def FIXME():
function NOTE (line 35) | def NOTE():
function foo (line 44) | def foo(self):
FILE: testdata/comments/in/3.cpp
function main (line 40) | int main() {
FILE: testdata/comments/in/4.js
function getValues (line 15) | function getValues(n, t) {
function getCases (line 35) | function getCases(variables) {
function statementToTable (line 58) | function statementToTable(s) {
function tableToMarkdown (line 78) | function tableToMarkdown(table) {
function makeTruthTable (line 102) | function makeTruthTable(s, type) {
FILE: testdata/fixtures/formats/subdir1/test.rs
function add_one (line 10) | fn add_one(x: i32) -> i32 {
FILE: testdata/fixtures/formats/test.cc
type foo (line 6) | namespace foo {
function Bar (line 8) | Bar Baz(const std::string& color_string) {
FILE: testdata/fixtures/formats/test.java
class HelloWorld (line 11) | public class HelloWorld {
method main (line 12) | public static void main(String[] args) {
FILE: testdata/fixtures/formats/test.jsx
function formatName (line 3) | function formatName(user) {
FILE: testdata/fixtures/formats/test.py
function FIXME (line 3) | def FIXME():
function NOTE (line 26) | def NOTE():
function foo (line 34) | def foo(self):
FILE: testdata/fixtures/formats/test.rs
function add_one (line 10) | fn add_one(x: i32) -> i32 {
FILE: testdata/fixtures/formats/test.ts
type User (line 2) | interface User {
class UserAccount (line 7) | class UserAccount {
method constructor (line 11) | constructor(name: string, id: number) {
constant XXX (line 18) | const XXX: User = new UserAccount("Murphy", 1);
FILE: testdata/fixtures/fragments/test.cc
function main (line 40) | int main() {
FILE: testdata/fixtures/fragments/test.go
function Println (line 18) | func Println(a ...interface{}) (n int, err error) {
function main (line 22) | func main() {
FILE: testdata/fixtures/fragments/test.js
function getValues (line 15) | function getValues(n, t) {
function getCases (line 35) | function getCases(variables) {
function statementToTable (line 58) | function statementToTable(s) {
function tableToMarkdown (line 78) | function tableToMarkdown(table) {
function makeTruthTable (line 102) | function makeTruthTable(s, type) {
FILE: testdata/fixtures/fragments/test.py
function my_function (line 6) | def my_function(my_arg, my_other_arg):
function _var_docstrings (line 12) | def _var_docstrings(self) -> dict[str, str]:
class Doc (line 17) | class Doc():
method __init__ (line 19) | def __init__(self) -> None:
FILE: testdata/fixtures/fragments/test.rs
type Person (line 6) | pub struct Person {
method new (line 26) | pub fn new(name: &str) -> Person {
method hello (line 35) | pub fn hello(&self) {
function main (line 40) | fn main() {
FILE: testdata/fixtures/fragments/test2.py
function _include_fullname_in_traceback (line 55) | def _include_fullname_in_traceback(f):
class Doc (line 79) | class Doc(Generic[T]):
method __init__ (line 120) | def __init__(
method fullname (line 135) | def fullname(self) -> str:
method name (line 143) | def name(self) -> str:
method docstring (line 151) | def docstring(self) -> str:
method source (line 161) | def source(self) -> str:
method source_file (line 171) | def source_file(self) -> Path | None:
method source_lines (line 185) | def source_lines(self) -> tuple[int, int] | None:
method is_inherited (line 197) | def is_inherited(self) -> bool:
method type (line 209) | def type(cls) -> str:
method type (line 219) | def type(self) -> str: # noqa
method __lt__ (line 222) | def __lt__(self, other):
class Namespace (line 229) | class Namespace(Doc[T], metaclass=ABCMeta):
method _member_objects (line 237) | def _member_objects(self) -> dict[str, Any]:
method _var_docstrings (line 245) | def _var_docstrings(self) -> dict[str, str]:
method _var_annotations (line 250) | def _var_annotations(self) -> dict[str, Any]:
method _taken_from (line 256) | def _taken_from(self, member_name: str, obj: Any) -> tuple[str, str]:
FILE: testdata/fixtures/fragments/test2.rs
constant ABOUT (line 15) | const ABOUT: &str = "
constant USAGE (line 23) | const USAGE: &str = "
constant TEMPLATE (line 33) | const TEMPLATE: &str = "\
function app (line 44) | pub fn app() -> App<'static, 'static> {
function long_version (line 78) | pub fn long_version(revision_hash: Option<&str>, cpu: bool) -> String {
function compile_cpu_features (line 109) | fn compile_cpu_features() -> Vec<&'static str> {
function runtime_cpu_features (line 126) | fn runtime_cpu_features() -> Vec<&'static str> {
function runtime_cpu_features (line 146) | fn runtime_cpu_features() -> Vec<&'static str> {
type Arg (line 152) | type Arg = clap::Arg<'static, 'static>;
type RGArg (line 162) | pub struct RGArg {
method positional (line 289) | fn positional(name: &'static str, value_name: &'static str) -> RGArg {
method switch (line 311) | fn switch(long_name: &'static str) -> RGArg {
method flag (line 337) | fn flag(long_name: &'static str, value_name: &'static str) -> RGArg {
method short (line 362) | fn short(mut self, name: &'static str) -> RGArg {
method help (line 379) | fn help(mut self, text: &'static str) -> RGArg {
method long_help (line 389) | fn long_help(mut self, text: &'static str) -> RGArg {
method multiple (line 408) | fn multiple(mut self) -> RGArg {
method hidden (line 433) | fn hidden(mut self) -> RGArg {
method possible_values (line 448) | fn possible_values(mut self, values: &[&'static str]) -> RGArg {
method alias (line 469) | fn alias(mut self, name: &'static str) -> RGArg {
method allow_leading_hyphen (line 477) | fn allow_leading_hyphen(mut self) -> RGArg {
method required_unless (line 490) | fn required_unless(mut self, names: &[&'static str]) -> RGArg {
method conflicts (line 498) | fn conflicts(mut self, names: &[&'static str]) -> RGArg {
method overrides (line 507) | fn overrides(mut self, name: &'static str) -> RGArg {
method default_value (line 514) | fn default_value(mut self, value: &'static str) -> RGArg {
method default_value_if (line 521) | fn default_value_if(mut self, value: &'static str, arg_name: &'static ...
method number (line 528) | fn number(mut self) -> RGArg {
type RGArgKind (line 210) | pub enum RGArgKind {
function all_args_and_flags (line 547) | pub fn all_args_and_flags() -> Vec<RGArg> {
function arg_pattern (line 655) | fn arg_pattern(args: &mut Vec<RGArg>) {
function arg_path (line 675) | fn arg_path(args: &mut Vec<RGArg>) {
function flag_after_context (line 690) | fn flag_after_context(args: &mut Vec<RGArg>) {
function flag_auto_hybrid_regex (line 708) | fn flag_auto_hybrid_regex(args: &mut Vec<RGArg>) {
function flag_before_context (line 754) | fn flag_before_context(args: &mut Vec<RGArg>) {
function flag_binary (line 772) | fn flag_binary(args: &mut Vec<RGArg>) {
function flag_block_buffered (line 818) | fn flag_block_buffered(args: &mut Vec<RGArg>) {
function flag_byte_offset (line 850) | fn flag_byte_offset(args: &mut Vec<RGArg>) {
function flag_case_sensitive (line 871) | fn flag_case_sensitive(args: &mut Vec<RGArg>) {
function flag_color (line 888) | fn flag_color(args: &mut Vec<RGArg>) {
function flag_colors (line 916) | fn flag_colors(args: &mut Vec<RGArg>) {
function flag_column (line 952) | fn flag_column(args: &mut Vec<RGArg>) {
function flag_context (line 972) | fn flag_context(args: &mut Vec<RGArg>) {
function flag_context_separator (line 993) | fn flag_context_separator(args: &mut Vec<RGArg>) {
function flag_count (line 1018) | fn flag_count(args: &mut Vec<RGArg>) {
function flag_count_matches (line 1043) | fn flag_count_matches(args: &mut Vec<RGArg>) {
function flag_crlf (line 1066) | fn flag_crlf(args: &mut Vec<RGArg>) {
function flag_debug (line 1091) | fn flag_debug(args: &mut Vec<RGArg>) {
function flag_dfa_size_limit (line 1111) | fn flag_dfa_size_limit(args: &mut Vec<RGArg>) {
function flag_encoding (line 1128) | fn flag_encoding(args: &mut Vec<RGArg>) {
function flag_engine (line 1155) | fn flag_engine(args: &mut Vec<RGArg>) {
function flag_field_context_separator (line 1186) | fn flag_field_context_separator(args: &mut Vec<RGArg>) {
function flag_field_match_separator (line 1202) | fn flag_field_match_separator(args: &mut Vec<RGArg>) {
function flag_file (line 1218) | fn flag_file(args: &mut Vec<RGArg>) {
function flag_files (line 1238) | fn flag_files(args: &mut Vec<RGArg>) {
function flag_files_with_matches (line 1255) | fn flag_files_with_matches(args: &mut Vec<RGArg>) {
function flag_files_without_match (line 1271) | fn flag_files_without_match(args: &mut Vec<RGArg>) {
function flag_fixed_strings (line 1287) | fn flag_fixed_strings(args: &mut Vec<RGArg>) {
function flag_follow (line 1310) | fn flag_follow(args: &mut Vec<RGArg>) {
function flag_glob (line 1331) | fn flag_glob(args: &mut Vec<RGArg>) {
function flag_glob_case_insensitive (line 1361) | fn flag_glob_case_insensitive(args: &mut Vec<RGArg>) {
function flag_heading (line 1382) | fn flag_heading(args: &mut Vec<RGArg>) {
function flag_hidden (line 1414) | fn flag_hidden(args: &mut Vec<RGArg>) {
function flag_iglob (line 1438) | fn flag_iglob(args: &mut Vec<RGArg>) {
function flag_ignore_case (line 1456) | fn flag_ignore_case(args: &mut Vec<RGArg>) {
function flag_ignore_file (line 1475) | fn flag_ignore_file(args: &mut Vec<RGArg>) {
function flag_ignore_file_case_insensitive (line 1497) | fn flag_ignore_file_case_insensitive(args: &mut Vec<RGArg>) {
function flag_include_zero (line 1519) | fn flag_include_zero(args: &mut Vec<RGArg>) {
function flag_invert_match (line 1532) | fn flag_invert_match(args: &mut Vec<RGArg>) {
function flag_json (line 1546) | fn flag_json(args: &mut Vec<RGArg>) {
function flag_line_buffered (line 1599) | fn flag_line_buffered(args: &mut Vec<RGArg>) {
function flag_line_number (line 1632) | fn flag_line_number(args: &mut Vec<RGArg>) {
function flag_line_regexp (line 1662) | fn flag_line_regexp(args: &mut Vec<RGArg>) {
function flag_max_columns (line 1680) | fn flag_max_columns(args: &mut Vec<RGArg>) {
function flag_max_columns_preview (line 1697) | fn flag_max_columns_preview(args: &mut Vec<RGArg>) {
function flag_max_count (line 1722) | fn flag_max_count(args: &mut Vec<RGArg>) {
function flag_max_depth (line 1737) | fn flag_max_depth(args: &mut Vec<RGArg>) {
function flag_max_filesize (line 1756) | fn flag_max_filesize(args: &mut Vec<RGArg>) {
function flag_mmap (line 1773) | fn flag_mmap(args: &mut Vec<RGArg>) {
function flag_multiline (line 1807) | fn flag_multiline(args: &mut Vec<RGArg>) {
function flag_multiline_dotall (line 1856) | fn flag_multiline_dotall(args: &mut Vec<RGArg>) {
function flag_no_config (line 1887) | fn flag_no_config(args: &mut Vec<RGArg>) {
function flag_no_ignore (line 1901) | fn flag_no_ignore(args: &mut Vec<RGArg>) {
function flag_no_ignore_dot (line 1926) | fn flag_no_ignore_dot(args: &mut Vec<RGArg>) {
function flag_no_ignore_exclude (line 1948) | fn flag_no_ignore_exclude(args: &mut Vec<RGArg>) {
function flag_no_ignore_files (line 1969) | fn flag_no_ignore_files(args: &mut Vec<RGArg>) {
function flag_no_ignore_global (line 1990) | fn flag_no_ignore_global(args: &mut Vec<RGArg>) {
function flag_no_ignore_messages (line 2012) | fn flag_no_ignore_messages(args: &mut Vec<RGArg>) {
function flag_no_ignore_parent (line 2033) | fn flag_no_ignore_parent(args: &mut Vec<RGArg>) {
function flag_no_ignore_vcs (line 2053) | fn flag_no_ignore_vcs(args: &mut Vec<RGArg>) {
function flag_no_messages (line 2075) | fn flag_no_messages(args: &mut Vec<RGArg>) {
function flag_no_pcre2_unicode (line 2094) | fn flag_no_pcre2_unicode(args: &mut Vec<RGArg>) {
function flag_no_require_git (line 2117) | fn flag_no_require_git(args: &mut Vec<RGArg>) {
function flag_no_unicode (line 2141) | fn flag_no_unicode(args: &mut Vec<RGArg>) {
function flag_null (line 2188) | fn flag_null(args: &mut Vec<RGArg>) {
function flag_null_data (line 2202) | fn flag_null_data(args: &mut Vec<RGArg>) {
function flag_one_file_system (line 2225) | fn flag_one_file_system(args: &mut Vec<RGArg>) {
function flag_only_matching (line 2251) | fn flag_only_matching(args: &mut Vec<RGArg>) {
function flag_path_separator (line 2266) | fn flag_path_separator(args: &mut Vec<RGArg>) {
function flag_passthru (line 2282) | fn flag_passthru(args: &mut Vec<RGArg>) {
function flag_pcre2 (line 2305) | fn flag_pcre2(args: &mut Vec<RGArg>) {
function flag_pcre2_version (line 2344) | fn flag_pcre2_version(args: &mut Vec<RGArg>) {
function flag_pre (line 2357) | fn flag_pre(args: &mut Vec<RGArg>) {
function flag_pre_glob (line 2410) | fn flag_pre_glob(args: &mut Vec<RGArg>) {
function flag_pretty (line 2439) | fn flag_pretty(args: &mut Vec<RGArg>) {
function flag_quiet (line 2455) | fn flag_quiet(args: &mut Vec<RGArg>) {
function flag_regex_size_limit (line 2473) | fn flag_regex_size_limit(args: &mut Vec<RGArg>) {
function flag_regexp (line 2488) | fn flag_regexp(args: &mut Vec<RGArg>) {
function flag_replace (line 2512) | fn flag_replace(args: &mut Vec<RGArg>) {
function flag_search_zip (line 2539) | fn flag_search_zip(args: &mut Vec<RGArg>) {
function flag_smart_case (line 2563) | fn flag_smart_case(args: &mut Vec<RGArg>) {
function flag_sort_files (line 2587) | fn flag_sort_files(args: &mut Vec<RGArg>) {
function flag_sort (line 2614) | fn flag_sort(args: &mut Vec<RGArg>) {
function flag_sortr (line 2644) | fn flag_sortr(args: &mut Vec<RGArg>) {
function flag_stats (line 2674) | fn flag_stats(args: &mut Vec<RGArg>) {
function flag_text (line 2698) | fn flag_text(args: &mut Vec<RGArg>) {
function flag_threads (line 2731) | fn flag_threads(args: &mut Vec<RGArg>) {
function flag_trim (line 2746) | fn flag_trim(args: &mut Vec<RGArg>) {
function flag_type (line 2765) | fn flag_type(args: &mut Vec<RGArg>) {
function flag_type_add (line 2786) | fn flag_type_add(args: &mut Vec<RGArg>) {
function flag_type_clear (line 2817) | fn flag_type_clear(args: &mut Vec<RGArg>) {
function flag_type_not (line 2834) | fn flag_type_not(args: &mut Vec<RGArg>) {
function flag_type_list (line 2850) | fn flag_type_list(args: &mut Vec<RGArg>) {
function flag_unrestricted (line 2866) | fn flag_unrestricted(args: &mut Vec<RGArg>) {
function flag_vimgrep (line 2885) | fn flag_vimgrep(args: &mut Vec<RGArg>) {
function flag_with_filename (line 2898) | fn flag_with_filename(args: &mut Vec<RGArg>) {
function flag_word_regexp (line 2932) | fn flag_word_regexp(args: &mut Vec<RGArg>) {
FILE: testdata/fixtures/views/test.java
class HelloWorld (line 11) | public class HelloWorld {
method main (line 12) | public static void main(String[] args) {
FILE: testdata/fixtures/views/test.py
function FIXME (line 3) | def FIXME():
function NOTE (line 24) | def NOTE():
function foo (line 32) | def foo(self):
Copy disabled (too large)
Download .json
Condensed preview — 889 files, each showing path, character count, and a content snippet. Download the .json file for the full structured content (10,868K chars).
[
{
"path": ".gitattributes",
"chars": 146,
"preview": "CRLF.md text eol=crlf\nCR.md text eol=cr\n\nfixtures/** linguist-vendored=true\nfeatures/** linguist-vendored=true\ntestdata/"
},
{
"path": ".github/CODE_OF_CONDUCT.md",
"chars": 5216,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
},
{
"path": ".github/CONTRIBUTING.md",
"chars": 5826,
"preview": "# Contributing to Vale\n\nInterested in contributing to Vale? Great—we welcome contributions of any kind including d"
},
{
"path": ".github/ISSUE_TEMPLATE/0_feature_request.yml",
"chars": 635,
"preview": "name: Feature Request\ndescription: \"Request new functionality or improvements to existing functionality.\"\nlabels: [\"Type"
},
{
"path": ".github/ISSUE_TEMPLATE/1_bug_report.yml",
"chars": 1067,
"preview": "name: Bug Report\ndescription: \"Report a bug, crash, or unexpected behavior.\"\nlabels: [\"Type: Bug\"]\nbody:\n - type: check"
},
{
"path": ".github/workflows/codeql.yml",
"chars": 2307,
"preview": "name: \"CodeQL\"\n\non:\n push:\n branches: [ 'v2' ]\n pull_request:\n # The branches below must be a subset of the bran"
},
{
"path": ".github/workflows/golangci-lint.yml",
"chars": 511,
"preview": "name: golangci-lint\non:\n push:\n branches:\n - v3\n pull_request:\n\npermissions:\n contents: read\n # Optional: al"
},
{
"path": ".github/workflows/main.yml",
"chars": 940,
"preview": "name: Build + Lint\n\non: push\n\njobs:\n goreleaser:\n runs-on: ubuntu-latest\n steps:\n - name: Checkout\n u"
},
{
"path": ".gitignore",
"chars": 478,
"preview": "# Folders\ndocs/public*\n_obj\n_test\nvendor/bundle/\nbuilds\nbin\ntmp\ntags\nsite\ntarget\ndist\n_vendor-*\ndocs/public/\nfixtures/fo"
},
{
"path": ".gitlab-ci.yml",
"chars": 686,
"preview": "image: golang:1.25\n\nstages:\n - test\n - release\n\ntest:\n stage: test\n script:\n - export BUNDLE_GEMFILE=$PWD/testdat"
},
{
"path": ".golangci.yml",
"chars": 2316,
"preview": "version: \"2\"\nrun:\n go: \"1.25\"\nlinters:\n default: none\n enable:\n - asciicheck\n - bidichk\n - bodyclose\n - c"
},
{
"path": ".goreleaser.yml",
"chars": 4789,
"preview": "env:\n - CGO_ENABLED=1\n\nbuilds:\n - id: vale-darwin-amd64\n main: ./cmd/vale\n binary: vale\n goarch:\n - amd6"
},
{
"path": ".pre-commit-hooks.yaml",
"chars": 110,
"preview": "- id: vale\n name: vale\n description: Run vale on your text\n entry: vale\n language: golang\n types: [text]\n"
},
{
"path": ".vale.ini",
"chars": 138,
"preview": "StylesPath = .github/styles\nMinAlertLevel = suggestion\n\nVocab = Vale\n\nPackages = Microsoft, Readability\n\n[README.md]\nBas"
},
{
"path": ".well-known/funding-manifest-urls",
"chars": 29,
"preview": "https://vale.sh/funding.json\n"
},
{
"path": "Dockerfile",
"chars": 755,
"preview": "# syntax=docker/dockerfile:1\nARG GOLANG_VER=1.25\nFROM golang:${GOLANG_VER}-alpine AS build\n\n# See https://cloud.docker.c"
},
{
"path": "LICENSE",
"chars": 1068,
"preview": "MIT License\n\nCopyright (c) 2016 Joseph Kato\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "Makefile",
"chars": 2501,
"preview": "PACKAGE_NAME := github.com/errata-ai/vale/v3\nGOLANG_CROSS_VERSION ?= v0.2.0\n\nSYSROOT_DIR ?= sysroots\nSYSRO"
},
{
"path": "README.md",
"chars": 8608,
"preview": "# Vale: Your style, our editor []("
},
{
"path": "appveyor.yml",
"chars": 1158,
"preview": "version: \"{build}\"\nimage: Visual Studio 2019\nclone_folder: c:\\GOPATH\\src\\github.com\\vale-cli\\vale\nenvironment:\n GOPATH:"
},
{
"path": "cmd/vale/api.go",
"chars": 2657,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/spf13/pflag\"\n\n\t\"github.com/errata-a"
},
{
"path": "cmd/vale/color.go",
"chars": 2079,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/olekukonko/tablewriter/tw\"\n\t\"github.com/pterm/pterm\"\n\n\t\"git"
},
{
"path": "cmd/vale/command.go",
"chars": 8346,
"preview": "package main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/jdkato/twine/nlp/tag\"\n\t\"github.com/pterm/pt"
},
{
"path": "cmd/vale/common.go",
"chars": 575,
"preview": "package main\n\nimport (\n\t\"sort\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n)\n\n// PrintAlerts prints the given alerts "
},
{
"path": "cmd/vale/custom.go",
"chars": 1409,
"preview": "package main\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"text/template\"\n\n\t\"github.com/Masterminds/sprig/v3\"\n\n\t\"github.com/errata-"
},
{
"path": "cmd/vale/error.go",
"chars": 2001,
"preview": "package main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/errata-ai/vale/"
},
{
"path": "cmd/vale/flag.go",
"chars": 2066,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/pflag\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n)\n\n// Flags are "
},
{
"path": "cmd/vale/funcs.go",
"chars": 1452,
"preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"text/template\"\n\n\t\"github.com/olekukonko/tablewriter\"\n\t\"gith"
},
{
"path": "cmd/vale/info.go",
"chars": 2308,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"slices\"\n\n\t\"github.com/olekukonko/tablewriter/tw\"\n\t\"github.com/pterm/pterm\"\n\t\"githu"
},
{
"path": "cmd/vale/json.go",
"chars": 487,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n)\n\n// PrintJSONAlerts prints Alerts in map["
},
{
"path": "cmd/vale/library.go",
"chars": 581,
"preview": "package main\n\nimport \"encoding/json\"\n\nvar library = \"https://raw.githubusercontent.com/errata-ai/styles/master/library.j"
},
{
"path": "cmd/vale/line.go",
"chars": 1061,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n)\n\n// Pri"
},
{
"path": "cmd/vale/main.go",
"chars": 2513,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/spf13/pflag\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"gi"
},
{
"path": "cmd/vale/main_unix.go",
"chars": 174,
"preview": "//go:build !windows\n// +build !windows\n\npackage main\n\nfunc unsetManifestRegistry(_ string) error {\n\treturn nil\n}\n\nfunc s"
},
{
"path": "cmd/vale/main_windows.go",
"chars": 1233,
"preview": "//go:build windows\n// +build windows\n\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"golang.org/x/sys/windows/registry\"\n)\n\nfunc unsetM"
},
{
"path": "cmd/vale/native.go",
"chars": 9970,
"preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/adrg"
},
{
"path": "cmd/vale/pkg_test.go",
"chars": 4449,
"preview": "package main\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"git"
},
{
"path": "cmd/vale/sync.go",
"chars": 4090,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\tcp \"github.com/otiai10/copy\"\n\n\t\"github.com/errata-ai/v"
},
{
"path": "cmd/vale/util.go",
"chars": 1287,
"preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/pterm/pterm\"\n\n\t\"github.com/errata-ai/vale"
},
{
"path": "go.mod",
"chars": 2676,
"preview": "module github.com/errata-ai/vale/v3\n\ngo 1.25.7\n\nrequire (\n\tgithub.com/Masterminds/sprig/v3 v3.3.0\n\tgithub.com/adrg/front"
},
{
"path": "go.sum",
"chars": 16513,
"preview": "dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=\ndario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobSt"
},
{
"path": "internal/check/action.go",
"chars": 5056,
"preview": "package check\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gith"
},
{
"path": "internal/check/capitalization.go",
"chars": 4432,
"preview": "package check\n\nimport (\n\t\"github.com/errata-ai/regexp2\"\n\t\"github.com/jdkato/twine/strcase\"\n\n\t\"github.com/errata-ai/vale/"
},
{
"path": "internal/check/conditional.go",
"chars": 3101,
"preview": "package check\n\nimport (\n\t\"github.com/errata-ai/regexp2\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/erra"
},
{
"path": "internal/check/consistency.go",
"chars": 2916,
"preview": "package check\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/errata-ai/regexp2\"\n\t\"github.com/mitchellh/mapstructure\"\n\n\t\"githu"
},
{
"path": "internal/check/definition.go",
"chars": 10017,
"preview": "package check\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/errata-ai/regexp2\"\n\t\"github.com/mit"
},
{
"path": "internal/check/doc.go",
"chars": 67,
"preview": "// Package check implements Vale's extension points.\npackage check\n"
},
{
"path": "internal/check/existence.go",
"chars": 2618,
"preview": "package check\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/errata-ai/regexp2\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core"
},
{
"path": "internal/check/existence_test.go",
"chars": 1359,
"preview": "package check\n\nimport (\n\t\"testing\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/errata-ai/vale/v3/interna"
},
{
"path": "internal/check/filter.go",
"chars": 2956,
"preview": "package check\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/expr-lang/expr\"\n\n\t\"github.com/errata-ai/vale/v3/internal/c"
},
{
"path": "internal/check/manager.go",
"chars": 7636,
"preview": "package check\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"golang.org/x/exp/maps\"\n\n\t\"github."
},
{
"path": "internal/check/manager_test.go",
"chars": 1177,
"preview": "package check\n\nimport (\n\t\"testing\"\n)\n\n/*var checktests = []struct {\n\tcheck string\n\tmsg string\n}{\n\t{\"NoExtends.yml\", \"Y"
},
{
"path": "internal/check/metric.go",
"chars": 3292,
"preview": "package check\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/d5/tengo/v2\"\n\t\"github.com/d5/tengo/v2/stdli"
},
{
"path": "internal/check/occurrence.go",
"chars": 3042,
"preview": "package check\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/errata-ai/regexp2\"\n\n\t\"github.com/errata-ai/vale/v3/internal/"
},
{
"path": "internal/check/readability.go",
"chars": 2444,
"preview": "package check\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/jdkato/twine/summarize\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"gi"
},
{
"path": "internal/check/repetition.go",
"chars": 3060,
"preview": "package check\n\nimport (\n\t\"strings\"\n\n\t\"github.com/errata-ai/regexp2\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"git"
},
{
"path": "internal/check/scope.go",
"chars": 2775,
"preview": "package check\n\nimport (\n\t\"strings\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/errata-ai/vale/v3/interna"
},
{
"path": "internal/check/scope_test.go",
"chars": 617,
"preview": "package check\n\nimport (\n\t\"testing\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n)\n\nfunc TestSelectors(t *testing.T) {\n"
},
{
"path": "internal/check/script.go",
"chars": 3224,
"preview": "package check\n\nimport (\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/d5/tengo/v2\"\n\t\"github.com/d5/tengo/v2/stdlib\"\n\n\t\"github.com/errat"
},
{
"path": "internal/check/sequence.go",
"chars": 7072,
"preview": "package check\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/errata-ai/regexp2\"\n\t\"github.com/jdkato/twine/nlp/tag\"\n\t\"github.c"
},
{
"path": "internal/check/spelling.go",
"chars": 7044,
"preview": "package check\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/errata-ai/regexp2"
},
{
"path": "internal/check/substitution.go",
"chars": 6013,
"preview": "package check\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"golang.org/x/exp/maps\"\n\n\t\"github.com/errata-ai/regexp2\"\n\t\"github.co"
},
{
"path": "internal/check/substitution_test.go",
"chars": 3622,
"preview": "package check\n\nimport (\n\t\"testing\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/errata-ai/vale/v3/interna"
},
{
"path": "internal/check/variables.go",
"chars": 3184,
"preview": "package check\n\nimport (\n\t\"strings\"\n\n\t\"github.com/errata-ai/regexp2\"\n\t\"github.com/jdkato/twine/strcase\"\n\n\t\"github.com/err"
},
{
"path": "internal/check/variables_test.go",
"chars": 1800,
"preview": "package check\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jdkato/twine/strcase\"\n)\n\ntype changeCase struct {\n\tmatch bool\n\thea"
},
{
"path": "internal/core/alert.go",
"chars": 2209,
"preview": "package core\n\n// AlertLevels holds the possible values for \"level\" in an external rule.\nvar AlertLevels = []string{\"sugg"
},
{
"path": "internal/core/config.go",
"chars": 11789,
"preview": "package core\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/adrg/xdg\"\n"
},
{
"path": "internal/core/config_test.go",
"chars": 2680,
"preview": "package core\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/errata-ai/vale/v3/internal/system\"\n)\n\nvar testDat"
},
{
"path": "internal/core/doc.go",
"chars": 75,
"preview": "// Package core contains configuration and utility internals.\npackage core\n"
},
{
"path": "internal/core/error.go",
"chars": 3160,
"preview": "package core\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/pterm/pterm\"\n)"
},
{
"path": "internal/core/file.go",
"chars": 11589,
"preview": "package core\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"githu"
},
{
"path": "internal/core/format.go",
"chars": 4463,
"preview": "package core\n\nimport (\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n)\n\n// CommentsByNormedExt determines what parts of a file w"
},
{
"path": "internal/core/ini.go",
"chars": 11197,
"preview": "package core\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/errata-ai/ini\"\n\n\t\"githu"
},
{
"path": "internal/core/ini_test.go",
"chars": 4135,
"preview": "package core\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc Test_processConfig_commentDelimiters(t *tes"
},
{
"path": "internal/core/location.go",
"chars": 2377,
"preview": "package core\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/errata-ai/vale/v3/internal/nlp\"\n)\n\n// initialPosition calculat"
},
{
"path": "internal/core/markup.go",
"chars": 3798,
"preview": "// The MIT License (MIT)\n\n// Copyright (c) 2016 Write.as\n\n// Permission is hereby granted, free of charge, to any person"
},
{
"path": "internal/core/source.go",
"chars": 5253,
"preview": "package core\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/errata-ai/ini\"\n\t\"github."
},
{
"path": "internal/core/source_test.go",
"chars": 1446,
"preview": "package core\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nvar knownConfig = filepath.Join(testData, \"fixtures\", \"forma"
},
{
"path": "internal/core/util.go",
"chars": 6927,
"preview": "package core\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\t\"unicode\"\n\n\t\"github.com/errata-ai/vale/v3/"
},
{
"path": "internal/core/util_test.go",
"chars": 3972,
"preview": "package core\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/errata-ai/vale/v3/internal/system\"\n)\n\nfunc TestFo"
},
{
"path": "internal/core/view.go",
"chars": 4617,
"preview": "package core\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\n\t\"github.com/pelletier/go-toml/v2\"\n"
},
{
"path": "internal/glob/glob.go",
"chars": 1232,
"preview": "// Package glob implements pure-Go globbing utilities.\npackage glob\n\nimport (\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/"
},
{
"path": "internal/glob/glob_test.go",
"chars": 994,
"preview": "package glob\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\ntype globTest struct {\n\tquery string\n\tmatch bool\n}\n\nvar globTests = []struct"
},
{
"path": "internal/lint/adoc.go",
"chars": 2723,
"preview": "package lint\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com"
},
{
"path": "internal/lint/ast.go",
"chars": 6932,
"preview": "package lint\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"unicode/utf8\"\n\n\t\"golang.org/x/net/html\"\n\n\t\"github.com/errata-ai/vale/v3/int"
},
{
"path": "internal/lint/code/c.go",
"chars": 340,
"preview": "package code\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/smacker/go-tree-sitter/c\"\n)"
},
{
"path": "internal/lint/code/comments.go",
"chars": 2849,
"preview": "package code\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\n\tsitter \"github.com/smacker/go-tree-sitter\"\n)\n\n// "
},
{
"path": "internal/lint/code/comments_test.go",
"chars": 1027,
"preview": "package code\n\nimport (\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nvar testDir = \"../../../testdata/comments\"\nv"
},
{
"path": "internal/lint/code/cpp.go",
"chars": 342,
"preview": "package code\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/smacker/go-tree-sitter/cpp\""
},
{
"path": "internal/lint/code/css.go",
"chars": 401,
"preview": "package code\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/smacker/go-tree-sitter/css\""
},
{
"path": "internal/lint/code/go.go",
"chars": 345,
"preview": "package code\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/smacker/go-tree-sitter/gola"
},
{
"path": "internal/lint/code/java.go",
"chars": 415,
"preview": "package code\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/smacker/go-tree-sitter/java"
},
{
"path": "internal/lint/code/jl.go",
"chars": 493,
"preview": "package code\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/jdkato/go-tree-sitter-julia"
},
{
"path": "internal/lint/code/js.go",
"chars": 381,
"preview": "package code\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/smacker/go-tree-sitter/java"
},
{
"path": "internal/lint/code/lang.go",
"chars": 1175,
"preview": "package code\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\tsitter \"github.com/smacker/go-tr"
},
{
"path": "internal/lint/code/proto.go",
"chars": 355,
"preview": "package code\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/smacker/go-tree-sitter/prot"
},
{
"path": "internal/lint/code/py.go",
"chars": 924,
"preview": "package code\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/smacker/go-tree-sitter/pyth"
},
{
"path": "internal/lint/code/query.go",
"chars": 1367,
"preview": "package code\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\n\tsitter \"github.com/smacker/go-tree-sitter\"\n)\n\ntype QueryEngine struct {\n\ttr"
},
{
"path": "internal/lint/code/rb.go",
"chars": 425,
"preview": "package code\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/smacker/go-tree-sitter/ruby"
},
{
"path": "internal/lint/code/rs.go",
"chars": 423,
"preview": "package code\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/smacker/go-tree-sitter/rust"
},
{
"path": "internal/lint/code/ts.go",
"chars": 372,
"preview": "package code\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/smacker/go-tree-sitter/type"
},
{
"path": "internal/lint/code/tsx.go",
"chars": 351,
"preview": "package code\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/smacker/go-tree-sitter/type"
},
{
"path": "internal/lint/code/util.go",
"chars": 593,
"preview": "package code\n\nimport (\n\t\"encoding/json\"\n\t\"strings\"\n\t\"unicode/utf8\"\n)\n\nfunc toJSON(comments []Comment) string {\n\tj, _ := "
},
{
"path": "internal/lint/code/yaml.go",
"chars": 395,
"preview": "package code\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/smacker/go-tree-sitter/yaml"
},
{
"path": "internal/lint/code.go",
"chars": 3800,
"preview": "package lint\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"gi"
},
{
"path": "internal/lint/data.go",
"chars": 2096,
"preview": "package lint\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/errata-ai/vale/v3/i"
},
{
"path": "internal/lint/dita.go",
"chars": 1796,
"preview": "package lint\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/errata-ai/vale/v3/i"
},
{
"path": "internal/lint/doc.go",
"chars": 192,
"preview": "/*\nPackage lint implements Vale's syntax-aware linting functionality.\n\nThe package is split into core linting logic (thi"
},
{
"path": "internal/lint/fragment.go",
"chars": 2007,
"preview": "package lint\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/errata-ai/vale/v3/i"
},
{
"path": "internal/lint/html.go",
"chars": 4490,
"preview": "package lint\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/errata-ai/regexp2\"\n\n\t\"github.com/errata-ai/vale/v"
},
{
"path": "internal/lint/html_test.go",
"chars": 2925,
"preview": "package lint\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n)\n\nfunc Test_applyPatterns(t"
},
{
"path": "internal/lint/lint.go",
"chars": 8610,
"preview": "package lint\n\nimport (\n\t\"errors\"\n\t\"io/fs\"\n\t\"net/http\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/remeh/sizedwaitgroup\"\n\n\t"
},
{
"path": "internal/lint/lint_test.go",
"chars": 4668,
"preview": "package lint\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/errata-ai/vale/v3/i"
},
{
"path": "internal/lint/md.go",
"chars": 2421,
"preview": "package lint\n\nimport (\n\t\"bytes\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/yuin/goldmark\"\n\t\"github.com/yuin/goldmark/extension\"\n"
},
{
"path": "internal/lint/mdx.go",
"chars": 644,
"preview": "package lint\n\nimport (\n\t\"errors\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/errata-ai/vale/v3/internal/"
},
{
"path": "internal/lint/metadata.go",
"chars": 1367,
"preview": "package lint\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/adrg/frontmatter\"\n\n\t\"github.com/errata-ai/vale/v3/inter"
},
{
"path": "internal/lint/org.go",
"chars": 1629,
"preview": "package lint\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/niklasfasching/go-org/org\"\n\n\t\"github.com/errata-ai/vale/v3/int"
},
{
"path": "internal/lint/rst.go",
"chars": 2016,
"preview": "package lint\n\nimport (\n\t\"errors\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github.com/errata"
},
{
"path": "internal/lint/util.go",
"chars": 1912,
"preview": "package lint\n\nimport (\n\t\"strings\"\n\n\t\"github.com/adrg/strutil\"\n\t\"github.com/adrg/strutil/metrics\"\n\n\t\"github.com/errata-ai"
},
{
"path": "internal/lint/walk.go",
"chars": 5391,
"preview": "package lint\n\nimport (\n\t\"bytes\"\n\t\"net/url\"\n\t\"strings\"\n\t\"unicode/utf8\"\n\t\"unsafe\"\n\n\t\"golang.org/x/net/html\"\n\n\t\"github.com/"
},
{
"path": "internal/lint/xml.go",
"chars": 1032,
"preview": "package lint\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/errata-ai/vale/v3/internal/core\"\n\t\"github."
},
{
"path": "internal/lint/xml_test.go",
"chars": 645,
"preview": "package lint\n\nimport (\n\t\"testing\"\n)\n\nfunc TestXSLTArgsNotPolluted(t *testing.T) {\n\t// We want to test that xsltArgs in x"
},
{
"path": "internal/nlp/doc.go",
"chars": 97,
"preview": "// Package nlp implements POS tagging, word tokenization, and sentence segmentation.\npackage nlp\n"
},
{
"path": "internal/nlp/http.go",
"chars": 1217,
"preview": "package nlp\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\n\t\"github.com/jdkato/twine/nlp/tag\"\n)\n\ntype SegmentR"
},
{
"path": "internal/nlp/prose.go",
"chars": 1605,
"preview": "package nlp\n\nimport (\n\t\"strings\"\n\n\t\"github.com/jdkato/twine/nlp/segment\"\n\t\"github.com/jdkato/twine/nlp/tag\"\n\t\"github.com"
},
{
"path": "internal/nlp/provider.go",
"chars": 2907,
"preview": "package nlp\n\nimport (\n\t\"strings\"\n)\n\ntype segmenter func(string) []string\n\n// A Block represents a section of text.\ntype "
},
{
"path": "internal/nlp/tokenize.go",
"chars": 4649,
"preview": "package nlp\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\t\"unicode\"\n\t\"unicode/utf8\"\n)\n\ntype TokenTester func(string) bool\n\ntype Tokeni"
},
{
"path": "internal/nlp/tokenize_test.go",
"chars": 885,
"preview": "package nlp\n\nimport (\n\t\"testing\"\n)\n\ntype Word2Tok struct {\n\tInput string\n\tOutput []string\n}\n\nvar cases = []Word2Tok{\n\t{"
},
{
"path": "internal/nlp/util.go",
"chars": 898,
"preview": "package nlp\n\nimport (\n\t\"strings\"\n\t\"unicode\"\n\t\"unicode/utf8\"\n)\n\n// StrLen returns the number of runes in a string.\nfunc S"
},
{
"path": "internal/nlp/var.go",
"chars": 1503,
"preview": "package nlp\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n)\n\nvar internalRE = regexp.MustCompile(`^(?:[A-Za-z]\\.){2,}$|^[A-Z][a-z]{1,2}"
},
{
"path": "internal/spell/LICENSE",
"chars": 1081,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016 Nick Galbreath\n\nPermission is hereby granted, free of charge, to any person ob"
},
{
"path": "internal/spell/aff.go",
"chars": 8443,
"preview": "package spell\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// affixType is either an affix prefix "
},
{
"path": "internal/spell/aff_test.go",
"chars": 4068,
"preview": "package spell\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestParseFlagsASCII(t *testing.T) {\n\tdc := dictConfig{Flag: \"ASCII"
},
{
"path": "internal/spell/data/en_US-web.aff",
"chars": 3097,
"preview": "SET UTF-8\nTRY esianrtolcdugmphbyfvkwzESIANRTOLCDUGMPHBYFVKWZ'\nICONV 2\nICONV ’ '\nICONV ‘ '\nNOSUGGEST !\n\n# ordinal numbers"
},
{
"path": "internal/spell/data/en_US-web.dic",
"chars": 891220,
"preview": "81529\n0/nm\n0th/pt\n1/n1\n1st/p\n1th/tc\n2/nm\n2nd/p\n2th/tc\n3/nm\n3rd/p\n3th/tc\n4/nm\n4th/pt\n5/nm\n5th/pt\n6/nm\n6th/pt\n7/nm\n7th/pt\n"
},
{
"path": "internal/spell/doc.go",
"chars": 122,
"preview": "// Package spell is a pure-Go, high-performance spell checker based on\n// Hunspell-compatible dictionaries.\npackage spel"
},
{
"path": "internal/spell/gospell.go",
"chars": 5365,
"preview": "package spell\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/adrg/strutil\"\n\t\"github.co"
},
{
"path": "internal/spell/multi.go",
"chars": 5223,
"preview": "package spell\n\nimport (\n\t\"bytes\"\n\t_ \"embed\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\n\t\"github.com/errata-ai/vale/v3/intern"
},
{
"path": "internal/spell/words.go",
"chars": 2018,
"preview": "package spell\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\t\"unicode\"\n)\n\n// number form, may include dots, commas and dashes\nvar numbe"
},
{
"path": "internal/system/cmd.go",
"chars": 469,
"preview": "package system\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"os/exec\"\n\t\"strings\"\n)\n\n// ExecuteWithInput runs a command with the given t"
},
{
"path": "internal/system/dir.go",
"chars": 321,
"preview": "package system\n\nimport \"os\"\n\n// Mkdir creates a directory at the given path.\nfunc Mkdir(dir string) error {\n\treturn os.M"
},
{
"path": "internal/system/file.go",
"chars": 915,
"preview": "package system\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\n// FileExists determines if the path given by `filename` e"
},
{
"path": "internal/system/path.go",
"chars": 1628,
"preview": "package system\n\nimport (\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\n// AbsPath returns the absolute path of `path`."
},
{
"path": "internal/system/path_test.go",
"chars": 980,
"preview": "package system\n\nimport (\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc TestDeterminePath(t *testing.T) {\n\ttests := []struct {\n\t\tco"
},
{
"path": "internal/system/system.go",
"chars": 855,
"preview": "package system\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\t\"strings\"\n)\n\n// Name returns the current OS.\nfunc Name() string {\n\treturn ru"
},
{
"path": "internal/system/walk.go",
"chars": 900,
"preview": "package system\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n)\n\nfunc walk(filename string, linkDirname string, walkFn filepath.WalkFu"
},
{
"path": "internal/system/zip.go",
"chars": 1133,
"preview": "package system\n\nimport (\n\t\"archive/zip\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\n// Unarchive extracts a ZIP ar"
},
{
"path": "testdata/Gemfile",
"chars": 138,
"preview": "source 'https://rubygems.org'\n\ngem 'specific_install', '~> 0.3.8'\ngem 'cucumber', '~> 6.1.0'\ngem 'os', '~> 0.9.6'\n\ngem \""
},
{
"path": "testdata/comments/in/0.go",
"chars": 565,
"preview": "/*\nPackage lint implments Vale's syntax-aware linting functionality.\n\nThe package is split into core linting logic (this"
},
{
"path": "testdata/comments/in/1.rs",
"chars": 1844,
"preview": "// This module defines the set of command line arguments that ripgrep supports,\n// including some light validation.\n//\n/"
},
{
"path": "testdata/comments/in/2.py",
"chars": 636,
"preview": "# FIXME:\n\n\ndef FIXME():\n \"\"\"\n FIXME: this is *mardown*.\n\n ```python\n print(\"FIXME: this is *python*.\")\n `"
},
{
"path": "testdata/comments/in/3.cpp",
"chars": 1692,
"preview": "\n/*!\n \\class QObject\n \\brief The QObject class is the base class of all Qt objects.\n\n \\ingroup objectmodel\n\n "
},
{
"path": "testdata/comments/in/4.js",
"chars": 2793,
"preview": "var mdTable = require('markdown-table')\n\n/**\n * Get all boolean input values for n variables.\n *\n * @example\n * // [ [ t"
},
{
"path": "testdata/comments/in/5.yml",
"chars": 512,
"preview": "# This Deployment runs our API component\n#\n# To increase the TODO number of replicas that run,\n# change the value of the"
},
{
"path": "testdata/comments/out/0.json",
"chars": 1275,
"preview": "[\n {\n \"Text\": \"\\nPackage lint implments Vale's syntax-aware linting functionality.\\n\\nThe package is split int"
},
{
"path": "testdata/comments/out/1.json",
"chars": 3747,
"preview": "[\n {\n \"Text\": \"This module defines the set of command line arguments that ripgrep supports,\\nincluding some li"
},
{
"path": "testdata/comments/out/2.json",
"chars": 1608,
"preview": "[\n {\n \"Text\": \"FIXME:\",\n \"Source\": \"# FIXME:\",\n \"Line\": 1,\n \"Offset\": 0,\n \"Scope\":"
},
{
"path": "testdata/comments/out/3.json",
"chars": 3616,
"preview": "[\n {\n \"Text\": \"\\n\\\\class QObject\\n\\\\brief The QObject class is the base class of all Qt objects.\\n\\n\\\\ingroup "
},
{
"path": "testdata/comments/out/4.json",
"chars": 3481,
"preview": "[\n {\n \"Text\": \"\\n* Get all boolean input values for n variables.\\n*\\n* @example\\n* [ [ true, true ], [ true, "
},
{
"path": "testdata/comments/out/5.json",
"chars": 918,
"preview": "[\n {\n \"Text\": \"This Deployment runs our API component\\n\\nTo increase the TODO number of replicas that run,\\nch"
},
{
"path": "testdata/features/CLI.feature",
"chars": 8691,
"preview": "Feature: CLI\n Scenario: Lint with a custom output format (line)\n When I test template \"line.tmpl\"\n Then"
},
{
"path": "testdata/features/checks.feature",
"chars": 9227,
"preview": "Feature: Checks\n Scenario: Script\n When I test \"checks/Script\"\n Then the output should contain exactly:"
},
{
"path": "testdata/features/comments.feature",
"chars": 5070,
"preview": "Feature: Comments\n\n Scenario: MDX\n When I test comments for \"test.mdx\"\n Then the output should contain exactly:\n "
},
{
"path": "testdata/features/config.feature",
"chars": 16952,
"preview": "Feature: Config\n Background:\n Given a file named \"test.md\" with:\n \"\"\"\n This is a very im"
},
{
"path": "testdata/features/fragments.feature",
"chars": 3482,
"preview": "Feature: Fragments\n Scenario: Code -> Markup\n When I test \"fragments\"\n Then the output should contain e"
},
{
"path": "testdata/features/frontmatter.feature",
"chars": 2888,
"preview": "Feature: Frontmatter\n\n Scenario: Markup with frontmatter\n When I test \"frontmatter\"\n Then the output sh"
},
{
"path": "testdata/features/lint.feature",
"chars": 16495,
"preview": "Feature: Lint\n\n Scenario: Lint a path with excluded folders\n When I lint path with exclusions\n Then the"
},
{
"path": "testdata/features/misc.feature",
"chars": 6376,
"preview": "Feature: Misc\n\n Scenario: Vocab\n When I use Vocab \"Basic\"\n Then the output should contain exactly:\n "
},
{
"path": "testdata/features/patterns.feature",
"chars": 5483,
"preview": "Feature: IgnorePatterns\n\n Scenario: MDX\n When I test patterns for \"test.mdx\"\n Then the output should co"
},
{
"path": "testdata/features/pkg.feature",
"chars": 1327,
"preview": "Feature: Packages\n\n Scenario: Sync a complete, v3 package\n When I sync pkg \"complete\"\n Then the output "
},
{
"path": "testdata/features/scopes.feature",
"chars": 6001,
"preview": "Feature: Scopes\n\n Scenario: Negated\n When I test scope \"multi\"\n Then the output should contain exactly:"
},
{
"path": "testdata/features/steps.rb",
"chars": 4769,
"preview": "require 'os'\n\nexe = 'vale'\nif OS.windows?\n exe += '.exe'\nend\ncmd = (exe + ' --output=line --sort --normalize --relative"
},
{
"path": "testdata/features/styles.feature",
"chars": 4448,
"preview": "Feature: Styles\n Scenario: Lint against Scripts\n When I apply style \"Scripts\"\n Then the output should c"
},
{
"path": "testdata/features/support/env.rb",
"chars": 25,
"preview": "require 'aruba/cucumber'\n"
},
{
"path": "testdata/features/views.feature",
"chars": 2839,
"preview": "Feature: Views\n\n Scenario: YAML\n When I test \"views\"\n Then the output should contain exactly:\n "
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/VERSION.xsl",
"chars": 4569,
"preview": "<?xml version='1.0'?> <!-- -*- nxml -*- vim: set foldlevel=2: -->\n<xsl:stylesheet xmlns:xsl=\"http://www.w3.org/1999/XSL/"
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/build.xml",
"chars": 1899,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<project name=\"DocBook XSLT 1.0 stylesheets\" default=\"all\">\r\n\r\n <!-- \r\n T"
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/catalog.xml",
"chars": 580,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<catalog xmlns=\"urn:oasis:names:tc:entity:xmlns:xml:catalog\">\n <!-- XML Catalog "
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/addns.xsl",
"chars": 4895,
"preview": "<?xml version='1.0'?>\n<xsl:stylesheet xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n xmlns:exsl=\"http:"
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/af.xml",
"chars": 46148,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><l:l10n xmlns:l=\"http://docbook.sourceforge.net/xmlns/l10n/1.0\" language=\"af\" engl"
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/am.xml",
"chars": 44203,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><l:l10n xmlns:l=\"http://docbook.sourceforge.net/xmlns/l10n/1.0\" language=\"am\" engl"
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/ar.xml",
"chars": 44661,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><l:l10n xmlns:l=\"http://docbook.sourceforge.net/xmlns/l10n/1.0\" language=\"ar\" engl"
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/as.xml",
"chars": 32434,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><l:l10n xmlns:l=\"http://docbook.sourceforge.net/xmlns/l10n/1.0\" language=\"as\" engl"
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/ast.xml",
"chars": 32722,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><l:l10n xmlns:l=\"http://docbook.sourceforge.net/xmlns/l10n/1.0\" language=\"ast\" eng"
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/autoidx-kimber.xsl",
"chars": 1527,
"preview": "<?xml version=\"1.0\"?>\n<!DOCTYPE xsl:stylesheet [\n<!ENTITY % common.entities SYSTEM \"entities.ent\">\n%common.entities;\n<!-"
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/autoidx-kosek.xsl",
"chars": 5058,
"preview": "<?xml version=\"1.0\"?>\n<!DOCTYPE xsl:stylesheet [\n<!ENTITY % common.entities SYSTEM \"entities.ent\">\n%common.entities;\n]>\n"
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/az.xml",
"chars": 32978,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><l:l10n xmlns:l=\"http://docbook.sourceforge.net/xmlns/l10n/1.0\" language=\"az\" engl"
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/bg.xml",
"chars": 34606,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><l:l10n xmlns:l=\"http://docbook.sourceforge.net/xmlns/l10n/1.0\" language=\"bg\" engl"
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/bn.xml",
"chars": 45101,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><l:l10n xmlns:l=\"http://docbook.sourceforge.net/xmlns/l10n/1.0\" language=\"bn\" engl"
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/bn_in.xml",
"chars": 32689,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><l:l10n xmlns:l=\"http://docbook.sourceforge.net/xmlns/l10n/1.0\" language=\"bn_in\" e"
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/bs.xml",
"chars": 32567,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><l:l10n xmlns:l=\"http://docbook.sourceforge.net/xmlns/l10n/1.0\" language=\"bs\" engl"
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/build.xml",
"chars": 935,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<project name=\"DocBook XSLT 1.0 stylesheets - Gentext & Localization\" defaul"
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/ca.xml",
"chars": 32616,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><l:l10n xmlns:l=\"http://docbook.sourceforge.net/xmlns/l10n/1.0\" language=\"ca\" engl"
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/charmap.xml",
"chars": 6606,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<reference xmlns:xlink=\"http://www.w3.org/1999/xlink\" xml:id=\"charmap\">\n <info>\n"
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/charmap.xsl",
"chars": 9368,
"preview": "<?xml version='1.0'?>\n<xsl:stylesheet xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n xmlns:d=\"http://d"
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/common.xml",
"chars": 18401,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<reference xmlns:xlink=\"http://www.w3.org/1999/xlink\" xml:id=\"base\">\n <info>\n "
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/common.xsl",
"chars": 80323,
"preview": "<?xml version='1.0'?>\n<!DOCTYPE xsl:stylesheet [\n<!ENTITY lowercase \"'abcdefghijklmnopqrstuvwxyz'\">\n<!ENTITY uppercase \""
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/cs.xml",
"chars": 31398,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><l:l10n xmlns:l=\"http://docbook.sourceforge.net/xmlns/l10n/1.0\" language=\"cs\" engl"
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/cy.xml",
"chars": 45322,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><l:l10n xmlns:l=\"http://docbook.sourceforge.net/xmlns/l10n/1.0\" language=\"cy\" engl"
},
{
"path": "testdata/fixtures/XSL/docbook-xsl-snapshot/common/da.xml",
"chars": 32496,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><l:l10n xmlns:l=\"http://docbook.sourceforge.net/xmlns/l10n/1.0\" language=\"da\" engl"
}
]
// ... and 689 more files (download for full content)
About this extraction
This page contains the full source code of the ValeLint/vale GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 889 files (24.8 MB), approximately 2.5M tokens, and a symbol index with 803 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.