Skip to content

Commit 23ce52b

Browse files
committed
Add Tree MVP
1 parent b44848d commit 23ce52b

File tree

5 files changed

+142
-2
lines changed

5 files changed

+142
-2
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
"@testing-library/jest-dom": "^5.11.4",
99
"@testing-library/react": "^11.1.0",
1010
"@testing-library/user-event": "^12.1.10",
11+
"@types/classnames": "^2.3.1",
1112
"@types/esquery": "^1.0.2",
1213
"@types/jest": "^26.0.15",
1314
"@types/node": "^12.0.0",
1415
"@types/react": "^17.0.0",
1516
"@types/react-dom": "^17.0.0",
17+
"classnames": "^2.3.1",
1618
"esquery": "^1.4.0",
1719
"monaco-editor": "^0.30.0",
1820
"prettier": "^2.3.2",

src/App.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { FC, useCallback, useEffect, useState } from 'react';
2-
import { Node } from 'typescript';
2+
import { Node, ScriptKind } from 'typescript';
3+
import { tsquery } from '@phenomnomnominal/tsquery';
34
import './index.css';
45
import { queryCode } from './engine';
56
import QueryEditor from './QueryEditor';
67
import Code, { HighlightedInterval, HighlightedIntervals } from './Code';
78
import { isSyntaxError } from './tsquery-util';
89
import Header from './Header';
10+
import Tree from './Tree';
911

1012
const REG_EXPS: Record<string, RegExp> = {
1113
AllLineBreaks: /\n/g,
@@ -52,14 +54,18 @@ const App: FC = () => {
5254
}
5355
}, [query, code]);
5456

55-
// Layout
57+
/** @todo move */
58+
const node = tsquery.ast(code, undefined, ScriptKind.JSX);
59+
5660
return (
5761
<div className="App">
5862
<Header />
5963
<h2>Query</h2>
6064
<QueryEditor onChange={handleQueryChange} />
6165
<h2>Code</h2>
6266
<Code highlighted={highlightedIntervals} onChange={handleCodeChange} />
67+
<h2>Tree</h2>
68+
{node && <Tree node={node} />}
6369
</div>
6470
);
6571
};

src/Tree.css

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
.toggable {
2+
position: relative;
3+
}
4+
5+
.toggable::before {
6+
content: '+';
7+
color: lightgreen;
8+
position: absolute;
9+
left: -10px;
10+
}
11+
12+
.toggable.open::before {
13+
content: '-';
14+
color: pink;
15+
}
16+
17+
.nc {
18+
color: lightblue;
19+
}
20+
21+
.nc:hover {
22+
text-decoration: underline;
23+
cursor: pointer;
24+
}
25+
26+
.nb {
27+
color: orange;
28+
}
29+
30+
.p {
31+
color: gray;
32+
}
33+
34+
.s {
35+
color: lightcyan;
36+
}

src/Tree.tsx

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { FC, useCallback, useState } from 'react';
2+
import classNames from 'classnames';
3+
import { Node, SyntaxKind } from 'typescript';
4+
import './Tree.css';
5+
6+
const Tree: FC<{ node: Node }> = ({ node }) => {
7+
return (
8+
<pre>
9+
<TreeNode node={node} level={0} />
10+
</pre>
11+
);
12+
};
13+
14+
export default Tree;
15+
16+
const TreeNode: FC<{ node: Node; level: number }> = ({ node, level }) => {
17+
const [open, setOpen] = useState(false);
18+
const handleClick = useCallback(() => setOpen((open) => !open), []);
19+
return (
20+
<>
21+
<span onClick={handleClick} className={classNames('toggable', { open })}>
22+
<span className="nc">{SyntaxKind[node.kind]}</span> <span className="p">{'{'}</span>
23+
</span>
24+
{open &&
25+
Object.entries(node).map(([key, value]) => {
26+
if (key === 'parent') {
27+
return null;
28+
}
29+
return (
30+
<div key={key}>
31+
{'\t'.repeat(level + 1)}
32+
<PropertyName name={key} />:{' '}
33+
{isNode(value) ? (
34+
<TreeNode node={value} level={level + 1} />
35+
) : Array.isArray(value) ? (
36+
<TreeNodeArray array={value} level={level + 1} />
37+
) : (
38+
<PrimitiveValue value={value} />
39+
)}
40+
</div>
41+
);
42+
})}
43+
<span>
44+
{open && '\t'.repeat(level)}
45+
<span className="p">{'}'}</span>
46+
</span>
47+
</>
48+
);
49+
};
50+
51+
const TreeNodeArray: FC<{ array: unknown[]; level: number }> = ({ array, level }) => {
52+
const [open, setOpen] = useState(true);
53+
const handleClick = useCallback(() => setOpen((open) => !open), []);
54+
if (!array.length) {
55+
return <span className="p">[]</span>;
56+
}
57+
return (
58+
<>
59+
<span onClick={handleClick} className={classNames('toggable', { open })}>
60+
<span className="p">[</span>
61+
</span>
62+
{open &&
63+
array.map((item, i) => (
64+
<div key={i}>
65+
{'\t'.repeat(level + 1)}
66+
{isNode(item) ? <TreeNode node={item} level={level + 1} /> : <PrimitiveValue value={item} />}
67+
</div>
68+
))}
69+
<span className="p">{open && '\t'.repeat(level)}]</span>
70+
</>
71+
);
72+
};
73+
74+
const PrimitiveValue: FC<{ value: unknown }> = ({ value }) => {
75+
return <span className="s">{typeof value === 'string' ? JSON.stringify(value) : String(value)}</span>;
76+
};
77+
78+
const PropertyName: FC<{ name: string }> = ({ name }) => {
79+
return <span className="nb">{name}</span>;
80+
};
81+
82+
function isNode(value: unknown): value is Node {
83+
return Boolean(value) && typeof value === 'object' && Boolean(SyntaxKind[(value as Node).kind]);
84+
}

yarn.lock

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1802,6 +1802,13 @@
18021802
dependencies:
18031803
"@babel/types" "^7.3.0"
18041804

1805+
"@types/classnames@^2.3.1":
1806+
version "2.3.1"
1807+
resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.3.1.tgz#3c2467aa0f1a93f1f021e3b9bcf938bd5dfdc0dd"
1808+
integrity sha512-zeOWb0JGBoVmlQoznvqXbE0tEC/HONsnoUNH19Hc96NFsTAwTXbTqb8FMYkru1F/iqp7a18Ws3nWJvtA1sHD1A==
1809+
dependencies:
1810+
classnames "*"
1811+
18051812
"@types/eslint@^7.28.2":
18061813
version "7.29.0"
18071814
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.29.0.tgz#e56ddc8e542815272720bb0b4ccc2aff9c3e1c78"
@@ -3390,6 +3397,11 @@ class-utils@^0.3.5:
33903397
isobject "^3.0.0"
33913398
static-extend "^0.1.1"
33923399

3400+
classnames@*, classnames@^2.3.1:
3401+
version "2.3.1"
3402+
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
3403+
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
3404+
33933405
clean-css@^4.2.3:
33943406
version "4.2.4"
33953407
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.4.tgz#733bf46eba4e607c6891ea57c24a989356831178"

0 commit comments

Comments
 (0)