/* 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 . */ // @flow import { parse } from "../../utils/url"; import type { TreeNode, TreeSource, TreeDirectory, ParentMap } from "./types"; import type { Source, Thread, URL } from "../../types"; import type { SourcesMapByThread } from "../../reducers/types"; import { isPretty } from "../source"; import { getURL, type ParsedURL } from "./getURL"; const IGNORED_URLS = ["debugger eval code", "XStringBundle"]; export type PathPart = { part: string, path: string, debuggeeHostIfRoot: ?string, }; export function getPathParts( url: ParsedURL, thread: string, debuggeeHost: ?string ): Array { const parts = url.path.split("/"); if (parts.length > 1 && parts[parts.length - 1] === "") { parts.pop(); if (url.search) { parts.push(url.search); } } else { parts[parts.length - 1] += url.search; } parts[0] = url.group; if (thread) { parts.unshift(thread); } let path = ""; return parts.map((part, index) => { if (index == 0 && thread) { path = thread; } else { path = `${path}/${part}`; } const debuggeeHostIfRoot = index === 1 ? debuggeeHost : null; return { part, path, debuggeeHostIfRoot, }; }); } export function nodeHasChildren(item: TreeNode): boolean { return item.type == "directory" && Array.isArray(item.contents); } export function isExactUrlMatch(pathPart: string, debuggeeUrl: URL): boolean { // compare to hostname with an optional 'www.' prefix const { host } = parse(debuggeeUrl); if (!host) { return false; } return ( host === pathPart || host.replace(/^www\./, "") === pathPart.replace(/^www\./, "") ); } export function isPathDirectory(path: string): boolean { // Assume that all urls point to files except when they end with '/' // Or directory node has children if (path.endsWith("/")) { return true; } let separators = 0; for (let i = 0; i < path.length - 1; ++i) { if (path[i] === "/") { if (path[i + i] !== "/") { return false; } ++separators; } } switch (separators) { case 0: { return false; } case 1: { return !path.startsWith("/"); } default: { return true; } } } export function isDirectory(item: TreeNode): boolean { return ( (item.type === "directory" || isPathDirectory(item.path)) && item.name != "(index)" ); } export function getSourceFromNode(item: TreeNode): ?Source { const { contents } = item; if (!isDirectory(item) && !Array.isArray(contents)) { return contents; } } export function isSource(item: TreeNode): boolean { return item.type === "source"; } export function getFileExtension(source: Source): string { const { path } = getURL(source); if (!path) { return ""; } const lastIndex = path.lastIndexOf("."); return lastIndex !== -1 ? path.slice(lastIndex + 1) : ""; } export function isNotJavaScript(source: Source): boolean { return ["css", "svg", "png"].includes(getFileExtension(source)); } export function isInvalidUrl(url: ParsedURL, source: Source): boolean { return ( !source.url || !url.group || isNotJavaScript(source) || IGNORED_URLS.includes(url) || isPretty(source) ); } export function partIsFile( index: number, parts: Array, url: Object ): boolean { const isLastPart = index === parts.length - 1; return isLastPart && !isDirectory(url); } export function createDirectoryNode( name: string, path: string, contents: TreeNode[] ): TreeDirectory { return { type: "directory", name, path, contents, }; } export function createSourceNode( name: string, path: string, contents: Source ): TreeSource { return { type: "source", name, path, contents, }; } export function createParentMap(tree: TreeNode): ParentMap { const map = new WeakMap(); function _traverse(subtree) { if (subtree.type === "directory") { for (const child of subtree.contents) { map.set(child, subtree); _traverse(child); } } } if (tree.type === "directory") { // Don't link each top-level path to the "root" node because the // user never sees the root tree.contents.forEach(_traverse); } return map; } export function getRelativePath(url: URL): string { const { pathname } = parse(url); if (!pathname) { return url; } const index = pathname.indexOf("/"); return index !== -1 ? pathname.slice(index + 1) : ""; } export function getPathWithoutThread(path: string): string { const pathParts = path.split(/(context\d+?\/)/).splice(2); if (pathParts && pathParts.length > 0) { return pathParts.join(""); } return ""; } export function findSource( { threads, sources }: { threads: Thread[], sources: SourcesMapByThread }, itemPath: string, source: ?Source ): ?Source { const targetThread = threads.find(thread => itemPath.includes(thread.actor)); if (targetThread && source) { const { actor } = targetThread; if (sources[actor]) { return sources[actor][source.id]; } } return source; } // NOTE: we get the source from sources because item.contents is cached export function getSource( item: TreeNode, { threads, sources }: { threads: Thread[], sources: SourcesMapByThread } ): ?Source { const source = getSourceFromNode(item); return findSource({ threads, sources }, item.path, source); } export function getChildren(item: $Shape) { return nodeHasChildren(item) ? item.contents : []; } export function getAllSources({ threads, sources, }: { threads: Thread[], sources: SourcesMapByThread, }): Source[] { const sourcesAll = []; threads.forEach(thread => { const { actor } = thread; for (const source in sources[actor]) { sourcesAll.push(sources[actor][source]); } }); return sourcesAll; } export function getSourcesInsideGroup( item: TreeNode, { threads, sources }: { threads: Thread[], sources: SourcesMapByThread } ): Source[] { const sourcesInsideDirectory = []; const findAllSourcesInsideDirectory = (directoryToSearch: TreeDirectory) => { const childrenItems = getChildren(directoryToSearch); childrenItems.forEach((itemChild: TreeNode) => { if (itemChild.type === "directory") { findAllSourcesInsideDirectory(itemChild); } else { const source = getSource(itemChild, { threads, sources }); if (source) { sourcesInsideDirectory.push(source); } } }); }; if (item.type === "directory") { findAllSourcesInsideDirectory(item); } return sourcesInsideDirectory; }