Skip to content

Commit 66e21a6

Browse files
yeonjuannzakaslumirlumir
authored
feat: add html-eslint/parser explorer (#99)
Co-authored-by: Nicholas C. Zakas <[email protected]> Co-authored-by: 루밀LuMir <[email protected]>
1 parent 9fbd4a4 commit 66e21a6

File tree

13 files changed

+224
-1
lines changed

13 files changed

+224
-1
lines changed

package-lock.json

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

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
},
3636
"dependencies": {
3737
"@codemirror/lang-css": "^6.3.1",
38+
"@codemirror/lang-html": "^6.4.9",
3839
"@codemirror/lang-javascript": "^6.2.2",
3940
"@codemirror/lang-json": "^6.0.1",
4041
"@codemirror/lang-markdown": "^6.2.5",
@@ -43,6 +44,7 @@
4344
"@eslint/js": "^9.9.0",
4445
"@eslint/json": "^0.12.0",
4546
"@eslint/markdown": "^6.4.0",
47+
"@html-eslint/eslint-plugin": "^0.40.3",
4648
"@lezer/highlight": "^1.2.1",
4749
"@radix-ui/react-accordion": "^1.2.0",
4850
"@radix-ui/react-dialog": "^1.1.1",

public/languages/html.svg

Lines changed: 7 additions & 0 deletions
Loading
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import {
2+
AccordionContent,
3+
AccordionItem,
4+
AccordionTrigger,
5+
} from "@/components/ui/accordion";
6+
import { TreeEntry } from "../tree-entry";
7+
import type { FC } from "react";
8+
import { cn } from "@/lib/utils";
9+
10+
type ASTNode = {
11+
readonly type: string;
12+
readonly [key: string]: unknown;
13+
};
14+
15+
export type HtmlAstTreeItemProperties = {
16+
readonly index: number;
17+
readonly data: ASTNode;
18+
readonly esqueryMatchedNodes: ASTNode[];
19+
};
20+
21+
export const HtmlAstTreeItem: FC<HtmlAstTreeItemProperties> = ({
22+
data,
23+
index,
24+
esqueryMatchedNodes,
25+
}) => {
26+
const isEsqueryMatchedNode = esqueryMatchedNodes.includes(data);
27+
28+
return (
29+
<AccordionItem
30+
value={`${index}-${data.type}`}
31+
className={cn(
32+
"border border-card rounded-lg overflow-hidden",
33+
isEsqueryMatchedNode && "border-primary border-4",
34+
)}
35+
>
36+
<AccordionTrigger className="text-sm bg-card px-4 py-3 capitalize">
37+
{data.type}
38+
</AccordionTrigger>
39+
<AccordionContent className="p-4 border-t">
40+
<div className="space-y-1">
41+
{Object.entries(data).map(item => (
42+
<TreeEntry
43+
key={item[0]}
44+
data={item}
45+
esqueryMatchedNodes={esqueryMatchedNodes}
46+
/>
47+
))}
48+
</div>
49+
</AccordionContent>
50+
</AccordionItem>
51+
);
52+
};

src/components/ast/html-ast.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { Accordion } from "@/components/ui/accordion";
2+
import { Editor } from "@/components/editor";
3+
import { useAST } from "@/hooks/use-ast";
4+
import { useExplorer } from "@/hooks/use-explorer";
5+
import {
6+
HtmlAstTreeItem,
7+
type HtmlAstTreeItemProperties,
8+
} from "./html-ast-tree-item";
9+
import type { FC } from "react";
10+
import { parseError } from "@/lib/parse-error";
11+
import { ErrorState } from "../error-boundary";
12+
13+
export const HtmlAst: FC = () => {
14+
const result = useAST();
15+
const { viewModes } = useExplorer();
16+
const { astView } = viewModes;
17+
18+
if (!result.ok) {
19+
const message = parseError(result.errors[0]);
20+
return <ErrorState message={message} />;
21+
}
22+
23+
const ast = JSON.stringify(result.ast, null, 2);
24+
25+
if (astView === "tree") {
26+
return (
27+
<Accordion
28+
type="multiple"
29+
className="px-8 pb-4 font-mono space-y-3 min-w-max"
30+
defaultValue={["0-Program"]}
31+
>
32+
<HtmlAstTreeItem
33+
data={result.ast as HtmlAstTreeItemProperties["data"]}
34+
index={0}
35+
esqueryMatchedNodes={
36+
result.esqueryMatchedNodes as HtmlAstTreeItemProperties["esqueryMatchedNodes"]
37+
}
38+
/>
39+
</Accordion>
40+
);
41+
}
42+
43+
return <Editor readOnly value={ast} />;
44+
};

src/components/ast/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { JavascriptAst } from "./javascript-ast";
55
import { JsonAst } from "./json-ast";
66
import { CssAst } from "./css-ast";
77
import { MarkdownAst } from "./markdown-ast";
8+
import { HtmlAst } from "./html-ast";
89
import type { FC } from "react";
910

1011
export const Ast: FC = () => {
@@ -17,6 +18,8 @@ export const Ast: FC = () => {
1718
return <JsonAst />;
1819
case "css":
1920
return <CssAst />;
21+
case "html":
22+
return <HtmlAst />;
2023
default:
2124
return <JavascriptAst />;
2225
}

src/components/editor.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { json } from "@codemirror/lang-json";
77
import { javascript } from "@codemirror/lang-javascript";
88
import { markdown } from "@codemirror/lang-markdown";
99
import { css } from "@codemirror/lang-css";
10+
import { html } from "@codemirror/lang-html";
1011
import { EditorView } from "@codemirror/view";
1112
import { EditorState } from "@codemirror/state";
1213
import clsx from "clsx";
@@ -27,6 +28,7 @@ const languageExtensions: Record<string, (isJSX?: boolean) => LanguageSupport> =
2728
json: () => json(),
2829
markdown: () => markdown(),
2930
css: () => css(),
31+
html: () => html(),
3032
};
3133

3234
type EditorProperties = {

src/components/options.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ const JavaScriptPanel = () => {
181181
);
182182
};
183183

184+
const HTMLPanel: React.FC = () => {
185+
return <></>;
186+
};
187+
184188
const Panel = ({ language }: { language: string }) => {
185189
switch (language) {
186190
case "json":
@@ -189,6 +193,8 @@ const Panel = ({ language }: { language: string }) => {
189193
return <MarkdownPanel />;
190194
case "css":
191195
return <CssPanel />;
196+
case "html":
197+
return <HTMLPanel />;
192198
default:
193199
return <JavaScriptPanel />;
194200
}

src/hooks/use-ast.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Node as EstreeNode } from "estree";
33
import css from "@eslint/css";
44
import json from "@eslint/json";
55
import markdown from "@eslint/markdown";
6+
import html from "@html-eslint/eslint-plugin";
67
import esquery from "esquery";
78
import { useExplorer } from "@/hooks/use-explorer";
89
import { assertIsUnreachable } from "@/lib/utils";
@@ -86,6 +87,17 @@ export function useAST() {
8687
break;
8788
}
8889

90+
case "html": {
91+
const language = html.languages.html;
92+
astParseResult = language.parse({
93+
body: code.html,
94+
path: "",
95+
physicalPath: "",
96+
bom: false,
97+
});
98+
break;
99+
}
100+
89101
default: {
90102
assertIsUnreachable(language);
91103
}

src/hooks/use-explorer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
} from "../lib/const";
1818
export type SourceType = Exclude<Options["sourceType"], undefined>;
1919
export type Version = Exclude<Options["ecmaVersion"], undefined>;
20-
export type Language = "javascript" | "json" | "markdown" | "css";
20+
export type Language = "javascript" | "json" | "markdown" | "css" | "html";
2121
export type JsonMode = "json" | "jsonc" | "json5";
2222
export type MarkdownMode = "commonmark" | "gfm";
2323
export type MarkdownFrontmatter = "off" | "yaml" | "toml";
@@ -28,6 +28,7 @@ export type Code = {
2828
json: string;
2929
markdown: string;
3030
css: string;
31+
html: string;
3132
};
3233
export type JsOptions = {
3334
parser: string;

0 commit comments

Comments
 (0)