diff --git a/configs/firefox-panel.json b/configs/firefox-panel.json index 78608fdb9e..e8337d662f 100644 --- a/configs/firefox-panel.json +++ b/configs/firefox-panel.json @@ -17,6 +17,7 @@ "codeCoverage": { "enabled": false }, "codeFolding": { "enabled": false }, "searchNav": { "enabled": true }, - "collapseFrame": { "enabled": true } + "collapseFrame": { "enabled": true }, + "columnBreakpoints": { "enabled": true } } } diff --git a/src/actions/navigation.js b/src/actions/navigation.js index e9341b78f9..7a28118f7d 100644 --- a/src/actions/navigation.js +++ b/src/actions/navigation.js @@ -2,6 +2,7 @@ import { clearDocuments } from "../utils/editor"; import { getSources } from "../reducers/sources"; import { waitForMs } from "../utils/utils"; import { newSources } from "./sources"; +import { clearSymbols } from "../utils/parser"; /** * Redux actions for the navigation state @@ -16,6 +17,7 @@ export function willNavigate(_, event) { return async function({ dispatch, getState, client, sourceMaps }: ThunkArgs) { await sourceMaps.clearSourceMaps(); clearDocuments(); + clearSymbols(); dispatch(navigate(event.url)); }; diff --git a/src/actions/tests/__snapshots__/ast.js.snap b/src/actions/tests/__snapshots__/ast.js.snap index bc1659f2fc..240efb90ad 100644 --- a/src/actions/tests/__snapshots__/ast.js.snap +++ b/src/actions/tests/__snapshots__/ast.js.snap @@ -65,6 +65,7 @@ Object { exports[`ast setSymbols when the source is loaded should be able to set symbols 1`] = ` Object { + "callExpressions": Array [], "comments": Array [], "functions": Array [ Object { diff --git a/src/components/Editor/CallSite.css b/src/components/Editor/CallSite.css new file mode 100644 index 0000000000..165556a182 --- /dev/null +++ b/src/components/Editor/CallSite.css @@ -0,0 +1,46 @@ +.call-site { + background: #f0f9ff; + cursor: pointer; + position: relative; +} + +.call-site::before { + content: ""; + position: absolute; + width: 100%; + height: calc(100% - 2px); + border-bottom: 2px solid #aed3ef; +} + +.call-site-bp { + cursor: pointer; + position: relative; +} + +.debug-expression.call-site-bp, .call-site-bp { + background-color: #fce7e7; +} + +.call-site-bp::before { + content: ""; + position: absolute; + width: 100%; + height: calc(100% - 2px); + border-bottom: 2px solid red; +} + +.theme-dark .call-site { + background-color: #4b5462; +} + +.theme-dark .call-site::before { + border-bottom-color: #5f78a4; +} + +.theme-dark .call-site-bp { + background-color: #4b3f3f; +} + +.theme-dark .call-site-bp::before { + border-bottom-color: #dd4d4d; +} diff --git a/src/components/Editor/CallSite.js b/src/components/Editor/CallSite.js new file mode 100644 index 0000000000..091832df22 --- /dev/null +++ b/src/components/Editor/CallSite.js @@ -0,0 +1,95 @@ +// @flow +import { Component } from "react"; + +import { markText } from "../../utils/editor"; +require("./CallSite.css"); + +type MarkerType = { + clear: Function +}; + +type props = { + callSite: Object, + editor: Object, + breakpoint: Object, + showCallSite: Boolean +}; +export default class CallSite extends Component { + props: props; + + addCallSite: Function; + marker: ?MarkerType; + + constructor() { + super(); + + this.marker = undefined; + const self: any = this; + self.addCallSite = this.addCallSite.bind(this); + self.clearCallSite = this.clearCallSite.bind(this); + } + + addCallSite(nextProps: props) { + const { editor, callSite, breakpoint } = nextProps || this.props; + const className = !breakpoint ? "call-site" : "call-site-bp"; + this.marker = markText(editor, className, callSite.location); + } + + clearCallSite() { + if (this.marker) { + this.marker.clear(); + this.marker = null; + } + } + + shouldComponentUpdate(nextProps: any) { + return this.props.editor !== nextProps.editor; + } + + componentDidMount() { + const { breakpoint, editor, showCallSite } = this.props; + if (!editor) { + return; + } + + if (!breakpoint && !showCallSite) { + return; + } + + this.addCallSite(); + } + + componentWillReceiveProps(nextProps: props) { + const { breakpoint, showCallSite } = this.props; + + if (nextProps.breakpoint !== breakpoint) { + if (this.marker) { + this.marker.clear(); + } + this.addCallSite(nextProps); + } + + if (nextProps.showCallSite !== showCallSite) { + if (nextProps.showCallSite) { + if (!this.marker) { + this.addCallSite(); + } + } else if (!nextProps.breakpoint) { + this.clearCallSite(); + } + } + } + + componentWillUnmount() { + if (!this.props.editor || !this.marker) { + return; + } + this.marker.clear(); + } + + render() { + return null; + } +} + +CallSite.displayName = "CallSite"; diff --git a/src/components/Editor/CallSites.js b/src/components/Editor/CallSites.js new file mode 100644 index 0000000000..1e3c0e8f21 --- /dev/null +++ b/src/components/Editor/CallSites.js @@ -0,0 +1,229 @@ +import { Component, createFactory, DOM as dom } from "react"; +import { connect } from "react-redux"; +import { bindActionCreators } from "redux"; +import { isEnabled } from "devtools-config"; + +import range from "lodash/range"; +import keyBy from "lodash/keyBy"; +import find from "lodash/find"; +import isEqualWith from "lodash/isEqualWith"; + +import _CallSite from "./CallSite"; +const CallSite = createFactory(_CallSite); + +import { + getSelectedSource, + getSymbols, + getSelectedLocation, + getBreakpointsForSource +} from "../../selectors"; + +import { getTokenLocation } from "../../utils/editor"; + +import actions from "../../actions"; + +function getCallSiteAtLocation(callSites, location) { + return find(callSites, callSite => + isEqualWith(callSite.location, location, (cloc, loc) => { + return ( + loc.line === cloc.start.line && + (loc.column >= cloc.start.column && loc.column <= cloc.end.column) + ); + }) + ); +} + +class CallSites extends Component { + props: { + symbols: Array, + callSites: Array, + editor: Object, + addBreakpoint: Function, + removeBreakpoint: Function, + selectedSource: Object, + selectedLocation: Object + }; + + constructor(props) { + super(props); + this.onKeyDown = this.onKeyDown.bind(this); + this.onKeyUp = this.onKeyUp.bind(this); + + this.state = { + showCallSites: false + }; + } + + componentDidMount() { + const { editor } = this.props; + const codeMirrorWrapper = editor.codeMirror.getWrapperElement(); + + codeMirrorWrapper.addEventListener("click", e => this.onTokenClick(e)); + document.body.addEventListener("keydown", this.onKeyDown); + document.body.addEventListener("keyup", this.onKeyUp); + } + + componentDidUnMount() { + const { editor } = this.props; + const codeMirrorWrapper = editor.codeMirror.getWrapperElement(); + + codeMirrorWrapper.addEventListener("click", e => this.onTokenClick(e)); + document.body.removeEventListener("keydown", e => this.onKeyDown); + document.body.removeEventListener("keyup", this.onKeyUp); + } + + onKeyUp(e) { + if (e.key === "Alt") { + e.preventDefault(); + this.setState({ showCallSites: false }); + } + } + + onKeyDown(e) { + if (e.key === "Alt") { + e.preventDefault(); + this.setState({ showCallSites: true }); + } + } + + onTokenClick(e) { + const { target } = e; + const { editor } = this.props; + + if ( + !isEnabled("columnBreakpoints") || + !e.altKey || + (!target.classList.contains("call-site") && + !target.classList.contains("call-site-bp")) + ) { + return; + } + + const { line, column } = getTokenLocation(editor.codeMirror, target); + + this.toggleBreakpoint(line + 1, column - 2); + } + + toggleBreakpoint(line, column = undefined) { + const { + selectedSource, + selectedLocation, + addBreakpoint, + removeBreakpoint, + callSites + } = this.props; + + const callSite = getCallSiteAtLocation(callSites, { line, column }); + + if (!callSite) { + return; + } + + const bp = callSite.breakpoint; + + if ((bp && bp.loading) || !selectedLocation || !selectedSource) { + return; + } + + const { sourceId } = selectedLocation; + + if (bp) { + // NOTE: it's possible the breakpoint has slid to a column + column = column || bp.location.column; + removeBreakpoint({ + sourceId: sourceId, + line: line, + column + }); + } else { + addBreakpoint({ + sourceId: sourceId, + sourceUrl: selectedSource.get("url"), + line: line, + column: column + }); + } + } + + render() { + const { editor, callSites } = this.props; + const { showCallSites } = this.state; + let sites; + if (!callSites) { + return null; + } + + editor.codeMirror.operation(() => { + sites = dom.div( + {}, + callSites.map((callSite, index) => { + return CallSite({ + key: index, + callSite, + editor, + breakpoint: callSite.breakpoint, + showCallSite: showCallSites + }); + }) + ); + }); + return sites; + } +} + +CallSites.displayName = "CallSites"; +function getCallSites(symbols, breakpoints) { + if (!symbols || !symbols.callExpressions) { + return; + } + + const callSites = symbols.callExpressions; + + // NOTE: we create a breakpoint map keyed on location + // to speed up the lookups. Hopefully we'll fix the + // inconsistency with column offsets so that we can expect + // a breakpoint to be added at the beginning of a call expression. + const bpLocationMap = keyBy(breakpoints.valueSeq().toJS(), ({ location }) => + locationKey(location) + ); + + function locationKey({ line, column }) { + return `${line}/${column}`; + } + + function findBreakpoint(callSite) { + const { location: { start, end } } = callSite; + + const breakpointId = range(start.column - 1, end.column) + .map(column => locationKey({ line: start.line, column })) + .find(key => bpLocationMap[key]); + + if (breakpointId) { + return bpLocationMap[breakpointId]; + } + } + + return callSites + .filter(({ location }) => location.start.line === location.end.line) + .map(callSite => ({ ...callSite, breakpoint: findBreakpoint(callSite) })); +} + +export default connect( + state => { + const selectedLocation = getSelectedLocation(state); + const selectedSource = getSelectedSource(state); + const sourceId = selectedLocation && selectedLocation.sourceId; + const source = selectedSource && selectedSource.toJS(); + + const symbols = getSymbols(state, source); + const breakpoints = getBreakpointsForSource(state, sourceId); + + return { + selectedLocation, + selectedSource, + callSites: getCallSites(symbols, breakpoints), + breakpoints: breakpoints + }; + }, + dispatch => bindActionCreators(actions, dispatch) +)(CallSites); diff --git a/src/components/Editor/ColumnBreakpoint.js b/src/components/Editor/ColumnBreakpoint.js deleted file mode 100644 index dab79a9aa3..0000000000 --- a/src/components/Editor/ColumnBreakpoint.js +++ /dev/null @@ -1,89 +0,0 @@ -// @flow -import { Component } from "react"; -import { isEnabled } from "devtools-config"; -import ReactDOM from "react-dom"; -import Svg from "../shared/Svg"; -import classnames from "classnames"; - -const breakpointSvg = document.createElement("span"); -ReactDOM.render(Svg("column-breakpoint"), breakpointSvg); - -type BookMarkType = { - clear: Function -}; - -function makeBookmark(isDisabled: boolean) { - const bp = breakpointSvg.cloneNode(true); - bp.className = classnames("editor column-breakpoint", { - "breakpoint-disabled": isDisabled - }); - - return bp; -} - -class ColumnBreakpoint extends Component { - props: { - breakpoint: Object, - editor: Object - }; - - addBreakpoint: Function; - bookmark: ?BookMarkType; - - constructor() { - super(); - - this.bookmark = undefined; - const self: any = this; - self.addBreakpoint = this.addBreakpoint.bind(this); - } - - addBreakpoint() { - if (!isEnabled("columnBreakpoints")) { - return; - } - - const bp = this.props.breakpoint; - const line = bp.location.line - 1; - const column = bp.location.column; - const editor = this.props.editor; - - const widget = makeBookmark(bp.disabled); - const bookmark = editor.setBookmark({ line, ch: column }, { widget }); - this.bookmark = bookmark; - } - shouldComponentUpdate(nextProps: any) { - return ( - this.props.editor !== nextProps.editor || - this.props.breakpoint.disabled !== nextProps.breakpoint.disabled || - this.props.breakpoint.condition !== nextProps.breakpoint.condition - ); - } - componentDidMount() { - if (!this.props.editor) { - return; - } - - this.addBreakpoint(); - } - componentDidUpdate() { - if (this.bookmark) { - this.bookmark.clear(); - } - this.addBreakpoint(); - } - componentWillUnmount() { - if (!this.props.editor || !this.bookmark) { - return; - } - - this.bookmark.clear(); - } - render() { - return null; - } -} - -ColumnBreakpoint.displayName = "ColumnBreakpoint"; - -export default ColumnBreakpoint; diff --git a/src/components/Editor/index.js b/src/components/Editor/index.js index 65c58861a3..ce5c3555fa 100644 --- a/src/components/Editor/index.js +++ b/src/components/Editor/index.js @@ -52,12 +52,12 @@ const Preview = createFactory(_Preview); import _Breakpoint from "./Breakpoint"; const Breakpoint = createFactory(_Breakpoint); -import _ColumnBreakpoint from "./ColumnBreakpoint"; -const ColumnBreakpoint = createFactory(_ColumnBreakpoint); - import _HitMarker from "./HitMarker"; const HitMarker = createFactory(_HitMarker); +import _CallSites from "./CallSites"; +const CallSites = createFactory(_CallSites); + import { getDocument, setDocument, @@ -70,7 +70,6 @@ import { getCursorLine, resizeBreakpointGutter, traverseResults, - getTokenLocation, updateSelection, markText } from "../../utils/editor"; @@ -187,7 +186,6 @@ class Editor extends PureComponent { codeMirrorWrapper.tabIndex = 0; codeMirrorWrapper.addEventListener("keydown", e => this.onKeyDown(e)); codeMirrorWrapper.addEventListener("mouseover", e => this.onMouseOver(e)); - codeMirrorWrapper.addEventListener("click", e => this.onTokenClick(e)); const toggleFoldMarkerVisibility = e => { if (node instanceof HTMLElement) { @@ -333,20 +331,6 @@ class Editor extends PureComponent { this.clearPreviewSelection(); } - onTokenClick(e) { - const { target } = e; - if ( - !isEnabled("columnBreakpoints") || - !e.altKey || - !target.parentElement.closest(".CodeMirror-line") - ) { - return; - } - - const { line, column } = getTokenLocation(this.editor.codeMirror, target); - this.toggleBreakpoint(line - 1, column - 1); - } - onSearchAgain(_, e) { const { query, searchModifiers } = this.props; const { editor: { codeMirror } } = this.editor; @@ -683,18 +667,7 @@ class Editor extends PureComponent { }) ); - const columnBreakpointBookmarks = breakpoints - .valueSeq() - .filter(b => (isEnabled("columnBreakpoints") ? b.location.column : false)) - .map(bp => - ColumnBreakpoint({ - key: makeLocationId(bp.location), - breakpoint: bp, - editor: this.editor && this.editor.codeMirror - }) - ); - - return breakpointMarkers.concat(columnBreakpointBookmarks); + return breakpointMarkers; } renderHitCounts() { @@ -791,6 +764,15 @@ class Editor extends PureComponent { ); } + renderCallSites() { + const editor = this.editor; + + if (!editor || !isEnabled("columnBreakpoints")) { + return null; + } + return CallSites({ editor }); + } + render() { const { selectSource, @@ -826,7 +808,8 @@ class Editor extends PureComponent { this.renderInScopeLines(), this.renderHitCounts(), Footer({ editor: this.editor, horizontal }), - this.renderPreview() + this.renderPreview(), + this.renderCallSites() ); } } @@ -885,7 +868,7 @@ export default connect( highlightedLineRange: getHighlightedLineRange(state), searchOn: getActiveSearchState(state) === "file", loadedObjects: getLoadedObjects(state), - breakpoints: getBreakpointsForSource(state, sourceId || ""), + breakpoints: getBreakpointsForSource(state, sourceId), hitCount: getHitCountForSource(state, sourceId), selectedFrame: getSelectedFrame(state), pauseData: getPause(state), diff --git a/src/components/Editor/tests/__snapshots__/Editor.js.snap b/src/components/Editor/tests/__snapshots__/Editor.js.snap index 6b07b1e48a..3dddc1fe33 100644 --- a/src/components/Editor/tests/__snapshots__/Editor.js.snap +++ b/src/components/Editor/tests/__snapshots__/Editor.js.snap @@ -148,7 +148,7 @@ ShallowWrapper { "validateCallback": [Function], }, }, - "_mountOrder": 5, + "_mountOrder": 3, "_pendingCallbacks": null, "_pendingElement": null, "_pendingForceUpdate": false, diff --git a/src/utils/editor/expression.js b/src/utils/editor/expression.js index c9e1d5af19..8b86c1482a 100644 --- a/src/utils/editor/expression.js +++ b/src/utils/editor/expression.js @@ -2,14 +2,15 @@ import isEqual from "lodash/isEqual"; -const lineOffset = 1; - export function getTokenLocation(codeMirror: any, tokenEl: HTMLElement) { - const { left, top } = tokenEl.getBoundingClientRect(); - const { line, ch } = codeMirror.coordsChar({ left, top }); + const { left, top, width, height } = tokenEl.getBoundingClientRect(); + const { line, ch } = codeMirror.coordsChar({ + left: left + width / 2, + top: top + height / 2 + }); return { - line: line + lineOffset, + line: line, column: ch }; } diff --git a/src/utils/parser/getSymbols.js b/src/utils/parser/getSymbols.js index 2bf0e701e4..38198c3e8d 100644 --- a/src/utils/parser/getSymbols.js +++ b/src/utils/parser/getSymbols.js @@ -8,7 +8,7 @@ import getFunctionName from "./utils/getFunctionName"; import type { Source } from "debugger-html"; import type { NodePath, Node, Location as BabelLocation } from "babel-traverse"; -const symbolDeclarations = new Map(); +let symbolDeclarations = new Map(); export type SymbolDeclaration = {| name: string, @@ -27,6 +27,7 @@ export type SymbolDeclarations = { functions: Array, variables: Array, memberExpressions: Array, + callExpressions: Array, objectProperties: Array, identifiers: Array, comments: Array @@ -73,6 +74,7 @@ function extractSymbols(source: Source) { const functions = []; const variables = []; const memberExpressions = []; + const callExpressions = []; const objectProperties = []; const identifiers = []; @@ -117,6 +119,17 @@ function extractSymbols(source: Source) { }); } + if (t.isCallExpression(path)) { + const callee = path.node.callee; + if (!t.isMemberExpression(callee)) { + const { start, end, identifierName } = callee.loc; + callExpressions.push({ + name: identifierName, + location: { start, end } + }); + } + } + if (t.isIdentifier(path)) { const { start, end } = path.node.loc; @@ -146,6 +159,7 @@ function extractSymbols(source: Source) { return { functions, variables, + callExpressions, memberExpressions, objectProperties, comments, @@ -177,10 +191,6 @@ function extendSnippet( const prevArray = t.isArrayExpression(prevPath); const array = t.isArrayExpression(path); - // if (!name) { - // return expression; - // } - if (expression === "") { if (computed) { return `[${name}]`; @@ -316,6 +326,7 @@ export function formatSymbols(source: Source) { const { objectProperties, memberExpressions, + callExpressions, identifiers, variables } = getSymbols(source); @@ -348,6 +359,9 @@ export function formatSymbols(source: Source) { "member expressions", memberExpressions.map(summarize).join("\n"), + "call expressions", + callExpressions.map(summarize).join("\n"), + "identifiers", identifiers.map(summarize).join("\n"), @@ -355,3 +369,7 @@ export function formatSymbols(source: Source) { variables.map(summarize).join("\n") ].join("\n"); } + +export function clearSymbols() { + symbolDeclarations = new Map(); +} diff --git a/src/utils/parser/index.js b/src/utils/parser/index.js index 0da0fc30e5..3307bc1b2e 100644 --- a/src/utils/parser/index.js +++ b/src/utils/parser/index.js @@ -11,6 +11,7 @@ export const getClosestExpression = dispatcher.task("getClosestExpression"); export const getSymbols = dispatcher.task("getSymbols"); export const getVariablesInScope = dispatcher.task("getVariablesInScope"); export const getOutOfScopeLocations = dispatcher.task("getOutOfScopeLocations"); +export const clearSymbols = dispatcher.task("clearSymbols"); export type { SymbolDeclaration, SymbolDeclarations } from "./getSymbols"; export type { AstLocation } from "./types"; diff --git a/src/utils/parser/tests/__snapshots__/getSymbols.js.snap b/src/utils/parser/tests/__snapshots__/getSymbols.js.snap index cd59774634..b70be1b47e 100644 --- a/src/utils/parser/tests/__snapshots__/getSymbols.js.snap +++ b/src/utils/parser/tests/__snapshots__/getSymbols.js.snap @@ -10,6 +10,8 @@ member expressions [(21, 4), (21, 17)] [(21, 0), (21, 17)] Obj.otherProperty otherProperty [(25, 9), (25, 16)] [(25, 4), (25, 16)] this.awesome awesome [(29, 12), (29, 15)] [(29, 4), (29, 15)] console.log log +call expressions + identifiers [(1, 6), (1, 15)] TIME TIME [(1, 6), (1, 10)] TIME TIME @@ -56,12 +58,36 @@ variables [(28, 12), (28, 18)] person " `; +exports[`Parser.getSymbols call sites 1`] = ` +"properties + +member expressions +[(2, 13), (2, 17)] [(2, 0), (2, 17)] ffff +[(2, 7), (2, 10)] [(2, 0), (2, 10)] eee +call expressions +[(1, 0), (1, 3)] aaa +[(1, 4), (1, 7)] bbb +[(1, 11), (1, 14)] ccc +[(2, 0), (2, 4)] dddd +identifiers +[(1, 0), (1, 3)] aaa aaa +[(1, 4), (1, 7)] bbb bbb +[(1, 11), (1, 14)] ccc ccc +[(2, 0), (2, 4)] dddd dddd +[(2, 7), (2, 10)] eee eee +[(2, 13), (2, 17)] ffff ffff +variables +" +`; + exports[`Parser.getSymbols class 1`] = ` "properties member expressions [(3, 9), (3, 12)] [(3, 4), (3, 12)] this.foo foo [(7, 12), (7, 15)] [(7, 4), (7, 15)] console.log log +call expressions + identifiers [(1, 6), (1, 10)] Test Test [(2, 2), (2, 13)] constructor constructor @@ -125,6 +151,9 @@ member expressions [(27, 29), (27, 43)] [(27, 13), (27, 43)] secondProperty [(27, 27), (27, 28)] [(27, 13), (27, 28)] c [(27, 18), (27, 24)] [(27, 13), (27, 24)] obj2.doEvil doEvil +call expressions +[(22, 21), (22, 28)] compute +[(23, 21), (23, 28)] compute identifiers [(1, 6), (1, 27)] obj obj [(1, 6), (1, 9)] obj obj @@ -264,6 +293,9 @@ member expressions [(26, 4), (26, 7)] [(25, 19), (26, 7)] map map [(30, 15), (30, 24)] [(30, 2), (30, 24)] globalObject.greetings greetings [(38, 11), (38, 14)] [(38, 3), (38, 14)] console.log log +call expressions +[(36, 3), (39, 3)] undefined +[(37, 20), (37, 28)] sayHello identifiers [(4, 6), (7, 3)] globalObject globalObject [(4, 6), (4, 18)] globalObject globalObject @@ -312,6 +344,8 @@ exports[`Parser.getSymbols func 1`] = ` member expressions +call expressions +[(7, 1), (9, 1)] undefined identifiers [(1, 9), (1, 15)] square square [(1, 16), (1, 17)] n n @@ -327,6 +361,9 @@ exports[`Parser.getSymbols math 1`] = ` member expressions +call expressions +[(5, 14), (5, 20)] square +[(6, 15), (6, 22)] squaare identifiers [(1, 9), (1, 13)] math math [(1, 14), (1, 15)] n n @@ -362,6 +399,8 @@ member expressions [(5, 31), (5, 37)] [(5, 17), (5, 37)] Backbone.View.extend extend [(5, 26), (5, 30)] [(5, 17), (5, 30)] Backbone.View View [(9, 12), (9, 15)] [(9, 4), (9, 15)] console.log log +call expressions + identifiers [(1, 6), (1, 25)] foo foo [(1, 6), (1, 9)] foo foo @@ -393,6 +432,8 @@ exports[`Parser.getSymbols var 1`] = ` member expressions +call expressions + identifiers [(1, 4), (1, 11)] foo foo [(1, 4), (1, 7)] foo foo diff --git a/src/utils/parser/tests/fixtures/call-sites.js b/src/utils/parser/tests/fixtures/call-sites.js new file mode 100644 index 0000000000..c592a831c5 --- /dev/null +++ b/src/utils/parser/tests/fixtures/call-sites.js @@ -0,0 +1,2 @@ +aaa(bbb(), ccc()); +dddd().eee().ffff(); diff --git a/src/utils/parser/tests/getSymbols.js b/src/utils/parser/tests/getSymbols.js index 0d4231504e..af38c7df9e 100644 --- a/src/utils/parser/tests/getSymbols.js +++ b/src/utils/parser/tests/getSymbols.js @@ -39,6 +39,11 @@ describe("Parser.getSymbols", () => { expect(symbols).toMatchSnapshot(); }); + it("call sites", () => { + const symbols = formatSymbols(getSource("call-sites")); + expect(symbols).toMatchSnapshot(); + }); + it("finds symbols in an html file", () => { const symbols = formatSymbols(getSource("parseScriptTags", "html")); expect(symbols).toMatchSnapshot();