From f775268daca3ca02076f419ca4d576082bf54f36 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Mon, 11 Jul 2022 10:57:03 -0400 Subject: [PATCH] Migrate SourcesTree logic and components to TS (#7365) --- .../{SourcesTree.js => SourcesTree.tsx} | 127 +++++++++++------- ...SourcesTreeItem.js => SourcesTreeItem.tsx} | 97 ++++++------- .../client/debugger/src/utils/source.ts | 5 +- .../{addToTree.js => addToTree.ts} | 46 +++++-- .../{collapseTree.js => collapseTree.ts} | 6 +- .../{formatTree.js => formatTree.ts} | 4 +- .../{getDirectories.js => getDirectories.ts} | 20 +-- .../sources-tree/{getURL.js => getURL.ts} | 17 ++- .../utils/sources-tree/{index.js => index.ts} | 0 .../sources-tree/{sortTree.js => sortTree.ts} | 4 +- .../{treeOrder.js => treeOrder.ts} | 25 +++- .../debugger/src/utils/sources-tree/types.js | 11 -- .../debugger/src/utils/sources-tree/types.ts | 31 +++++ .../{updateTree.js => updateTree.ts} | 28 +++- .../utils/sources-tree/{utils.js => utils.ts} | 42 +++--- .../debugger/src/utils/{url.js => url.ts} | 25 +++- src/ui/utils/mixpanel.ts | 1 + 17 files changed, 318 insertions(+), 171 deletions(-) rename src/devtools/client/debugger/src/components/PrimaryPanes/{SourcesTree.js => SourcesTree.tsx} (75%) rename src/devtools/client/debugger/src/components/PrimaryPanes/{SourcesTreeItem.js => SourcesTreeItem.tsx} (80%) rename src/devtools/client/debugger/src/utils/sources-tree/{addToTree.js => addToTree.ts} (72%) rename src/devtools/client/debugger/src/utils/sources-tree/{collapseTree.js => collapseTree.ts} (88%) rename src/devtools/client/debugger/src/utils/sources-tree/{formatTree.js => formatTree.ts} (80%) rename src/devtools/client/debugger/src/utils/sources-tree/{getDirectories.js => getDirectories.ts} (63%) rename src/devtools/client/debugger/src/utils/sources-tree/{getURL.js => getURL.ts} (86%) rename src/devtools/client/debugger/src/utils/sources-tree/{index.js => index.ts} (100%) rename src/devtools/client/debugger/src/utils/sources-tree/{sortTree.js => sortTree.ts} (89%) rename src/devtools/client/debugger/src/utils/sources-tree/{treeOrder.js => treeOrder.ts} (83%) delete mode 100644 src/devtools/client/debugger/src/utils/sources-tree/types.js create mode 100644 src/devtools/client/debugger/src/utils/sources-tree/types.ts rename src/devtools/client/debugger/src/utils/sources-tree/{updateTree.js => updateTree.ts} (60%) rename src/devtools/client/debugger/src/utils/sources-tree/{utils.js => utils.ts} (65%) rename src/devtools/client/debugger/src/utils/{url.js => url.ts} (64%) diff --git a/src/devtools/client/debugger/src/components/PrimaryPanes/SourcesTree.js b/src/devtools/client/debugger/src/components/PrimaryPanes/SourcesTree.tsx similarity index 75% rename from src/devtools/client/debugger/src/components/PrimaryPanes/SourcesTree.js rename to src/devtools/client/debugger/src/components/PrimaryPanes/SourcesTree.tsx index e7c6dfeb536..40899703704 100644 --- a/src/devtools/client/debugger/src/components/PrimaryPanes/SourcesTree.js +++ b/src/devtools/client/debugger/src/components/PrimaryPanes/SourcesTree.tsx @@ -7,7 +7,9 @@ // Dependencies import React, { Component } from "react"; -import { connect } from "react-redux"; +import { connect, ConnectedProps } from "react-redux"; + +import type { UIState } from "ui/state"; // Selectors import { @@ -21,8 +23,6 @@ import { getContext, } from "../../selectors"; -import { getGeneratedSourceByURL } from "../../reducers/sources"; - // Actions import actions from "../../actions"; @@ -35,16 +35,18 @@ import { createTree, getDirectories, isDirectory, - findSourceTreeNodes, getSourceFromNode, nodeHasChildren, updateTree, } from "../../utils/sources-tree"; import { parse } from "../../utils/url"; -import { getRawSourceURL } from "../../utils/source"; import { trackEvent } from "ui/utils/telemetry"; +import { TreeDirectory, TreeNode, TreeSource } from "../../utils/sources-tree/types"; +import { Source } from "../../reducers/sources"; + +type $FixTypeLater = any; -function shouldAutoExpand(depth, item, debuggeeUrl) { +function shouldAutoExpand(depth: number, item: TreeNode, debuggeeUrl: string) { if (depth !== 1) { return false; } @@ -53,37 +55,74 @@ function shouldAutoExpand(depth, item, debuggeeUrl) { return item.name === host; } -function findSource({ sources }, itemPath, source) { +function findSource(sources: Record, itemPath: string, source: Source) { if (source) { return sources[source.id]; } return source; } -class SourcesTree extends Component { - constructor(props) { +const mapStateToProps = (state: UIState) => { + const selectedSource = getSelectedSource(state); + const shownSource = getShownSource(state); + const sources = getDisplayedSources(state); + + return { + cx: getContext(state), + sourcesLoading: getSourcesLoading(state), + shownSource: shownSource, + selectedSource: selectedSource, + debuggeeUrl: getDebuggeeUrl(state), + expanded: getExpandedState(state), + focused: getFocusedSourceItem(state), + sources: sources, + sourceCount: Object.values(sources).length, + }; +}; + +const connector = connect(mapStateToProps, { + selectSource: actions.selectSource, + setExpandedState: actions.setExpandedState, + focusItem: actions.focusItem, +}); + +type PropsFromRedux = ConnectedProps; + +interface STState { + uncollapsedTree: TreeDirectory; + sourceTree: TreeNode; + parentMap: WeakMap; + listItems?: (TreeDirectory | TreeSource)[]; + highlightItems?: (TreeDirectory | TreeSource)[]; +} + +class SourcesTree extends Component { + constructor(props: PropsFromRedux) { super(props); const { debuggeeUrl, sources } = this.props; const state = createTree({ debuggeeUrl, sources, - }); + }) as STState; if (props.shownSource) { - const listItems = getDirectories(props.shownSource, state.sourceTree); + const listItems = getDirectories(props.shownSource, state.sourceTree as TreeDirectory); state.listItems = listItems; } if (props.selectedSource) { - const highlightItems = getDirectories(props.selectedSource, state.sourceTree); + const highlightItems = getDirectories( + props.selectedSource, + this.state.sourceTree as TreeDirectory + ); state.highlightItems = highlightItems; } this.state = state; } - UNSAFE_componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps: PropsFromRedux) { const { debuggeeUrl, sources, shownSource, selectedSource } = this.props; const { uncollapsedTree, sourceTree } = this.state; @@ -99,12 +138,12 @@ class SourcesTree extends Component { } if (nextProps.shownSource && nextProps.shownSource != shownSource) { - const listItems = getDirectories(nextProps.shownSource, sourceTree); + const listItems = getDirectories(nextProps.shownSource, sourceTree as TreeDirectory); return this.setState({ listItems }); } if (nextProps.selectedSource && nextProps.selectedSource != selectedSource) { - const highlightItems = getDirectories(nextProps.selectedSource, sourceTree); + const highlightItems = getDirectories(nextProps.selectedSource, sourceTree as TreeDirectory); this.setState({ highlightItems }); } @@ -123,28 +162,28 @@ class SourcesTree extends Component { } } - selectItem = item => { + selectItem = (item: TreeNode) => { if (item.type == "source" && !Array.isArray(item.contents)) { trackEvent("source_explorer.select_source"); this.props.selectSource(this.props.cx, item.contents.id); } }; - onFocus = item => { + onFocus = (item: TreeNode) => { this.props.focusItem(item); }; - onActivate = item => { + onActivate = (item: TreeNode) => { this.selectItem(item); }; // NOTE: we get the source from sources because item.contents is cached - getSource(item) { + getSource(item: TreeNode) { const source = getSourceFromNode(item); - return findSource(this.props, item.path, source); + return findSource(this.props.sources, item.path, source!); } - getPath = item => { + getPath = (item: TreeNode) => { const { path } = item; const source = this.getSource(item); @@ -157,20 +196,21 @@ class SourcesTree extends Component { return `${path}/${source.id}/${blackBoxedPart}`; }; - onExpand = (item, expandedState) => { + onExpand = (item: TreeNode, expandedState: $FixTypeLater) => { this.props.setExpandedState(expandedState); }; - onCollapse = (item, expandedState) => { + onCollapse = (item: TreeNode, expandedState: $FixTypeLater) => { this.props.setExpandedState(expandedState); }; isEmpty() { const { sourceTree } = this.state; + // @ts-expect-error single/array mismatch return sourceTree.contents.length === 0; } - renderEmptyElement(message) { + renderEmptyElement(message: string) { return (
{message} @@ -178,15 +218,22 @@ class SourcesTree extends Component { ); } - getRoots = sourceTree => { + getRoots = (sourceTree: TreeNode) => { return sourceTree.contents; }; - getChildren = item => { + getChildren = (item: TreeNode) => { return nodeHasChildren(item) ? item.contents : []; }; - renderItem = (item, depth, focused, _, expanded, { setExpanded }) => { + renderItem = ( + item: TreeNode, + depth: number, + focused: boolean, + _: any, + expanded: boolean, + { setExpanded }: { setExpanded: () => void } + ) => { const { debuggeeUrl } = this.props; return ( @@ -216,7 +263,7 @@ class SourcesTree extends Component { expanded, focused, getChildren: this.getChildren, - getParent: item => parentMap.get(item), + getParent: (item: TreeNode) => parentMap.get(item), getPath: this.getPath, getRoots: () => this.getRoots(sourceTree), highlightItems, @@ -262,26 +309,4 @@ class SourcesTree extends Component { } } -const mapStateToProps = (state, props) => { - const selectedSource = getSelectedSource(state); - const shownSource = getShownSource(state); - const sources = getDisplayedSources(state); - - return { - cx: getContext(state), - sourcesLoading: getSourcesLoading(state), - shownSource: shownSource, - selectedSource: selectedSource, - debuggeeUrl: getDebuggeeUrl(state), - expanded: getExpandedState(state), - focused: getFocusedSourceItem(state), - sources: sources, - sourceCount: Object.values(sources).length, - }; -}; - -export default connect(mapStateToProps, { - selectSource: actions.selectSource, - setExpandedState: actions.setExpandedState, - focusItem: actions.focusItem, -})(SourcesTree); +export default connector(SourcesTree); diff --git a/src/devtools/client/debugger/src/components/PrimaryPanes/SourcesTreeItem.js b/src/devtools/client/debugger/src/components/PrimaryPanes/SourcesTreeItem.tsx similarity index 80% rename from src/devtools/client/debugger/src/components/PrimaryPanes/SourcesTreeItem.js rename to src/devtools/client/debugger/src/components/PrimaryPanes/SourcesTreeItem.tsx index 1ce87a42e28..c9d5acb52ab 100644 --- a/src/devtools/client/debugger/src/components/PrimaryPanes/SourcesTreeItem.js +++ b/src/devtools/client/debugger/src/components/PrimaryPanes/SourcesTreeItem.tsx @@ -5,9 +5,12 @@ // import React, { Component } from "react"; -import { connect } from "react-redux"; +import { connect, ConnectedProps } from "react-redux"; import classnames from "classnames"; + import { showMenu } from "devtools/shared/contextmenu"; +import type { UIState } from "ui/state"; +import type { Source } from "devtools/client/debugger/src/reducers/sources"; import SourceIcon from "../shared/SourceIcon"; import AccessibleImage from "../shared/AccessibleImage"; @@ -31,10 +34,45 @@ import { } from "../../utils/source"; import { isDirectory, getPathWithoutThread } from "../../utils/sources-tree"; import { copyToTheClipboard } from "../../utils/clipboard"; -import { downloadFile } from "../../utils/utils"; import { isFulfilled } from "../../utils/async-value"; -class SourceTreeItem extends Component { +type $FixTypeLater = any; + +interface STIProps { + item: $FixTypeLater; + depth: number; + focused: boolean; + autoExpand: boolean; + expanded: boolean; + focusItem: (item: $FixTypeLater) => void; + selectItem: (item: $FixTypeLater) => void; + source: Source; + debuggeeUrl: string; + setExpanded: (item: $FixTypeLater, a: boolean, b: boolean) => void; +} + +const mapStateToProps = (state: UIState, props: STIProps) => { + const { source, item } = props; + return { + cx: getContext(state), + hasMatchingGeneratedSource: getHasMatchingGeneratedSource(state, source), + hasSiblingOfSameName: getHasSiblingOfSameName(state, source), + hasPrettySource: source ? checkHasPrettySource(state, source.id) : false, + sourceContent: source ? getSourceContentValue(state, source) : null, + extensionName: + (isUrlExtension(item.name) && getExtensionNameBySourceUrl(state, item.name)) || null, + }; +}; + +const connector = connect(mapStateToProps, { + toggleBlackBox: actions.toggleBlackBox, + loadSourceText: actions.loadSourceText, +}); + +type PropsFromRedux = ConnectedProps; +type FinalSTIProps = PropsFromRedux & STIProps; + +class SourceTreeItem extends Component { componentDidMount() { const { autoExpand, item } = this.props; if (autoExpand) { @@ -42,7 +80,7 @@ class SourceTreeItem extends Component { } } - onClick = e => { + onClick = () => { const { item, focusItem, selectItem } = this.props; focusItem(item); @@ -51,7 +89,7 @@ class SourceTreeItem extends Component { } }; - onContextMenu = (event, item) => { + onContextMenu = (event: React.MouseEvent, item: $FixTypeLater) => { const copySourceUri2Label = "Copy source URI"; const copySourceUri2Key = "u"; @@ -94,22 +132,7 @@ class SourceTreeItem extends Component { showMenu(event, menuOptions); }; - handleDownloadFile = async (cx, source, item) => { - if (!source) { - return; - } - - if (!this.props.sourceContent) { - await this.props.loadSourceText({ cx, source }); - } - const data = this.props.sourceContent; - if (!data) { - return; - } - downloadFile(data, item.name); - }; - - addCollapseExpandAllOptions = (menuOptions, item) => { + addCollapseExpandAllOptions = (menuOptions: $FixTypeLater[], item: $FixTypeLater) => { const { setExpanded } = this.props; menuOptions.push({ @@ -136,7 +159,7 @@ class SourceTreeItem extends Component { ); } - renderIcon(item, depth) { + renderIcon(item: $FixTypeLater, depth: number) { const { source, hasPrettySource } = this.props; if (item.name === "webpack://") { @@ -164,13 +187,13 @@ class SourceTreeItem extends Component { } if (source) { - return icon === "extension"} />; + return icon === "extension"} />; } return null; } - renderItemName(depth) { + renderItemName(depth: number) { const { item, extensionName } = this.props; if (isExtensionDirectory(depth, extensionName)) { @@ -231,37 +254,21 @@ class SourceTreeItem extends Component { } } -function getHasMatchingGeneratedSource(state, source) { +function getHasMatchingGeneratedSource(state: UIState, source?: Source) { if (!source) { return false; } - return !!getGeneratedSourceByURL(state, source.url); + return !!getGeneratedSourceByURL(state, source.url!); } -function getSourceContentValue(state, source) { +function getSourceContentValue(state: UIState, source: Source) { const content = getSourceContent(state, source.id); return content && isFulfilled(content) ? content.value : null; } -function isExtensionDirectory(depth, extensionName) { +function isExtensionDirectory(depth: number, extensionName: string | null) { return extensionName && (depth === 1 || depth === 0); } -const mapStateToProps = (state, props) => { - const { source, item } = props; - return { - cx: getContext(state), - hasMatchingGeneratedSource: getHasMatchingGeneratedSource(state, source), - hasSiblingOfSameName: getHasSiblingOfSameName(state, source), - hasPrettySource: source ? checkHasPrettySource(state, source.id) : false, - sourceContent: source ? getSourceContentValue(state, source) : null, - extensionName: - (isUrlExtension(item.name) && getExtensionNameBySourceUrl(state, item.name)) || null, - }; -}; - -export default connect(mapStateToProps, { - toggleBlackBox: actions.toggleBlackBox, - loadSourceText: actions.loadSourceText, -})(SourceTreeItem); +export default connector(SourceTreeItem); diff --git a/src/devtools/client/debugger/src/utils/source.ts b/src/devtools/client/debugger/src/utils/source.ts index 84ef6edfe14..7e56a059cd5 100644 --- a/src/devtools/client/debugger/src/utils/source.ts +++ b/src/devtools/client/debugger/src/utils/source.ts @@ -110,7 +110,7 @@ export function getPrettySourceURL(url?: string) { return `${url}:formatted`; } -export function getFileExtension(source?: Source) { +export function getFileExtension(source: Source) { const { path } = getURL(source); if (!path) { return ""; @@ -120,7 +120,7 @@ export function getFileExtension(source?: Source) { return lastIndex !== -1 ? path.slice(lastIndex + 1) : ""; } -export function isNotJavaScript(source?: Source) { +export function isNotJavaScript(source: Source) { return ["css", "svg", "png"].includes(getFileExtension(source)); } @@ -259,7 +259,6 @@ export function getSourcePath(url?: UrlResult): string { return ""; } - // @ts-expect-error path field mismatch const { path, href } = parseURL(url); // for URLs like "about:home" the path is null so we pass the full href return path || href; diff --git a/src/devtools/client/debugger/src/utils/sources-tree/addToTree.js b/src/devtools/client/debugger/src/utils/sources-tree/addToTree.ts similarity index 72% rename from src/devtools/client/debugger/src/utils/sources-tree/addToTree.js rename to src/devtools/client/debugger/src/utils/sources-tree/addToTree.ts index 4d3af9935f8..0bbd8ed9e4f 100644 --- a/src/devtools/client/debugger/src/utils/sources-tree/addToTree.js +++ b/src/devtools/client/debugger/src/utils/sources-tree/addToTree.ts @@ -16,7 +16,11 @@ import { import { createTreeNodeMatcher, findNodeInContents } from "./treeOrder"; import { getURL } from "./getURL"; -function createNodeInTree(part, path, tree, index) { +import type { Source } from "../../reducers/sources"; +import type { TreeNode, TreeSource, TreeDirectory, ParentMap } from "./types"; +import type { ParsedUrl } from "./getURL"; + +function createNodeInTree(part: string, path: string, tree: TreeDirectory, index: number) { const node = createDirectoryNode(part, path, []); // we are modifying the tree @@ -32,7 +36,16 @@ function createNodeInTree(part, path, tree, index) { * 1. if it exists return it * 2. if it does not exist create it */ -function findOrCreateNode(parts, subTree, path, part, index, url, debuggeeHost, source) { +function findOrCreateNode( + parts: string[], + subTree: TreeDirectory, + path: string, + part: string, + index: number, + url: TreeNode, + debuggeeHost: string, + source: Source +) { const addedPartIsFile = partIsFile(index, parts, url); const { found: childFound, index: childIndex } = findNodeInContents( @@ -68,7 +81,7 @@ function findOrCreateNode(parts, subTree, path, part, index, url, debuggeeHost, * walk the source tree to the final node for a given url, * adding new nodes along the way */ -function traverseTree(url, tree, debuggeeHost, source) { +function traverseTree(url: ParsedUrl, tree: TreeDirectory, debuggeeHost: string, source: Source) { const parts = url.path.replace(/\/$/, "").split("/"); parts[0] = url.group; @@ -78,16 +91,27 @@ function traverseTree(url, tree, debuggeeHost, source) { const debuggeeHostIfRoot = index === 1 ? debuggeeHost : null; - return findOrCreateNode(parts, subTree, path, part, index, url, debuggeeHostIfRoot, source); + return findOrCreateNode( + parts, + subTree, + path, + part, + index, + // @ts-expect-error Problem with `url` vs `TreeNode` here and in other fns? + url, + debuggeeHostIfRoot, + source + ) as TreeDirectory; }, tree); } /* * Add a source file to a directory node in the tree */ -function addSourceToNode(node, url, source) { +function addSourceToNode(node: TreeDirectory, url: ParsedUrl, source: Source) { const isFile = !isPathDirectory(url.path); + // @ts-expect-error intentional error check apparently if (node.type == "source" && !isFile) { throw new Error(`Unexpected type "source" at: ${node.name}`); } @@ -95,7 +119,7 @@ function addSourceToNode(node, url, source) { // if we have a file, and the subtree has no elements, overwrite the // subtree contents with the source if (isFile) { - // $FlowIgnore + // @ts-expect-error this is intentional node.type = "source"; return source; } @@ -103,7 +127,7 @@ function addSourceToNode(node, url, source) { const { filename } = url; const { found: childFound, index: childIndex } = findNodeInContents( node, - createTreeNodeMatcher(filename, false, null) + createTreeNodeMatcher(filename, false, undefined) ); // if we are readding an existing file in the node, overwrite the existing @@ -118,7 +142,7 @@ function addSourceToNode(node, url, source) { } // if this is a new file, add the new file; - const newNode = createSourceNode(filename, source.url, source); + const newNode = createSourceNode(filename, source.url!, source); const contents = node.contents.slice(0); contents.splice(childIndex, 0, newNode); return contents; @@ -128,15 +152,15 @@ function addSourceToNode(node, url, source) { * @memberof utils/sources-tree * @static */ -export function addToTree(tree, source, debuggeeHost) { +export function addToTree(tree: TreeDirectory, source: Source, debuggeeHost?: string) { const url = getURL(source, debuggeeHost); if (isInvalidUrl(url, source)) { return; } - const finalNode = traverseTree(url, tree, debuggeeHost, source); + const finalNode = traverseTree(url, tree, debuggeeHost!, source); - // $FlowIgnore + // @ts-expect-error intentional finalNode.contents = addSourceToNode(finalNode, url, source); } diff --git a/src/devtools/client/debugger/src/utils/sources-tree/collapseTree.js b/src/devtools/client/debugger/src/utils/sources-tree/collapseTree.ts similarity index 88% rename from src/devtools/client/debugger/src/utils/sources-tree/collapseTree.js rename to src/devtools/client/debugger/src/utils/sources-tree/collapseTree.ts index 367d16bd15f..45509d01ec1 100644 --- a/src/devtools/client/debugger/src/utils/sources-tree/collapseTree.js +++ b/src/devtools/client/debugger/src/utils/sources-tree/collapseTree.ts @@ -6,10 +6,12 @@ import { createDirectoryNode } from "./utils"; +import type { TreeNode, TreeSource, TreeDirectory, ParentMap } from "./types"; + /** * Take an existing source tree, and return a new one with collapsed nodes. */ -function _collapseTree(node, depth) { +function _collapseTree(node: TreeNode, depth: number): TreeNode { // Node is a folder. if (node.type === "directory") { if (!Array.isArray(node.contents)) { @@ -45,7 +47,7 @@ function _collapseTree(node, depth) { return node; } -export function collapseTree(node) { +export function collapseTree(node: TreeNode) { const tree = _collapseTree(node, 0); return tree; } diff --git a/src/devtools/client/debugger/src/utils/sources-tree/formatTree.js b/src/devtools/client/debugger/src/utils/sources-tree/formatTree.ts similarity index 80% rename from src/devtools/client/debugger/src/utils/sources-tree/formatTree.js rename to src/devtools/client/debugger/src/utils/sources-tree/formatTree.ts index 594ac48cd5d..dbcd6cd18c4 100644 --- a/src/devtools/client/debugger/src/utils/sources-tree/formatTree.js +++ b/src/devtools/client/debugger/src/utils/sources-tree/formatTree.ts @@ -2,9 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at . */ -// +import type { TreeNode, TreeSource, TreeDirectory, ParentMap } from "./types"; -export function formatTree(tree, depth = 0, str = "") { +export function formatTree(tree: TreeNode, depth = 0, str = "") { const whitespace = new Array(depth * 2).join(" "); if (tree.type === "directory") { diff --git a/src/devtools/client/debugger/src/utils/sources-tree/getDirectories.js b/src/devtools/client/debugger/src/utils/sources-tree/getDirectories.ts similarity index 63% rename from src/devtools/client/debugger/src/utils/sources-tree/getDirectories.js rename to src/devtools/client/debugger/src/utils/sources-tree/getDirectories.ts index e41c6010cd6..80d2393150d 100644 --- a/src/devtools/client/debugger/src/utils/sources-tree/getDirectories.js +++ b/src/devtools/client/debugger/src/utils/sources-tree/getDirectories.ts @@ -7,8 +7,11 @@ import { createParentMap } from "./utils"; import flattenDeep from "lodash/flattenDeep"; -function findSourceItem(sourceTree, source) { - function _traverse(subtree) { +import type { Source } from "../../reducers/sources"; +import type { TreeNode, TreeSource, TreeDirectory, ParentMap } from "./types"; + +function findSourceItem(sourceTree: TreeDirectory, source: Source) { + function _traverse(subtree: TreeNode) { if (subtree.type === "source") { if (subtree.contents.url === source.url) { return subtree; @@ -17,31 +20,30 @@ function findSourceItem(sourceTree, source) { return null; } - const matches = subtree.contents.map(child => _traverse(child)); + const matches: (TreeNode | null)[] = subtree.contents.map(child => _traverse(child)); return matches && matches.filter(Boolean)[0]; } return _traverse(sourceTree); } -export function findSourceTreeNodes(sourceTree, path) { - function _traverse(subtree) { +export function findSourceTreeNodes(sourceTree: TreeDirectory, path: string) { + function _traverse(subtree: TreeNode): TreeNode[] | TreeNode | undefined { if (subtree.path.endsWith(path)) { return subtree; } if (subtree.type === "directory") { const matches = subtree.contents.map(child => _traverse(child)); - return matches && matches.filter(Boolean); + return matches && (matches.filter(Boolean) as TreeNode[]); } } const result = _traverse(sourceTree); - // $FlowIgnore return Array.isArray(result) ? flattenDeep(result) : result; } -function getAncestors(sourceTree, item) { +function getAncestors(sourceTree: TreeDirectory, item: TreeNode | null) { if (!item) { return null; } @@ -59,7 +61,7 @@ function getAncestors(sourceTree, item) { } } -export function getDirectories(source, sourceTree) { +export function getDirectories(source: Source, sourceTree: TreeDirectory) { const item = findSourceItem(sourceTree, source); const ancestors = getAncestors(sourceTree, item); return ancestors || [sourceTree]; diff --git a/src/devtools/client/debugger/src/utils/sources-tree/getURL.js b/src/devtools/client/debugger/src/utils/sources-tree/getURL.ts similarity index 86% rename from src/devtools/client/debugger/src/utils/sources-tree/getURL.js rename to src/devtools/client/debugger/src/utils/sources-tree/getURL.ts index d09f526946b..4658868c918 100644 --- a/src/devtools/client/debugger/src/utils/sources-tree/getURL.js +++ b/src/devtools/client/debugger/src/utils/sources-tree/getURL.ts @@ -7,13 +7,15 @@ import { parse } from "../url"; import { getUnicodeHostname, getUnicodeUrlPath } from "devtools/client/shared/unicode-url"; -export function getFilenameFromURL(url) { +import type { Source } from "../../reducers/sources"; + +export function getFilenameFromURL(url: string) { const { pathname } = parse(url); const filename = getUnicodeUrlPath(getFilenameFromPath(pathname)); return filename; } -export function getFilenameFromPath(pathname) { +export function getFilenameFromPath(pathname: string) { let filename = ""; if (pathname) { filename = pathname.substring(pathname.lastIndexOf("/") + 1); @@ -26,9 +28,16 @@ export function getFilenameFromPath(pathname) { } const NoDomain = "(no domain)"; -const def = { path: "", group: "", filename: "" }; -export function getURL(source, defaultDomain = "") { +export interface ParsedUrl { + path: string; + group: string; + filename: string; +} + +const def: ParsedUrl = { path: "", group: "", filename: "" }; + +export function getURL(source: Source, defaultDomain = "") { const { url } = source; if (!url) { return def; diff --git a/src/devtools/client/debugger/src/utils/sources-tree/index.js b/src/devtools/client/debugger/src/utils/sources-tree/index.ts similarity index 100% rename from src/devtools/client/debugger/src/utils/sources-tree/index.js rename to src/devtools/client/debugger/src/utils/sources-tree/index.ts diff --git a/src/devtools/client/debugger/src/utils/sources-tree/sortTree.js b/src/devtools/client/debugger/src/utils/sources-tree/sortTree.ts similarity index 89% rename from src/devtools/client/debugger/src/utils/sources-tree/sortTree.js rename to src/devtools/client/debugger/src/utils/sources-tree/sortTree.ts index d8ab6851e60..0bde98dff0f 100644 --- a/src/devtools/client/debugger/src/utils/sources-tree/sortTree.js +++ b/src/devtools/client/debugger/src/utils/sources-tree/sortTree.ts @@ -6,13 +6,15 @@ import { nodeHasChildren, isExactUrlMatch } from "./utils"; +import type { TreeNode, TreeSource, TreeDirectory, ParentMap } from "./types"; + /** * Look at the nodes in the source tree, and determine the index of where to * insert a new node. The ordering is index -> folder -> file. * @memberof utils/sources-tree * @static */ -export function sortTree(tree, debuggeeUrl = "") { +export function sortTree(tree: TreeDirectory, debuggeeUrl = "") { return tree.contents.sort((previousNode, currentNode) => { const currentNodeIsDir = nodeHasChildren(currentNode); const previousNodeIsDir = nodeHasChildren(previousNode); diff --git a/src/devtools/client/debugger/src/utils/sources-tree/treeOrder.js b/src/devtools/client/debugger/src/utils/sources-tree/treeOrder.ts similarity index 83% rename from src/devtools/client/debugger/src/utils/sources-tree/treeOrder.js rename to src/devtools/client/debugger/src/utils/sources-tree/treeOrder.ts index aeb72bf24cb..8d7d11036e8 100644 --- a/src/devtools/client/debugger/src/utils/sources-tree/treeOrder.js +++ b/src/devtools/client/debugger/src/utils/sources-tree/treeOrder.ts @@ -8,10 +8,13 @@ import { parse } from "../url"; import { nodeHasChildren } from "./utils"; +import type { Source } from "../../reducers/sources"; +import type { TreeNode, TreeSource, TreeDirectory, ParentMap } from "./types"; + /* * Gets domain from url (without www prefix) */ -export function getDomain(url) { +export function getDomain(url?: string) { if (!url) { return null; } @@ -25,7 +28,7 @@ export function getDomain(url) { /* * Checks if node name matches debugger host/domain. */ -function isExactDomainMatch(part, debuggeeHost) { +function isExactDomainMatch(part: string, debuggeeHost?: string) { return part.startsWith("www.") ? part.substr("www.".length) === debuggeeHost : part === debuggeeHost; @@ -34,7 +37,7 @@ function isExactDomainMatch(part, debuggeeHost) { /* * Checks if node name matches IndexName */ -function isIndexName(part, ...rest) { +function isIndexName(part: string, ...rest: any[]) { return part === IndexName; } @@ -45,6 +48,8 @@ function isIndexName(part, ...rest) { * in sorting order, or zero if the node is found. */ +export type FindNodeInContentsMatcher = (node: TreeNode) => number; + /* * Performs a binary search to insert a node into contents. Returns positive * number, index of the found child, or negative number, which can be used @@ -52,7 +57,7 @@ function isIndexName(part, ...rest) { * The matcher is a function that returns result of comparision of a node with * lookup value. */ -export function findNodeInContents(tree, matcher) { +export function findNodeInContents(tree: TreeNode, matcher: FindNodeInContentsMatcher) { if (tree.type === "source" || tree.contents.length === 0) { return { found: false, index: 0 }; } @@ -91,8 +96,14 @@ const matcherFunctions = [isIndexName, isExactDomainMatch]; * - hosts/directories (not files) sorted by name * - files sorted by name */ -export function createTreeNodeMatcher(part, isDir, debuggeeHost, source, sortByUrl) { - return node => { +export function createTreeNodeMatcher( + part: string, + isDir: boolean, + debuggeeHost?: string, + source?: Source, + sortByUrl?: boolean +) { + return (node: TreeNode) => { for (let i = 0; i < matcherFunctions.length; i++) { // Check part against exceptions if (matcherFunctions[i](part, debuggeeHost)) { @@ -122,7 +133,7 @@ export function createTreeNodeMatcher(part, isDir, debuggeeHost, source, sortByU } if (sortByUrl && node.type === "source" && source) { - return node.contents.url.localeCompare(source.url); + return node.contents.url!.localeCompare(source.url!); } if (isExactDomainMatch(part, node.name)) { diff --git a/src/devtools/client/debugger/src/utils/sources-tree/types.js b/src/devtools/client/debugger/src/utils/sources-tree/types.js deleted file mode 100644 index a4b1fb9cdc2..00000000000 --- a/src/devtools/client/debugger/src/utils/sources-tree/types.js +++ /dev/null @@ -1,11 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at . */ - -// - -/** - * TODO: createNode is exported so this type could be useful to other modules - * @memberof utils/sources-tree - * @static - */ diff --git a/src/devtools/client/debugger/src/utils/sources-tree/types.ts b/src/devtools/client/debugger/src/utils/sources-tree/types.ts new file mode 100644 index 00000000000..e194ae0ad50 --- /dev/null +++ b/src/devtools/client/debugger/src/utils/sources-tree/types.ts @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// These types are copy-pasted from the Mozilla Flow types at: +// https://hg.mozilla.org/mozilla-central/file/fd9f980e368173439465e38f6257557500f45c02/devtools/client/debugger/src/types.js + +import type { Source } from "devtools/client/debugger/src/reducers/sources"; + +export type TreeNode = TreeSource | TreeDirectory; + +export type TreeSource = { + type: "source"; + name: string; + path: string; + contents: Source; +}; + +export type TreeDirectory = { + type: "directory"; + name: string; + path: string; + contents: TreeNode[]; +}; + +export type ParentMap = WeakMap; + +export type SourcesGroups = { + sourcesInside: Source[]; + sourcesOuside: Source[]; +}; diff --git a/src/devtools/client/debugger/src/utils/sources-tree/updateTree.js b/src/devtools/client/debugger/src/utils/sources-tree/updateTree.ts similarity index 60% rename from src/devtools/client/debugger/src/utils/sources-tree/updateTree.js rename to src/devtools/client/debugger/src/utils/sources-tree/updateTree.ts index e14b22e06e0..4e87daedb11 100644 --- a/src/devtools/client/debugger/src/utils/sources-tree/updateTree.js +++ b/src/devtools/client/debugger/src/utils/sources-tree/updateTree.ts @@ -9,7 +9,13 @@ import { collapseTree } from "./collapseTree"; import { createDirectoryNode, createParentMap } from "./utils"; import { getDomain } from "./treeOrder"; -function getSourcesToAdd(newSources, prevSources) { +import type { Source } from "../../reducers/sources"; +import type { TreeNode, TreeSource, TreeDirectory, ParentMap } from "./types"; +import type { ParsedUrl } from "./getURL"; + +type SourcesMap = Record; + +function getSourcesToAdd(newSources: SourcesMap, prevSources: SourcesMap) { const sourcesToAdd = []; for (const sourceId in newSources) { @@ -23,7 +29,7 @@ function getSourcesToAdd(newSources, prevSources) { return sourcesToAdd; } -export function createTree({ debuggeeUrl, sources }) { +export function createTree({ debuggeeUrl, sources }: { debuggeeUrl: string; sources: SourcesMap }) { const uncollapsedTree = createDirectoryNode("root", "", []); return updateTree({ @@ -34,13 +40,27 @@ export function createTree({ debuggeeUrl, sources }) { }); } -export function updateTree({ newSources, prevSources, debuggeeUrl, uncollapsedTree }) { +interface UpdateTreeArgs { + newSources: SourcesMap; + prevSources: SourcesMap; + uncollapsedTree: TreeDirectory; + debuggeeUrl: string; + sourceTree?: TreeNode; +} + +export function updateTree({ + newSources, + prevSources, + debuggeeUrl, + uncollapsedTree, +}: UpdateTreeArgs) { const debuggeeHost = getDomain(debuggeeUrl); + // @ts-expect-error This used to be nested records - somehow it still works? const sourcesToAdd = getSourcesToAdd(Object.values(newSources), Object.values(prevSources)); for (const source of sourcesToAdd) { - addToTree(uncollapsedTree, source, debuggeeHost); + addToTree(uncollapsedTree, source, debuggeeHost!); } const newSourceTree = collapseTree(uncollapsedTree); diff --git a/src/devtools/client/debugger/src/utils/sources-tree/utils.js b/src/devtools/client/debugger/src/utils/sources-tree/utils.ts similarity index 65% rename from src/devtools/client/debugger/src/utils/sources-tree/utils.js rename to src/devtools/client/debugger/src/utils/sources-tree/utils.ts index d355e361f86..5069cec5d35 100644 --- a/src/devtools/client/debugger/src/utils/sources-tree/utils.js +++ b/src/devtools/client/debugger/src/utils/sources-tree/utils.ts @@ -4,16 +4,20 @@ // -import { parse } from "../../utils/url"; +import { parse } from "../url"; -import { isPretty } from "../source"; -import { getURL } from "./getURL"; +import type { Source } from "../../reducers/sources"; +import type { TreeNode, TreeSource, TreeDirectory, ParentMap } from "./types"; -export function nodeHasChildren(item) { +// Additional TS types ported from Mozilla Flow types: +// https://hg.mozilla.org/mozilla-central/file/fd9f980e368173439465e38f6257557500f45c02/devtools/client/debugger/src/utils/sources-tree +export type SourcesMap = Record; + +export function nodeHasChildren(item: TreeNode) { return item.type == "directory" && Array.isArray(item.contents); } -export function isExactUrlMatch(pathPart, debuggeeUrl) { +export function isExactUrlMatch(pathPart: string, debuggeeUrl: string) { // compare to hostname with an optional 'www.' prefix const { host } = parse(debuggeeUrl); if (!host) { @@ -22,7 +26,7 @@ export function isExactUrlMatch(pathPart, debuggeeUrl) { return host === pathPart || host.replace(/^www\./, "") === pathPart.replace(/^www\./, ""); } -export function isPathDirectory(path) { +export function isPathDirectory(path: string) { // Assume that all urls point to files except when they end with '/' // Or directory node has children @@ -54,27 +58,31 @@ export function isPathDirectory(path) { } } -export function isDirectory(item) { +export function isDirectory(item: TreeNode) { return (item.type === "directory" || isPathDirectory(item.path)) && item.name != "(index)"; } -export function getSourceFromNode(item) { +export function getSourceFromNode(item: TreeNode) { const { contents } = item; if (!isDirectory(item) && !Array.isArray(contents)) { return contents; } } -export function isSource(item) { +export function isSource(item: TreeNode) { return item.type === "source"; } -export function partIsFile(index, parts, url) { +export function partIsFile(index: number, parts: string[], url: TreeNode) { const isLastPart = index === parts.length - 1; return isLastPart && !isDirectory(url); } -export function createDirectoryNode(name, path, contents) { +export function createDirectoryNode( + name: string, + path: string, + contents: TreeNode[] +): TreeDirectory { return { type: "directory", name, @@ -83,7 +91,7 @@ export function createDirectoryNode(name, path, contents) { }; } -export function createSourceNode(name, path, contents) { +export function createSourceNode(name: string, path: string, contents: Source): TreeSource { return { type: "source", name, @@ -92,10 +100,10 @@ export function createSourceNode(name, path, contents) { }; } -export function createParentMap(tree) { +export function createParentMap(tree: TreeNode) { const map = new WeakMap(); - function _traverse(subtree) { + function _traverse(subtree: TreeNode) { if (subtree.type === "directory") { for (const child of subtree.contents) { map.set(child, subtree); @@ -113,7 +121,7 @@ export function createParentMap(tree) { return map; } -export function getRelativePath(url) { +export function getRelativePath(url: string) { const { pathname } = parse(url); if (!pathname) { return url; @@ -123,12 +131,12 @@ export function getRelativePath(url) { return index !== -1 ? pathname.slice(index + 1) : ""; } -export function getRelativePathWithoutFile(url) { +export function getRelativePathWithoutFile(url: string) { const path = getRelativePath(url); return path.slice(0, path.lastIndexOf("/")); } -export function getPathWithoutThread(path) { +export function getPathWithoutThread(path: string) { const pathParts = path.split(/(context\d+?\/)/).splice(2); if (pathParts && pathParts.length > 0) { return pathParts.join(""); diff --git a/src/devtools/client/debugger/src/utils/url.js b/src/devtools/client/debugger/src/utils/url.ts similarity index 64% rename from src/devtools/client/debugger/src/utils/url.js rename to src/devtools/client/debugger/src/utils/url.ts index 4ddfc2d77fb..ae10f636f78 100644 --- a/src/devtools/client/debugger/src/utils/url.js +++ b/src/devtools/client/debugger/src/utils/url.ts @@ -4,7 +4,24 @@ import memoize from "lodash/memoize"; -const defaultUrl = { +interface ParsedURL { + hash: string; + host: string; + hostname: string; + href: string; + origin: string; + password: string; + path: string; + pathname: string; + port: string; + protocol: string; + search: string; + // This should be a "URLSearchParams" object + searchParams: Record; + username: string; +} + +const defaultUrl: ParsedURL = { hash: "", host: "", hostname: "", @@ -21,14 +38,14 @@ const defaultUrl = { username: "", }; -export const parse = memoize(function parse(url) { +export const parse = memoize(function parse(url): ParsedURL { try { if (url.startsWith("webpack://_N_E")) { url = `webpack:${url.substring(14)}`; } else if (url.startsWith("webpack-internal:///.")) { url = `webpack-internal:${url.substring(21)}`; } - const urlObj = new URL(url); + const urlObj = new URL(url) as unknown as ParsedURL; urlObj.path = urlObj.pathname + urlObj.search; return urlObj; } catch (err) { @@ -41,6 +58,6 @@ export const parse = memoize(function parse(url) { } }); -export function sameOrigin(firstUrl, secondUrl) { +export function sameOrigin(firstUrl: string, secondUrl: string) { return parse(firstUrl).origin == parse(secondUrl).origin; } diff --git a/src/ui/utils/mixpanel.ts b/src/ui/utils/mixpanel.ts index 930cda89353..ccd455c9efb 100644 --- a/src/ui/utils/mixpanel.ts +++ b/src/ui/utils/mixpanel.ts @@ -91,6 +91,7 @@ type MixpanelEvent = | ["share_modal.set_private"] | ["share_modal.set_public"] | ["share_modal.set_team"] + | ["source_explorer.select_source"] | ["sources.select_location"] | ["team_change", { workspaceId: WorkspaceId | null }] | ["team.change_default", { workspaceUuid: WorkspaceUuid | null }]