Skip to content

Commit 89fa021

Browse files
committed
warn about basic CSS property typos
1 parent 41dea66 commit 89fa021

File tree

7 files changed

+104
-9
lines changed

7 files changed

+104
-9
lines changed

CHANGELOG.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,23 +42,42 @@
4242
The TypeScript compiler already [treats this as an error](https://github.com/microsoft/TypeScript/issues/36341), so esbuild now treats this as an error in TypeScript files too. That looks like this:
4343

4444
```
45-
✘ [ERROR] The character ">" is not valid inside a JSX element, but can be escaped as "{'>'}" instead
45+
✘ [ERROR] The character ">" is not valid inside a JSX element
4646

4747
example.tsx:2:14:
4848
2 │ return <div>></div>;
4949
│ ^
5050
╵ {'>'}
5151

52-
✘ [ERROR] The character "}" is not valid inside a JSX element, but can be escaped as "{'}'}" instead
52+
Did you mean to escape it as "{'>'}" instead?
53+
54+
✘ [ERROR] The character "}" is not valid inside a JSX element
5355

5456
example.tsx:5:17:
5557
5 │ return <div>{1}}</div>;
5658
│ ^
5759
╵ {'}'}
60+
61+
Did you mean to escape it as "{'}'}" instead?
5862
```
5963

6064
Babel doesn't yet treat this as an error, so esbuild only warns about these characters in JavaScript files for now. Babel 8 [treats this as an error](https://github.com/babel/babel/issues/11042) but Babel 8 [hasn't been released yet](https://github.com/babel/babel/issues/10746). If you see this warning, I recommend fixing the invalid JSX syntax because it will become an error in the future.
6165

66+
* Warn about basic CSS property typos
67+
68+
This release now generates a warning if you use a CSS property that is one character off from a known CSS property:
69+
70+
```
71+
▲ [WARNING] "marign-left" is not a known CSS property
72+
73+
example.css:2:2:
74+
2 │ marign-left: 12px;
75+
│ ~~~~~~~~~~~
76+
╵ margin-left
77+
78+
Did you mean "margin-left" instead?
79+
```
80+
6281
## 0.14.11
6382

6483
* Fix a bug with enum inlining ([#1903](https://github.com/evanw/esbuild/issues/1903))

internal/css_ast/css_decl_table.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package css_ast
22

3+
import "github.com/evanw/esbuild/internal/helpers"
4+
35
type D uint16
46

57
const (
@@ -640,3 +642,18 @@ var KnownDeclarations = map[string]D{
640642
"z-index": DZIndex,
641643
"zoom": DZoom,
642644
}
645+
646+
var typoDetector *helpers.TypoDetector
647+
648+
func MaybeCorrectDeclarationTypo(text string) (string, bool) {
649+
if typoDetector == nil {
650+
// Lazily-initialize the typo detector for speed when it's not needed
651+
valid := make([]string, 0, len(KnownDeclarations))
652+
for key := range KnownDeclarations {
653+
valid = append(valid, key)
654+
}
655+
detector := helpers.MakeTypoDetector(valid)
656+
typoDetector = &detector
657+
}
658+
return typoDetector.MaybeCorrectTypo(text)
659+
}

internal/css_parser/css_parser.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1377,8 +1377,20 @@ stop:
13771377
}
13781378
}
13791379

1380+
key := css_ast.KnownDeclarations[keyText]
1381+
1382+
// Attempt to point out trivial typos
1383+
if key == css_ast.DUnknown {
1384+
if corrected, ok := css_ast.MaybeCorrectDeclarationTypo(keyText); ok {
1385+
data := p.tracker.MsgData(keyToken.Range, fmt.Sprintf("%q is not a known CSS property", keyText))
1386+
data.Location.Suggestion = corrected
1387+
p.log.AddMsg(logger.Msg{Kind: logger.Warning, Data: data,
1388+
Notes: []logger.MsgData{{Text: fmt.Sprintf("Did you mean %q instead?", corrected)}}})
1389+
}
1390+
}
1391+
13801392
return css_ast.Rule{Loc: keyLoc, Data: &css_ast.RDeclaration{
1381-
Key: css_ast.KnownDeclarations[keyText],
1393+
Key: key,
13821394
KeyText: keyText,
13831395
KeyRange: keyToken.Range,
13841396
Value: result,

internal/helpers/typos.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package helpers
2+
3+
import "unicode/utf8"
4+
5+
type TypoDetector struct {
6+
oneCharTypos map[string]string
7+
}
8+
9+
func MakeTypoDetector(valid []string) TypoDetector {
10+
detector := TypoDetector{oneCharTypos: make(map[string]string)}
11+
12+
// Add all combinations of each valid word with one character missing
13+
for _, correct := range valid {
14+
if len(correct) > 2 {
15+
for i, ch := range correct {
16+
detector.oneCharTypos[correct[:i]+correct[i+utf8.RuneLen(ch):]] = correct
17+
}
18+
}
19+
}
20+
21+
return detector
22+
}
23+
24+
func (detector TypoDetector) MaybeCorrectTypo(typo string) (string, bool) {
25+
// Check for a single deleted character
26+
if corrected, ok := detector.oneCharTypos[typo]; ok {
27+
return corrected, true
28+
}
29+
30+
// Check for a single misplaced character
31+
for i, ch := range typo {
32+
if corrected, ok := detector.oneCharTypos[typo[:i]+typo[i+utf8.RuneLen(ch):]]; ok {
33+
return corrected, true
34+
}
35+
}
36+
37+
return "", false
38+
}

internal/js_lexer/js_lexer.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -914,7 +914,8 @@ func (lexer *Lexer) NextJSXElementChild() {
914914
replacement = "{'>'}"
915915
}
916916
msg := logger.Msg{Kind: logger.Error, Data: lexer.tracker.MsgData(logger.Range{Loc: logger.Loc{Start: int32(lexer.end)}, Len: 1},
917-
fmt.Sprintf("The character \"%c\" is not valid inside a JSX element, but can be escaped as %q instead", lexer.codePoint, replacement))}
917+
fmt.Sprintf("The character \"%c\" is not valid inside a JSX element", lexer.codePoint)),
918+
Notes: []logger.MsgData{{Text: fmt.Sprintf("Did you mean to escape it as %q instead?", replacement)}}}
918919
msg.Data.Location.Suggestion = replacement
919920
if !lexer.ts.Parse {
920921
// TypeScript treats this as an error but Babel doesn't treat this

internal/js_parser/js_parser_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4242,8 +4242,12 @@ func TestNewTarget(t *testing.T) {
42424242
}
42434243

42444244
func TestJSX(t *testing.T) {
4245-
expectParseErrorJSX(t, "<div>></div>", "<stdin>: WARNING: The character \">\" is not valid inside a JSX element, but can be escaped as \"{'>'}\" instead\n")
4246-
expectParseErrorJSX(t, "<div>{1}}</div>", "<stdin>: WARNING: The character \"}\" is not valid inside a JSX element, but can be escaped as \"{'}'}\" instead\n")
4245+
expectParseErrorJSX(t, "<div>></div>",
4246+
"<stdin>: WARNING: The character \">\" is not valid inside a JSX element\n"+
4247+
"NOTE: Did you mean to escape it as \"{'>'}\" instead?\n")
4248+
expectParseErrorJSX(t, "<div>{1}}</div>",
4249+
"<stdin>: WARNING: The character \"}\" is not valid inside a JSX element\n"+
4250+
"NOTE: Did you mean to escape it as \"{'}'}\" instead?\n")
42474251
expectPrintedJSX(t, "<div>></div>", "/* @__PURE__ */ React.createElement(\"div\", null, \">\");\n")
42484252
expectPrintedJSX(t, "<div>{1}}</div>", "/* @__PURE__ */ React.createElement(\"div\", null, 1, \"}\");\n")
42494253

internal/js_parser/ts_parser_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1796,8 +1796,12 @@ func TestTSOptionalChain(t *testing.T) {
17961796
}
17971797

17981798
func TestTSJSX(t *testing.T) {
1799-
expectParseErrorTSX(t, "<div>></div>", "<stdin>: ERROR: The character \">\" is not valid inside a JSX element, but can be escaped as \"{'>'}\" instead\n")
1800-
expectParseErrorTSX(t, "<div>{1}}</div>", "<stdin>: ERROR: The character \"}\" is not valid inside a JSX element, but can be escaped as \"{'}'}\" instead\n")
1799+
expectParseErrorTSX(t, "<div>></div>",
1800+
"<stdin>: ERROR: The character \">\" is not valid inside a JSX element\n"+
1801+
"NOTE: Did you mean to escape it as \"{'>'}\" instead?\n")
1802+
expectParseErrorTSX(t, "<div>{1}}</div>",
1803+
"<stdin>: ERROR: The character \"}\" is not valid inside a JSX element\n"+
1804+
"NOTE: Did you mean to escape it as \"{'}'}\" instead?\n")
18011805

18021806
expectPrintedTS(t, "const x = <number>1", "const x = 1;\n")
18031807
expectPrintedTSX(t, "const x = <number>1</number>", "const x = /* @__PURE__ */ React.createElement(\"number\", null, \"1\");\n")
@@ -1849,7 +1853,7 @@ func TestTSJSX(t *testing.T) {
18491853
expectPrintedTS(t, "const x = <[]>(y, z)", "const x = (y, z);\n")
18501854
expectPrintedTS(t, "const x = <[]>(y, z) => {}", "const x = (y, z) => {\n};\n")
18511855

1852-
invalid := "<stdin>: ERROR: The character \">\" is not valid inside a JSX element, but can be escaped as \"{'>'}\" instead\n"
1856+
invalid := "<stdin>: ERROR: The character \">\" is not valid inside a JSX element\nNOTE: Did you mean to escape it as \"{'>'}\" instead?\n"
18531857
expectParseErrorTSX(t, "(<T>(y) => {}</T>)", invalid)
18541858
expectParseErrorTSX(t, "(<T extends>(y) => {}</T>)", invalid)
18551859
expectParseErrorTSX(t, "(<T extends={false}>(y) => {}</T>)", invalid)

0 commit comments

Comments
 (0)