unsplash / intlc

Compile ICU messages into code. Supports TypeScript and JSX. No runtime.
MIT License
56 stars 2 forks source link

AST annotations and fancy lint errors #179

Closed samhh closed 1 year ago

samhh commented 1 year ago

There's some further minor refactoring that could be done but this PR is already fairly big, particularly conceptually, and I'm really happy with where it's at as-is.

This PR adds a mechanism by which we can record AST source annotations/offsets during parsing for later use, and utilises them to drastically improve linting output. Closes #142.

Given:

{
  "msg": {
    "message": "Some words {mySelect, select, other {}} {myCardinal, plural, =1 {} =1 {}}"
  }
}

Before:

msg: 
 Redundant select: mySelect
 Duplicate plural case: myCardinal, =1

With this PR:

translations.json:3:29:
  |
3 |     "message": "Some words {mySelect, select, other {}} {myCardinal, plural, =1 {} =1 {}}"
  |                             ^^^^^^^^
redundant-select: Select named `mySelect` is redundant as it only contains a wildcard.

Learn more: https://github.com/unsplash/intlc/wiki/Lint-rules-reference#redundant-select

translations.json:3:84:
  |
3 |     "message": "Some words {mySelect, select, other {}} {myCardinal, plural, =1 {} =1 {}}"
  |                                                                                    ^^
duplicate-plural-case: Plural named `myCardinal` contains a duplicate `=1` case.

Learn more: https://github.com/unsplash/intlc/wiki/Lint-rules-reference#duplicate-plural-case

A new wiki page is linked, closing #141: https://github.com/unsplash/intlc/wiki/Lint-rules-reference

Technically this PR piggybacks on the pattern functor introduced in #171, utilising Cofree to compose each layer of the AST with an additional Int representing a source offset for pretty printing later. Cofree is simpler than it looks or sounds from the consumer perspective: what was once FinF is now for example 123 :< FinF. What's particularly nice about this approach is that our core AST is untouched, leaving most of the code unaffected and leaving the door open for other compositions.

Additionally a couple of lint rules now make use of paramorphisms, which are like catamorphisms but give you access to the original data at each recursion layer as well. They do so in order to calculate where to place the pretty error carets.

I'm not intending to introduce any further abstract technical stuff after this, promise :angel: