Skip to content
This repository has been archived by the owner on Jan 11, 2023. It is now read-only.

Copy function #3970

Merged
merged 1 commit into from
Sep 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions assets/panel/debugger.properties
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ copySource.accesskey=y
copySourceUrl=Copy Source URL
copySourceUrl.accesskey=u

# LOCALIZATION NOTE (copyFunction): This is the text that appears in the
# context menu to copy the function the user selected
copyFunction.label=Copy Function
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@flodolo sorry, that's embarrassing :/

copyFunction.accesskey=F

# LOCALIZATION NOTE (copyStackTrace): This is the text that appears in the
# context menu to copy the stack trace methods, file names and row number.
copyStackTrace=Copy Stack Trace
Expand Down
19 changes: 16 additions & 3 deletions src/components/Editor/EditorMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ function getMenuItems(
onGutterContextMenu,
jumpToMappedLocation,
toggleBlackBox,
addExpression
addExpression,
getFunctionText
}
) {
const copySourceLabel = L10N.getStr("copySource");
const copySourceKey = L10N.getStr("copySource.accesskey");
const copyFunctionLabel = L10N.getStr("copyFunction.label");
const copyFunctionKey = L10N.getStr("copyFunction.accesskey");
const copySourceUrlLabel = L10N.getStr("copySourceUrl");
const copySourceUrlKey = L10N.getStr("copySourceUrl.accesskey");
const revealInTreeLabel = L10N.getStr("sourceTabs.revealInTree");
Expand Down Expand Up @@ -92,12 +95,22 @@ function getMenuItems(
click: () => showSource(selectedSource.get("id"))
};

const functionText = getFunctionText(line + 1);
const copyFunction = {
id: "node-menu-copy-function",
label: copyFunctionLabel,
accesskey: copyFunctionKey,
disabled: !functionText,
click: () => copyToTheClipboard(functionText)
};

const menuItems = [
copySource,
copySourceUrl,
jumpLabel,
showSourceMenuItem,
blackBoxMenuItem
blackBoxMenuItem,
copySource,
copyFunction
];

if (textSelected) {
Expand Down
17 changes: 14 additions & 3 deletions src/components/Editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import EditorMenu from "./EditorMenu";
import { renderConditionalPanel } from "./ConditionalPanel";
import { debugGlobal } from "devtools-launchpad";
import { isLoaded } from "../../utils/source";
import { findFunctionText } from "../../utils/function";

import { isEmptyLineInSource } from "../../reducers/ast";

Expand All @@ -28,7 +29,8 @@ import {
getFileSearchModifierState,
getVisibleBreakpoints,
getInScopeLines,
getConditionalBreakpointPanel
getConditionalBreakpointPanel,
getSymbols
} from "../../selectors";

import actions from "../../actions";
Expand Down Expand Up @@ -340,7 +342,8 @@ class Editor extends PureComponent {
showSource,
jumpToMappedLocation,
addExpression,
toggleBlackBox
toggleBlackBox,
getFunctionText
} = this.props;

return EditorMenu({
Expand All @@ -352,6 +355,7 @@ class Editor extends PureComponent {
jumpToMappedLocation,
addExpression,
toggleBlackBox,
getFunctionText,
onGutterContextMenu: this.onGutterContextMenu
});
}
Expand Down Expand Up @@ -757,7 +761,8 @@ Editor.propTypes = {
conditionalBreakpointPanel: PropTypes.number,
toggleConditionalBreakpointPanel: PropTypes.func.isRequired,
isEmptyLine: PropTypes.func,
continueToHere: PropTypes.func
continueToHere: PropTypes.func,
getFunctionText: PropTypes.func
};

Editor.contextTypes = {
Expand All @@ -784,6 +789,12 @@ export default connect(
query: getFileSearchQueryState(state),
searchModifiers: getFileSearchModifierState(state),
linesInScope: getInScopeLines(state),
getFunctionText: line =>
findFunctionText(
line,
selectedSource.toJS(),
getSymbols(state, selectedSource.toJS())
),
isEmptyLine: line =>
isEmptyLineInSource(state, line, selectedSource.toJS()),
conditionalBreakpointPanel: getConditionalBreakpointPanel(state)
Expand Down
2 changes: 1 addition & 1 deletion src/utils/breakpoint/astBreakpointLocation.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { containsPosition } from "../parser/utils/contains";
import type { Scope } from "../parser/types";
import type { Location, Source } from "debugger-html";

function findClosestScope(functions: Scope[], location: Location) {
export function findClosestScope(functions: Scope[], location: Location) {
return functions.reduce((found, currNode) => {
if (
currNode.name === "anonymous" ||
Expand Down
38 changes: 38 additions & 0 deletions src/utils/function.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { findClosestScope } from "./breakpoint/astBreakpointLocation";

function getIndentation(lines) {
const firstLine = lines[0];
const secondLine = lines[1];
const lastLine = lines[lines.length - 1];

const _getIndentation = line => line && line.match(/^\s*/)[0].length;

const indentations = [
_getIndentation(firstLine),
_getIndentation(secondLine),
_getIndentation(lastLine)
];

return Math.max(...indentations, 0);
}

export function findFunctionText(line, source, symbols) {
const func = findClosestScope(symbols.functions, { line, column: Infinity });
if (!func) {
return null;
}

const { location: { start, end } } = func;
const lines = source.text.split("\n");
const firstLine = lines[start.line - 1].slice(start.column);
const lastLine = lines[end.line - 1].slice(0, end.column);
const middle = lines.slice(start.line, end.line - 1);
const functionLines = [firstLine, ...middle, lastLine];

const indentation = getIndentation(functionLines);
const formattedLines = functionLines.map(_line =>
_line.replace(new RegExp(`^\\s{0,${indentation - 1}}`), "")
);

return formattedLines.join("\n").trim();
}
10 changes: 8 additions & 2 deletions src/utils/parser/tests/__snapshots__/getSymbols.spec.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ variables

exports[`Parser.getSymbols func 1`] = `
"properties

[(24, 2), (24, 5)] obj.foo foo
member expressions

call expressions
Expand All @@ -373,8 +373,14 @@ identifiers
[(9, 15), (9, 22)] slowFoo slowFoo
[(13, 22), (13, 31)] exSlowFoo exSlowFoo
[(17, 0), (17, 5)] child child
[(23, 6), (31, 1)] obj obj
[(23, 6), (23, 9)] obj obj
[(24, 2), (24, 5)] foo foo
[(24, 16), (24, 20)] name name
[(28, 2), (28, 5)] bar bar
variables
[(1, 16), (1, 17)] n "
[(1, 16), (1, 17)] n
[(23, 6), (31, 1)] obj "
`;

exports[`Parser.getSymbols math 1`] = `
Expand Down
10 changes: 10 additions & 0 deletions src/utils/parser/tests/fixtures/func.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,13 @@ child = function() {};
(function() {
2;
})();

const obj = {
foo: function name() {
2 + 2;
},

bar() {
2 + 2;
}
};
25 changes: 25 additions & 0 deletions src/utils/tests/__snapshots__/function.spec.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`function findFunctionText finds class function 1`] = `
"bar() {
2 + 2;
}"
`;

exports[`function findFunctionText finds function 1`] = `
"async function exSlowFoo() {
return \\"yay in a bit\\";
}"
`;

exports[`function findFunctionText finds function signature 1`] = `
"async function exSlowFoo() {
return \\"yay in a bit\\";
}"
`;

exports[`function findFunctionText finds property function 1`] = `
"function name() {
2 + 2;
}"
`;
58 changes: 58 additions & 0 deletions src/utils/tests/function.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { findFunctionText } from "../function";

import getSymbols from "../parser/getSymbols";
import { getSource } from "../parser/tests/helpers";

describe("function", () => {
describe("findFunctionText", () => {
it("finds function", () => {
const source = getSource("func");
const symbols = getSymbols(source);

const text = findFunctionText(14, source, symbols);
expect(text).toMatchSnapshot();
});

it("finds function signature", () => {
const source = getSource("func");
const symbols = getSymbols(source);

const text = findFunctionText(13, source, symbols);
expect(text).toMatchSnapshot();
});

it("misses function closing brace", () => {
const source = getSource("func");
const symbols = getSymbols(source);

const text = findFunctionText(15, source, symbols);

// TODO: we should try and match the closing bracket.
expect(text).toEqual(null);
});

it("finds property function", () => {
const source = getSource("func");
const symbols = getSymbols(source);

const text = findFunctionText(25, source, symbols);
expect(text).toMatchSnapshot();
});

it("finds class function", () => {
const source = getSource("func");
const symbols = getSymbols(source);

const text = findFunctionText(29, source, symbols);
expect(text).toMatchSnapshot();
});

it("cant find function", () => {
const source = getSource("func");
const symbols = getSymbols(source);

const text = findFunctionText(17, source, symbols);
expect(text).toEqual(null);
});
});
});