Skip to content

Commit 7ad9038

Browse files
nyrosmithjasonLaster
authored andcommitted
Improve JSX syntax highlighting (firefox-devtools#4539)
1 parent 4bc0d5a commit 7ad9038

File tree

14 files changed

+148
-22
lines changed

14 files changed

+148
-22
lines changed

src/actions/ast.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import { PROMISE } from "./utils/middleware/promise";
1414
import {
1515
getSymbols,
1616
getEmptyLines,
17-
getOutOfScopeLocations
17+
getOutOfScopeLocations,
18+
isReactComponent
1819
} from "../workers/parser";
1920

2021
import { findBestMatchExpression } from "../utils/ast";
@@ -29,6 +30,29 @@ const extraProps = {
2930
react: { displayName: "this._reactInternalInstance.getName()" }
3031
};
3132

33+
export function setSourceMetaData(sourceId: SourceId) {
34+
return async ({ dispatch, getState }: ThunkArgs) => {
35+
const sourceRecord = getSource(getState(), sourceId);
36+
if (!sourceRecord) {
37+
return;
38+
}
39+
40+
const source = sourceRecord.toJS();
41+
if (!source.text || source.isWasm) {
42+
return;
43+
}
44+
45+
const isReactComp = await isReactComponent(source);
46+
dispatch({
47+
type: "SET_SOURCE_METADATA",
48+
sourceId: source.id,
49+
sourceMetaData: {
50+
isReactComponent: isReactComp
51+
}
52+
});
53+
};
54+
}
55+
3256
export function setSymbols(sourceId: SourceId) {
3357
return async ({ dispatch, getState }: ThunkArgs) => {
3458
const sourceRecord = getSource(getState(), sourceId);

src/actions/sources/loadSourceText.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// @flow
22
import { PROMISE } from "../utils/middleware/promise";
3-
import { setEmptyLines, setSymbols } from "../ast";
3+
import { setEmptyLines, setSymbols, setSourceMetaData } from "../ast";
44
import { getSource } from "../../selectors";
55
import { setSource } from "../../workers/parser";
66
import type { Source } from "../../types";
@@ -45,5 +45,6 @@ export function loadSourceText(source: Source) {
4545
await setSource(newSource);
4646
await dispatch(setSymbols(source.id));
4747
await dispatch(setEmptyLines(source.id));
48+
await dispatch(setSourceMetaData(source.id));
4849
};
4950
}

src/actions/tests/ast.spec.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ import {
66
} from "../../utils/test-head";
77

88
import readFixture from "./helpers/readFixture";
9-
const { getSymbols, getEmptyLines, getOutOfScopeLocations } = selectors;
9+
const {
10+
getSymbols,
11+
getEmptyLines,
12+
getOutOfScopeLocations,
13+
getSourceMetaData
14+
} = selectors;
1015
import getInScopeLines from "../../selectors/linesInScope";
1116

1217
const threadClient = {
@@ -31,7 +36,8 @@ const threadClient = {
3136
const sourceTexts = {
3237
"base.js": "function base(boo) {}",
3338
"foo.js": "function base(boo) { return this.bazz; } outOfScope",
34-
"scopes.js": readFixture("scopes.js")
39+
"scopes.js": readFixture("scopes.js"),
40+
"reactComponent.js": readFixture("reactComponent.js")
3541
};
3642

3743
const evaluationResult = {
@@ -51,6 +57,26 @@ describe("ast", () => {
5157
expect(emptyLines).toMatchSnapshot();
5258
});
5359
});
60+
describe("setSourceMetaData", () => {
61+
it("should detect react components", async () => {
62+
const { dispatch, getState } = createStore(threadClient);
63+
const source = makeSource("reactComponent.js");
64+
await dispatch(actions.newSource(source));
65+
await dispatch(actions.loadSourceText({ id: "reactComponent.js" }));
66+
67+
const sourceMetaData = getSourceMetaData(getState(), source.id);
68+
expect(sourceMetaData).toEqual({ isReactComponent: true });
69+
});
70+
it("should not give false positive on non react components", async () => {
71+
const { dispatch, getState } = createStore(threadClient);
72+
const source = makeSource("base.js");
73+
await dispatch(actions.newSource(source));
74+
await dispatch(actions.loadSourceText({ id: "base.js" }));
75+
76+
const sourceMetaData = getSourceMetaData(getState(), source.id);
77+
expect(sourceMetaData).toEqual({ isReactComponent: false });
78+
});
79+
});
5480
describe("setSymbols", () => {
5581
describe("when the source is loaded", () => {
5682
it("should be able to set symbols", async () => {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import React, { Component } from "react";
2+
3+
class FixtureComponent extends Component {
4+
render() {
5+
return null;
6+
}
7+
}

src/components/Editor/Breakpoint.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ function makeMarker(isDisabled: boolean) {
2424
type Props = {
2525
breakpoint: Object,
2626
selectedSource: Object,
27-
editor: Object
27+
editor: Object,
28+
sourceMetaData: Object
2829
};
2930

3031
class Breakpoint extends Component<Props> {
@@ -36,7 +37,7 @@ class Breakpoint extends Component<Props> {
3637
}
3738

3839
addBreakpoint() {
39-
const { breakpoint, editor, selectedSource } = this.props;
40+
const { breakpoint, editor, selectedSource, sourceMetaData } = this.props;
4041

4142
// Hidden Breakpoints are never rendered on the client
4243
if (breakpoint.hidden) {
@@ -52,7 +53,7 @@ class Breakpoint extends Component<Props> {
5253
const sourceId = selectedSource.get("id");
5354
const line = toEditorLine(sourceId, breakpoint.location.line);
5455

55-
showSourceText(editor, selectedSource.toJS());
56+
showSourceText(editor, selectedSource.toJS(), sourceMetaData);
5657

5758
editor.codeMirror.setGutterMarker(
5859
line,

src/components/Editor/Breakpoints.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import React, { Component } from "react";
66
import Breakpoint from "./Breakpoint";
77

88
import actions from "../../actions";
9-
import { getSelectedSource } from "../../selectors";
9+
import { getSelectedSource, getSourceMetaData } from "../../selectors";
1010
import getVisibleBreakpoints from "../../selectors/visibleBreakpoints";
1111
import { makeLocationId } from "../../utils/breakpoint";
1212
import { isLoaded } from "../../utils/source";
@@ -16,7 +16,8 @@ import type { SourceRecord, BreakpointsMap } from "../../reducers/types";
1616
type Props = {
1717
selectedSource: SourceRecord,
1818
breakpoints: BreakpointsMap,
19-
editor: Object
19+
editor: Object,
20+
sourceMetaData: Object
2021
};
2122

2223
class Breakpoints extends Component<Props> {
@@ -32,7 +33,7 @@ class Breakpoints extends Component<Props> {
3233
}
3334

3435
render() {
35-
const { breakpoints, selectedSource, editor } = this.props;
36+
const { breakpoints, selectedSource, editor, sourceMetaData } = this.props;
3637

3738
if (!selectedSource || !breakpoints || selectedSource.get("isBlackBoxed")) {
3839
return null;
@@ -46,6 +47,7 @@ class Breakpoints extends Component<Props> {
4647
key={makeLocationId(bp.location)}
4748
breakpoint={bp}
4849
selectedSource={selectedSource}
50+
sourceMetaData={sourceMetaData}
4951
editor={editor}
5052
/>
5153
);
@@ -58,7 +60,8 @@ class Breakpoints extends Component<Props> {
5860
export default connect(
5961
state => ({
6062
breakpoints: getVisibleBreakpoints(state),
61-
selectedSource: getSelectedSource(state)
63+
selectedSource: getSelectedSource(state),
64+
sourceMetaData: getSourceMetaData(state, getSelectedSource(state).id)
6265
}),
6366
dispatch => bindActionCreators(actions, dispatch)
6467
)(Breakpoints);

src/components/Editor/index.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
getSelectedSource,
1717
getHitCountForSource,
1818
getCoverageEnabled,
19+
getSourceMetaData,
1920
getConditionalPanelLine
2021
} from "../../selectors";
2122

@@ -35,6 +36,7 @@ import EmptyLines from "./EmptyLines";
3536
import GutterMenu from "./GutterMenu";
3637
import EditorMenu from "./EditorMenu";
3738
import ConditionalPanel from "./ConditionalPanel";
39+
import type { SourceMetaDataType } from "../../reducers/ast";
3840

3941
import {
4042
showSourceText,
@@ -74,6 +76,7 @@ type Props = {
7476
startPanelSize: number,
7577
endPanelSize: number,
7678
conditionalPanelLine: number,
79+
sourceMetaData: SourceMetaDataType,
7780

7881
// Actions
7982
openConditionalPanel: number => void,
@@ -463,7 +466,7 @@ class Editor extends PureComponent<Props, State> {
463466
}
464467

465468
setText(props) {
466-
const { selectedSource } = props;
469+
const { selectedSource, sourceMetaData } = props;
467470
if (!this.state.editor) {
468471
return;
469472
}
@@ -481,7 +484,11 @@ class Editor extends PureComponent<Props, State> {
481484
}
482485

483486
if (selectedSource) {
484-
return showSourceText(this.state.editor, selectedSource.toJS());
487+
return showSourceText(
488+
this.state.editor,
489+
selectedSource.toJS(),
490+
sourceMetaData
491+
);
485492
}
486493
}
487494

@@ -607,7 +614,8 @@ const mapStateToProps = state => {
607614
hitCount: getHitCountForSource(state, sourceId),
608615
selectedFrame: getSelectedFrame(state),
609616
coverageOn: getCoverageEnabled(state),
610-
conditionalPanelLine: getConditionalPanelLine(state)
617+
conditionalPanelLine: getConditionalPanelLine(state),
618+
sourceMetaData: getSourceMetaData(state, sourceId)
611619
};
612620
};
613621

src/reducers/ast.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ type EmptyLinesType = number[];
2222
export type SymbolsMap = Map<string, SymbolDeclarations>;
2323
export type EmptyLinesMap = Map<string, EmptyLinesType>;
2424

25+
export type SourceMetaDataType = {
26+
isReactComponent: boolean
27+
};
28+
29+
export type SourceMetaDataMap = Map<string, SourceMetaDataType>;
30+
2531
export type Preview =
2632
| {| updating: true |}
2733
| null
@@ -38,7 +44,8 @@ export type ASTState = {
3844
symbols: SymbolsMap,
3945
emptyLines: EmptyLinesMap,
4046
outOfScopeLocations: ?Array<AstLocation>,
41-
preview: Preview
47+
preview: Preview,
48+
sourceMetaData: SourceMetaDataMap
4249
};
4350

4451
export function initialState() {
@@ -47,7 +54,8 @@ export function initialState() {
4754
symbols: I.Map(),
4855
emptyLines: I.Map(),
4956
outOfScopeLocations: null,
50-
preview: null
57+
preview: null,
58+
sourceMetaData: I.Map()
5159
}: ASTState)
5260
)();
5361
}
@@ -111,6 +119,13 @@ function update(
111119
return initialState();
112120
}
113121

122+
case "SET_SOURCE_METADATA": {
123+
return state.setIn(
124+
["sourceMetaData", action.sourceId],
125+
action.sourceMetaData
126+
);
127+
}
128+
114129
default: {
115130
return state;
116131
}
@@ -167,4 +182,8 @@ export function getPreview(state: OuterState) {
167182
return state.ast.get("preview");
168183
}
169184

185+
export function getSourceMetaData(state: OuterState, sourceId: string) {
186+
return state.ast.getIn(["sourceMetaData", sourceId]) || {};
187+
}
188+
170189
export default update;

src/utils/editor/source-documents.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { getMode } from "../source";
55
import type { Source } from "debugger-html";
66
import { isWasm, getWasmLineNumberFormatter, renderWasmText } from "../wasm";
77
import { resizeBreakpointGutter, resizeToggleButton } from "../ui";
8+
import type { SourceMetaDataType } from "../../reducers/ast";
89

910
let sourceDocs = {};
1011

@@ -81,19 +82,25 @@ function setEditorText(editor: Object, source: Source) {
8182
* Handle getting the source document or creating a new
8283
* document with the correct mode and text.
8384
*/
84-
function showSourceText(editor: Object, source: Source) {
85+
function showSourceText(
86+
editor: Object,
87+
source: Source,
88+
sourceMetaData: SourceMetaDataType
89+
) {
8590
if (!source) {
8691
return;
8792
}
8893

8994
let doc = getDocument(source.id);
9095
if (editor.codeMirror.doc === doc) {
96+
editor.setMode(getMode(source, sourceMetaData));
9197
return;
9298
}
9399

94100
if (doc) {
95101
editor.replaceDocument(doc);
96102
updateLineNumberFormat(editor, source.id);
103+
editor.setMode(getMode(source, sourceMetaData));
97104
return doc;
98105
}
99106

@@ -102,7 +109,7 @@ function showSourceText(editor: Object, source: Source) {
102109
editor.replaceDocument(doc);
103110

104111
setEditorText(editor, source);
105-
editor.setMode(getMode(source));
112+
editor.setMode(getMode(source, sourceMetaData));
106113
updateLineNumberFormat(editor, source.id);
107114
}
108115

src/utils/source.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { basename } from "../utils/path";
1111
import { parse as parseURL } from "url";
1212

1313
import type { Source } from "../types";
14+
import type { SourceMetaDataType } from "../reducers/ast";
1415

1516
type transformUrlCallback = string => string;
1617

@@ -204,13 +205,20 @@ function getSourceLineCount(source: Source) {
204205
* @static
205206
*/
206207

207-
function getMode(source: Source) {
208+
function getMode(source: Source, sourceMetaData: SourceMetaDataType) {
208209
const { contentType, text, isWasm, url } = source;
209210

210211
if (!text || isWasm) {
211212
return { name: "text" };
212213
}
213214

215+
if (
216+
(url && url.match(/\.jsx$/i)) ||
217+
(sourceMetaData && sourceMetaData.isReactComponent)
218+
) {
219+
return "jsx";
220+
}
221+
214222
// if the url ends with .marko we set the name to Javascript so
215223
// syntax highlighting works for marko too
216224
if (url && url.match(/\.marko$/i)) {

0 commit comments

Comments
 (0)