Skip to content

Commit d8675cc

Browse files
authored
Merge pull request #283 from carlosms/uast2-modes
UAST mode selector
2 parents bca82c0 + a977e18 commit d8675cc

File tree

12 files changed

+597
-28
lines changed

12 files changed

+597
-28
lines changed

docs/rest-api.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ curl -X POST \
212212

213213
## POST /parse
214214

215-
Receives a file content and returns UAST.
215+
Receives a file content and returns UAST parsed by the bblfsh server.
216216

217217
```bash
218218
curl -X POST \
@@ -221,6 +221,7 @@ curl -X POST \
221221
-d '{
222222
"language": "javascript",
223223
"content": "console.log(test)"
224+
"mode": "annotated"
224225
}'
225226
```
226227

@@ -243,8 +244,11 @@ curl -X POST \
243244
}
244245
```
245246

246-
The endpoint also receives additional parameters:
247+
The endpoint accepts these parameters:
247248

249+
- `language`: Language name.
250+
- `content`: The file contents to parse.
251+
- `mode`: Transformation mode. Can be one of `native`, `annotated`, `semantic`. The default is `semantic`.
248252
- `serverUrl` - allows to override bblfsh server URL.
249253
- `filename` - can be used instead of language. Then the bblfsh server would try to guess the language.
250254
- `filter` - [xpath query](https://doc.bblf.sh/user/uast-querying.html) to filter the results.

frontend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
"format": "eslint --fix 'src/**/*.js'"
2929
},
3030
"devDependencies": {
31+
"enzyme": "^3.7.0",
32+
"enzyme-adapter-react-16": "^1.7.0",
3133
"eslint-config-airbnb-base": "^12.1.0",
3234
"eslint-config-prettier": "^2.9.0",
3335
"eslint-plugin-jest": "^21.15.1",

frontend/src/api.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,16 @@ function detectLang(content, filename) {
135135
}).then(res => res.data);
136136
}
137137

138-
function parseCode(language, content, filter, customServerUrl) {
138+
const defaultUastMode = 'semantic';
139+
const uastModes = ['semantic', 'annotated', 'native'];
140+
141+
function parseCode(language, content, mode, filter, customServerUrl) {
139142
return apiCall('/parse', {
140143
method: 'POST',
141144
body: {
142145
language,
143146
content,
147+
mode,
144148
filter,
145149
serverUrl: customServerUrl
146150
}
@@ -178,5 +182,7 @@ export default {
178182
parseCode,
179183
getLanguages,
180184
filterUAST,
181-
version
185+
version,
186+
uastModes,
187+
defaultUastMode
182188
};

frontend/src/components/CodeViewer.js

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ function EditorUASTSpitPane({
5454
handleLangChange,
5555
handleShowLocationsChange,
5656
handleFilterChange,
57-
handleSearch
57+
handleSearch,
58+
mode,
59+
handleModeChange
5860
}) {
5961
return (
6062
<SplitPane split="vertical" defaultSize={500} minSize={1} maxSize={-15}>
@@ -72,6 +74,8 @@ function EditorUASTSpitPane({
7274
handleShowLocationsChange={handleShowLocationsChange}
7375
handleFilterChange={handleFilterChange}
7476
handleSearch={handleSearch}
77+
mode={mode}
78+
handleModeChange={handleModeChange}
7579
/>
7680
</SplitPane>
7781
);
@@ -87,7 +91,9 @@ EditorUASTSpitPane.propTypes = {
8791
handleLangChange: PropTypes.func.isRequired,
8892
handleShowLocationsChange: PropTypes.func.isRequired,
8993
handleFilterChange: PropTypes.func.isRequired,
90-
handleSearch: PropTypes.func.isRequired
94+
handleSearch: PropTypes.func.isRequired,
95+
mode: PropTypes.string.isRequired,
96+
handleModeChange: PropTypes.func.isRequired
9197
};
9298

9399
const EditorWithUAST = withUASTEditor(EditorUASTSpitPane);
@@ -104,7 +110,8 @@ class CodeViewer extends Component {
104110
uast: null,
105111
error: null,
106112
showLocations: false,
107-
filter: ''
113+
filter: '',
114+
mode: api.defaultUastMode
108115
};
109116

110117
this.handleLangChange = this.handleLangChange.bind(this);
@@ -113,6 +120,7 @@ class CodeViewer extends Component {
113120
this.removeError = this.removeError.bind(this);
114121
this.handleShowLocationsChange = this.handleShowLocationsChange.bind(this);
115122
this.handleFilterChange = this.handleFilterChange.bind(this);
123+
this.handleModeChange = this.handleModeChange.bind(this);
116124
}
117125

118126
componentDidMount() {
@@ -154,7 +162,12 @@ class CodeViewer extends Component {
154162
this.setState({ error: null, uast: null, uastLoading: true });
155163

156164
api
157-
.parseCode(this.state.language, this.props.code, this.state.filter)
165+
.parseCode(
166+
this.state.language,
167+
this.props.code,
168+
this.state.mode,
169+
this.state.filter
170+
)
158171
.then(res => {
159172
this.setState({ uast: res });
160173
})
@@ -176,6 +189,13 @@ class CodeViewer extends Component {
176189
this.setState({ filter: e.target.value });
177190
}
178191

192+
handleModeChange(mode) {
193+
this.setState({ mode });
194+
if (this.state.showUast) {
195+
this.parseCode();
196+
}
197+
}
198+
179199
render() {
180200
const { showModal, onHide, code, languages } = this.props;
181201
const {
@@ -186,7 +206,8 @@ class CodeViewer extends Component {
186206
uast,
187207
error,
188208
showLocations,
189-
filter
209+
filter,
210+
mode
190211
} = this.state;
191212

192213
if (loading) {
@@ -230,6 +251,8 @@ class CodeViewer extends Component {
230251
handleShowLocationsChange={this.handleShowLocationsChange}
231252
handleFilterChange={this.handleFilterChange}
232253
handleSearch={this.parseCode}
254+
mode={mode}
255+
handleModeChange={this.handleModeChange}
233256
/>
234257
{error ? (
235258
<div className="error">
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import api from '../api';
4+
5+
function ParseModeSwitcher({ mode, handleModeChange }) {
6+
return (
7+
<div style={{ padding: '20px' }}>
8+
{api.uastModes.map(m => (
9+
<label
10+
key={m}
11+
style={{
12+
marginRight: '20px',
13+
textTransform: 'capitalize'
14+
}}
15+
>
16+
<input
17+
type="radio"
18+
value={m}
19+
checked={mode === m}
20+
onChange={e => handleModeChange(e.target.value)}
21+
/>{' '}
22+
{m}
23+
</label>
24+
))}
25+
</div>
26+
);
27+
}
28+
29+
ParseModeSwitcher.propTypes = {
30+
mode: PropTypes.string.isRequired,
31+
handleModeChange: PropTypes.func.isRequired
32+
};
33+
34+
export default ParseModeSwitcher;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from 'react';
2+
import { shallow } from 'enzyme';
3+
import renderer from 'react-test-renderer';
4+
import ParseModeSwitcher from './ParseModeSwitcher';
5+
6+
test('it renders correctly', () => {
7+
const tree = renderer
8+
.create(
9+
<ParseModeSwitcher mode={'semantic'} handleModeChange={() => null} />
10+
)
11+
.toJSON();
12+
13+
expect(tree).toMatchSnapshot();
14+
});
15+
16+
test('it calls handleModeChange when the input is clicked', () => {
17+
const spy = jest.fn();
18+
const wrapper = shallow(
19+
<ParseModeSwitcher mode={'semantic'} handleModeChange={spy} />
20+
);
21+
wrapper
22+
.find('[value="annotated"]')
23+
.simulate('change', { target: { value: 'annotated' } });
24+
expect(spy.mock.calls.length).toBe(1);
25+
});

frontend/src/components/UASTViewerPane.js

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
33
import UASTViewer from 'uast-viewer';
44
import { Button } from 'react-bootstrap';
55
import './UASTViewerPane.less';
6+
import ParseModeSwitcher from './ParseModeSwitcher';
67

78
const ROOT_ID = 1;
89

@@ -34,7 +35,9 @@ function UASTViewerPane({
3435
filter,
3536
handleShowLocationsChange,
3637
handleFilterChange,
37-
handleSearch
38+
handleSearch,
39+
mode,
40+
handleModeChange
3841
}) {
3942
let content = null;
4043

@@ -57,6 +60,15 @@ function UASTViewerPane({
5760
}
5861
}
5962

63+
let modeSwitcher = null;
64+
if (mode) {
65+
modeSwitcher = (
66+
<div className="uast-mode-wrapper">
67+
<ParseModeSwitcher mode={mode} handleModeChange={handleModeChange} />
68+
</div>
69+
);
70+
}
71+
6072
return (
6173
<div className="uast-viewer-pane">
6274
<div className="show-locations-wrapper">
@@ -86,7 +98,6 @@ function UASTViewerPane({
8698
SEARCH
8799
</Button>
88100
<Button
89-
className="edit-query"
90101
bsStyle="gbpl-primary-tint-2-link"
91102
href="https://doc.bblf.sh/using-babelfish/uast-querying.html"
92103
target="_blank"
@@ -95,6 +106,7 @@ function UASTViewerPane({
95106
</Button>
96107
</form>
97108
</div>
109+
{modeSwitcher}
98110
{content}
99111
</div>
100112
);
@@ -107,7 +119,11 @@ UASTViewerPane.propTypes = {
107119
filter: PropTypes.string,
108120
handleShowLocationsChange: PropTypes.func.isRequired,
109121
handleFilterChange: PropTypes.func.isRequired,
110-
handleSearch: PropTypes.func.isRequired
122+
handleSearch: PropTypes.func.isRequired,
123+
// If mode is empty the mode selector will be hidden
124+
mode: PropTypes.string,
125+
// Mandatory if mode is set
126+
handleModeChange: PropTypes.func
111127
};
112128

113129
export default UASTViewerPane;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`it renders correctly 1`] = `
4+
<div
5+
style={
6+
Object {
7+
"padding": "20px",
8+
}
9+
}
10+
>
11+
<label
12+
style={
13+
Object {
14+
"marginRight": "20px",
15+
"textTransform": "capitalize",
16+
}
17+
}
18+
>
19+
<input
20+
checked={true}
21+
onChange={[Function]}
22+
type="radio"
23+
value="semantic"
24+
/>
25+
26+
semantic
27+
</label>
28+
<label
29+
style={
30+
Object {
31+
"marginRight": "20px",
32+
"textTransform": "capitalize",
33+
}
34+
}
35+
>
36+
<input
37+
checked={false}
38+
onChange={[Function]}
39+
type="radio"
40+
value="annotated"
41+
/>
42+
43+
annotated
44+
</label>
45+
<label
46+
style={
47+
Object {
48+
"marginRight": "20px",
49+
"textTransform": "capitalize",
50+
}
51+
}
52+
>
53+
<input
54+
checked={false}
55+
onChange={[Function]}
56+
type="radio"
57+
value="native"
58+
/>
59+
60+
native
61+
</label>
62+
</div>
63+
`;

frontend/src/setupTests.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import path from 'path';
22
import os from 'os';
33
import 'abortcontroller-polyfill/dist/polyfill-patch-fetch';
4+
import { configure } from 'enzyme';
5+
import Adapter from 'enzyme-adapter-react-16';
46
import initButtonStyles from './utils/bootstrap';
57

8+
configure({ adapter: new Adapter() });
9+
610
const { LocalStorage } = require('node-localstorage');
711
// URL API for node
812
const { URL } = require('whatwg-url');

0 commit comments

Comments
 (0)