Skip to content

Commit 6f1a53d

Browse files
committed
feat: a bit of manual parsing for better error
1 parent 373b46d commit 6f1a53d

File tree

3 files changed

+55
-15
lines changed

3 files changed

+55
-15
lines changed

src/main/gen/com/github/aleksandrsl/intellijbrowserslist/parser/BrowserslistParser.java

Lines changed: 6 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main/grammar/Browserslist.bnf

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
elementTypeClass="com.github.aleksandrsl.intellijbrowserslist.psi.BrowserslistElementType"
1313
tokenTypeClass="com.github.aleksandrsl.intellijbrowserslist.psi.BrowserslistTokenType"
1414

15+
parserUtilClass="com.github.aleksandrsl.intellijbrowserslist.parser.BrowserslistParserUtil"
16+
1517
tokens=[
1618
EOL="regexp:\R"
1719
LBRACKET="["
@@ -75,9 +77,10 @@ query ::=
7577
| defaultsQuery
7678
private query_recover ::= !(EOL|COMMENT|OR|AND|'[')
7779

80+
7881
private version ::= 'version' | 'versions'
7982
// TODO: Cover in custom stats like `cover 5% in browserslist-config-mycompany stats` should be supported?
80-
statsQuery ::= (COMPARE|'cover') PERCENT ('in' IDENTIFIER 'stats'?)? {pin=1}
83+
statsQuery ::= (COMPARE|'cover') PERCENT ('in' <<parseStats>>)? {pin=1}
8184
lastQuery ::= 'last' INTEGER TARGET? 'major'? version
8285
unreleasedQuery ::= 'unreleased' TARGET? version {pin=1}
8386
timeQuery ::= timeQueryLast_ | timeQuerySince_
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.github.aleksandrsl.intellijbrowserslist.parser
2+
3+
import com.github.aleksandrsl.intellijbrowserslist.psi.BrowserslistTypes.IDENTIFIER
4+
import com.intellij.lang.PsiBuilder
5+
import com.intellij.lang.parser.GeneratedParserUtilBase
6+
import com.intellij.openapi.diagnostic.logger
7+
8+
val countryCodeRegex = Regex("(alt-)?[a-z]{2}", option = RegexOption.IGNORE_CASE)
9+
10+
private val LOG = logger<BrowserslistParserUtil>()
11+
12+
object BrowserslistParserUtil : GeneratedParserUtilBase() {
13+
14+
/**
15+
* Parses stats reference patterns:
16+
* - "my stats"
17+
* - "{IDENTIFIER} stats"
18+
* - "(alt-)?{2 letter code}" (country codes like "us", "alt-us")
19+
*/
20+
@JvmStatic
21+
fun parseStats(builder: PsiBuilder, level: Int): Boolean {
22+
if (!recursion_guard_(builder, level, "parseStats")) return false
23+
24+
var result = parseMyStats(builder)
25+
if (!result) result = parseCountryCodeOrIdentifierStats(builder, level + 1)
26+
return result
27+
}
28+
29+
private fun parseMyStats(builder: PsiBuilder): Boolean {
30+
return consumeToken(builder, "my", false) && consumeToken(builder, "stats", false)
31+
}
32+
33+
private fun parseCountryCodeOrIdentifierStats(builder: PsiBuilder, level: Int): Boolean {
34+
if (!recursion_guard_(builder, level, "parseStats_codeOrIdentifier")) return false
35+
val text = builder.tokenText ?: return false
36+
37+
addVariant(builder, "xx or alt-xx (country code)")
38+
if (countryCodeRegex.matchEntire(text) != null) {
39+
builder.advanceLexer()
40+
return true
41+
} else {
42+
return consumeToken(builder, IDENTIFIER) && consumeToken(builder, "stats", false)
43+
}
44+
}
45+
}

0 commit comments

Comments
 (0)