From 276ee6f542ea2c144d115214abf7aa4a7b8c2445 Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews Date: Sun, 11 Apr 2021 22:19:29 -0700 Subject: [PATCH 01/13] Move search JS into search-index.js Export a few variables and functions into the global scope because they are needed both by main.js and search-index.js. --- src/librustdoc/html/render/write_shared.rs | 5 +- src/librustdoc/html/static/main.js | 1537 +------------------- src/librustdoc/html/static/search.js | 1512 +++++++++++++++++++ src/librustdoc/html/static_files.rs | 4 + 4 files changed, 1534 insertions(+), 1524 deletions(-) create mode 100644 src/librustdoc/html/static/search.js diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 8fb6d68f3c6bc..4240c986f5779 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -408,9 +408,8 @@ pub(super) fn write_shared( // with rustdoc running in parallel. all_indexes.sort(); write_crate("search-index.js", &|| { - let mut v = String::from("var searchIndex = JSON.parse('{\\\n"); - v.push_str(&all_indexes.join(",\\\n")); - v.push_str("\\\n}');\ninitSearch(searchIndex);"); + let v = static_files::SEARCH_JS + .replace(r#""SEARCH_INDEX_PLACEHOLDER": {}"#, &all_indexes.join(",\\\n")); Ok(v.into_bytes()) })?; diff --git a/src/librustdoc/html/static/main.js b/src/librustdoc/html/static/main.js index 80dc6b923f68d..7d7825f0f65d9 100644 --- a/src/librustdoc/html/static/main.js +++ b/src/librustdoc/html/static/main.js @@ -1,4 +1,3 @@ -// ignore-tidy-filelength // Local js definitions: /* global addClass, getSettingValue, hasClass */ /* global onEach, onEachLazy, hasOwnProperty, removeClass, updateLocalStorage */ @@ -173,37 +172,8 @@ function hideThemeButtonState() { (function() { "use strict"; - // This mapping table should match the discriminants of - // `rustdoc::html::item_type::ItemType` type in Rust. - var itemTypes = ["mod", - "externcrate", - "import", - "struct", - "enum", - "fn", - "type", - "static", - "trait", - "impl", - "tymethod", - "method", - "structfield", - "variant", - "macro", - "primitive", - "associatedtype", - "constant", - "associatedconstant", - "union", - "foreigntype", - "keyword", - "existential", - "attr", - "derive", - "traitalias"]; - var disableShortcuts = getSettingValue("disable-shortcuts") === "true"; - var search_input = getSearchInput(); + window.search_input = getSearchInput(); var searchTimeout = null; var toggleAllDocsId = "toggle-all-docs"; @@ -212,28 +182,19 @@ function hideThemeButtonState() { // 0 for "In Names" // 1 for "In Parameters" // 2 for "In Return Types" - var currentTab = 0; + window.currentTab = 0; - var mouseMovedAfterSearch = true; + window.mouseMovedAfterSearch = true; var titleBeforeSearch = document.title; - var searchTitle = null; - - function removeEmptyStringsFromArray(x) { - for (var i = 0, len = x.length; i < len; ++i) { - if (x[i] === "") { - x.splice(i, 1); - i -= 1; - } - } - } + window.searchTitle = null; - function clearInputTimeout() { + window.clearInputTimeout = function() { if (searchTimeout !== null) { clearTimeout(searchTimeout); searchTimeout = null; } - } + }; function getPageId() { if (window.location.hash) { @@ -276,7 +237,7 @@ function hideThemeButtonState() { document.getElementsByTagName("body")[0].style.marginTop = ""; } - function showSearchResults(search) { + window.showSearchResults = function(search) { if (search === null || typeof search === 'undefined') { search = getSearchElement(); } @@ -284,9 +245,9 @@ function hideThemeButtonState() { removeClass(search, "hidden"); mouseMovedAfterSearch = false; document.title = searchTitle; - } + }; - function hideSearchResults(search) { + window.hideSearchResults = function(search) { if (search === null || typeof search === 'undefined') { search = getSearchElement(); } @@ -298,13 +259,9 @@ function hideThemeButtonState() { history.replaceState("", window.currentCrate + " - Rust", getNakedUrl() + window.location.hash); } - } - - // used for special search precedence - var TY_PRIMITIVE = itemTypes.indexOf("primitive"); - var TY_KEYWORD = itemTypes.indexOf("keyword"); + }; - function getQueryStringParams() { + window.getQueryStringParams = function() { var params = {}; window.location.search.substring(1).split("&"). map(function(s) { @@ -313,11 +270,11 @@ function hideThemeButtonState() { typeof pair[1] === "undefined" ? null : decodeURIComponent(pair[1]); }); return params; - } + }; - function browserSupportsHistoryApi() { + window.browserSupportsHistoryApi = function() { return window.history && typeof window.history.pushState === "function"; - } + }; function isHidden(elem) { return elem.offsetHeight === 0; @@ -686,1444 +643,6 @@ function hideThemeButtonState() { } }()); - /** - * A function to compute the Levenshtein distance between two strings - * Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported - * Full License can be found at http://creativecommons.org/licenses/by-sa/3.0/legalcode - * This code is an unmodified version of the code written by Marco de Wit - * and was found at http://stackoverflow.com/a/18514751/745719 - */ - var levenshtein_row2 = []; - function levenshtein(s1, s2) { - if (s1 === s2) { - return 0; - } - var s1_len = s1.length, s2_len = s2.length; - if (s1_len && s2_len) { - var i1 = 0, i2 = 0, a, b, c, c2, row = levenshtein_row2; - while (i1 < s1_len) { - row[i1] = ++i1; - } - while (i2 < s2_len) { - c2 = s2.charCodeAt(i2); - a = i2; - ++i2; - b = i2; - for (i1 = 0; i1 < s1_len; ++i1) { - c = a + (s1.charCodeAt(i1) !== c2 ? 1 : 0); - a = row[i1]; - b = b < a ? (b < c ? b + 1 : c) : (a < c ? a + 1 : c); - row[i1] = b; - } - } - return b; - } - return s1_len + s2_len; - } - - window.initSearch = function(rawSearchIndex) { - var MAX_LEV_DISTANCE = 3; - var MAX_RESULTS = 200; - var GENERICS_DATA = 1; - var NAME = 0; - var INPUTS_DATA = 0; - var OUTPUT_DATA = 1; - var NO_TYPE_FILTER = -1; - var currentResults, index, searchIndex; - var ALIASES = {}; - var params = getQueryStringParams(); - - // Populate search bar with query string search term when provided, - // but only if the input bar is empty. This avoid the obnoxious issue - // where you start trying to do a search, and the index loads, and - // suddenly your search is gone! - if (search_input.value === "") { - search_input.value = params.search || ""; - } - - /** - * Executes the query and builds an index of results - * @param {[Object]} query [The user query] - * @param {[type]} searchWords [The list of search words to query - * against] - * @param {[type]} filterCrates [Crate to search in if defined] - * @return {[type]} [A search index of results] - */ - function execQuery(query, searchWords, filterCrates) { - function itemTypeFromName(typename) { - for (var i = 0, len = itemTypes.length; i < len; ++i) { - if (itemTypes[i] === typename) { - return i; - } - } - return NO_TYPE_FILTER; - } - - var valLower = query.query.toLowerCase(), - val = valLower, - typeFilter = itemTypeFromName(query.type), - results = {}, results_in_args = {}, results_returned = {}, - split = valLower.split("::"); - - removeEmptyStringsFromArray(split); - - function transformResults(results, isType) { - var out = []; - for (var i = 0, len = results.length; i < len; ++i) { - if (results[i].id > -1) { - var obj = searchIndex[results[i].id]; - obj.lev = results[i].lev; - if (isType !== true || obj.type) { - var res = buildHrefAndPath(obj); - obj.displayPath = pathSplitter(res[0]); - obj.fullPath = obj.displayPath + obj.name; - // To be sure than it some items aren't considered as duplicate. - obj.fullPath += "|" + obj.ty; - obj.href = res[1]; - out.push(obj); - if (out.length >= MAX_RESULTS) { - break; - } - } - } - } - return out; - } - - function sortResults(results, isType) { - var ar = []; - for (var entry in results) { - if (hasOwnProperty(results, entry)) { - ar.push(results[entry]); - } - } - results = ar; - var i, len, result; - for (i = 0, len = results.length; i < len; ++i) { - result = results[i]; - result.word = searchWords[result.id]; - result.item = searchIndex[result.id] || {}; - } - // if there are no results then return to default and fail - if (results.length === 0) { - return []; - } - - results.sort(function(aaa, bbb) { - var a, b; - - // sort by exact match with regard to the last word (mismatch goes later) - a = (aaa.word !== val); - b = (bbb.word !== val); - if (a !== b) { return a - b; } - - // Sort by non levenshtein results and then levenshtein results by the distance - // (less changes required to match means higher rankings) - a = (aaa.lev); - b = (bbb.lev); - if (a !== b) { return a - b; } - - // sort by crate (non-current crate goes later) - a = (aaa.item.crate !== window.currentCrate); - b = (bbb.item.crate !== window.currentCrate); - if (a !== b) { return a - b; } - - // sort by item name length (longer goes later) - a = aaa.word.length; - b = bbb.word.length; - if (a !== b) { return a - b; } - - // sort by item name (lexicographically larger goes later) - a = aaa.word; - b = bbb.word; - if (a !== b) { return (a > b ? +1 : -1); } - - // sort by index of keyword in item name (no literal occurrence goes later) - a = (aaa.index < 0); - b = (bbb.index < 0); - if (a !== b) { return a - b; } - // (later literal occurrence, if any, goes later) - a = aaa.index; - b = bbb.index; - if (a !== b) { return a - b; } - - // special precedence for primitive and keyword pages - if ((aaa.item.ty === TY_PRIMITIVE && bbb.item.ty !== TY_KEYWORD) || - (aaa.item.ty === TY_KEYWORD && bbb.item.ty !== TY_PRIMITIVE)) { - return -1; - } - if ((bbb.item.ty === TY_PRIMITIVE && aaa.item.ty !== TY_PRIMITIVE) || - (bbb.item.ty === TY_KEYWORD && aaa.item.ty !== TY_KEYWORD)) { - return 1; - } - - // sort by description (no description goes later) - a = (aaa.item.desc === ""); - b = (bbb.item.desc === ""); - if (a !== b) { return a - b; } - - // sort by type (later occurrence in `itemTypes` goes later) - a = aaa.item.ty; - b = bbb.item.ty; - if (a !== b) { return a - b; } - - // sort by path (lexicographically larger goes later) - a = aaa.item.path; - b = bbb.item.path; - if (a !== b) { return (a > b ? +1 : -1); } - - // que sera, sera - return 0; - }); - - for (i = 0, len = results.length; i < len; ++i) { - var result = results[i]; - - // this validation does not make sense when searching by types - if (result.dontValidate) { - continue; - } - var name = result.item.name.toLowerCase(), - path = result.item.path.toLowerCase(), - parent = result.item.parent; - - if (isType !== true && - validateResult(name, path, split, parent) === false) - { - result.id = -1; - } - } - return transformResults(results); - } - - function extractGenerics(val) { - val = val.toLowerCase(); - if (val.indexOf("<") !== -1) { - var values = val.substring(val.indexOf("<") + 1, val.lastIndexOf(">")); - return { - name: val.substring(0, val.indexOf("<")), - generics: values.split(/\s*,\s*/), - }; - } - return { - name: val, - generics: [], - }; - } - - function getObjectNameFromId(id) { - if (typeof id === "number") { - return searchIndex[id].name; - } - return id; - } - - function checkGenerics(obj, val) { - // The names match, but we need to be sure that all generics kinda - // match as well. - var tmp_lev, elem_name; - if (val.generics.length > 0) { - if (obj.length > GENERICS_DATA && - obj[GENERICS_DATA].length >= val.generics.length) { - var elems = Object.create(null); - var elength = object[GENERICS_DATA].length; - for (var x = 0; x < elength; ++x) { - elems[getObjectNameFromId(obj[GENERICS_DATA][x])] += 1; - } - var total = 0; - var done = 0; - // We need to find the type that matches the most to remove it in order - // to move forward. - var vlength = val.generics.length; - for (x = 0; x < vlength; ++x) { - var lev = MAX_LEV_DISTANCE + 1; - var firstGeneric = getObjectNameFromId(val.generics[x]); - var match = null; - if (elems[firstGeneric]) { - match = firstGeneric; - lev = 0; - } else { - for (elem_name in elems) { - tmp_lev = levenshtein(elem_name, firstGeneric); - if (tmp_lev < lev) { - lev = tmp_lev; - match = elem_name; - } - } - } - if (match !== null) { - elems[match] -= 1; - if (elems[match] == 0) { - delete elems[match]; - } - total += lev; - done += 1; - } else { - return MAX_LEV_DISTANCE + 1; - } - } - return Math.ceil(total / done); - } - } - return MAX_LEV_DISTANCE + 1; - } - - // Check for type name and type generics (if any). - function checkType(obj, val, literalSearch) { - var lev_distance = MAX_LEV_DISTANCE + 1; - var len, x, firstGeneric; - if (obj[NAME] === val.name) { - if (literalSearch === true) { - if (val.generics && val.generics.length !== 0) { - if (obj.length > GENERICS_DATA && - obj[GENERICS_DATA].length >= val.generics.length) { - var elems = Object.create(null); - len = obj[GENERICS_DATA].length; - for (x = 0; x < len; ++x) { - elems[getObjectNameFromId(obj[GENERICS_DATA][x])] += 1; - } - - var allFound = true; - len = val.generics.length; - for (x = 0; x < len; ++x) { - firstGeneric = getObjectNameFromId(val.generics[x]); - if (elems[firstGeneric]) { - elems[firstGeneric] -= 1; - } else { - allFound = false; - break; - } - } - if (allFound === true) { - return true; - } - } else { - return false; - } - } - return true; - } - // If the type has generics but don't match, then it won't return at this point. - // Otherwise, `checkGenerics` will return 0 and it'll return. - if (obj.length > GENERICS_DATA && obj[GENERICS_DATA].length !== 0) { - var tmp_lev = checkGenerics(obj, val); - if (tmp_lev <= MAX_LEV_DISTANCE) { - return tmp_lev; - } - } else { - return 0; - } - } - // Names didn't match so let's check if one of the generic types could. - if (literalSearch === true) { - if (obj.length > GENERICS_DATA && obj[GENERICS_DATA].length > 0) { - return obj[GENERICS_DATA].some( - function(name) { - return name === val.name; - }); - } - return false; - } - lev_distance = Math.min(levenshtein(obj[NAME], val.name), lev_distance); - if (lev_distance <= MAX_LEV_DISTANCE) { - // The generics didn't match but the name kinda did so we give it - // a levenshtein distance value that isn't *this* good so it goes - // into the search results but not too high. - lev_distance = Math.ceil((checkGenerics(obj, val) + lev_distance) / 2); - } else if (obj.length > GENERICS_DATA && obj[GENERICS_DATA].length > 0) { - // We can check if the type we're looking for is inside the generics! - var olength = obj[GENERICS_DATA].length; - for (x = 0; x < olength; ++x) { - lev_distance = Math.min(levenshtein(obj[GENERICS_DATA][x], val.name), - lev_distance); - } - } - // Now whatever happens, the returned distance is "less good" so we should mark it - // as such, and so we add 1 to the distance to make it "less good". - return lev_distance + 1; - } - - function findArg(obj, val, literalSearch, typeFilter) { - var lev_distance = MAX_LEV_DISTANCE + 1; - - if (obj && obj.type && obj.type[INPUTS_DATA] && obj.type[INPUTS_DATA].length > 0) { - var length = obj.type[INPUTS_DATA].length; - for (var i = 0; i < length; i++) { - var tmp = obj.type[INPUTS_DATA][i]; - if (typePassesFilter(typeFilter, tmp[1]) === false) { - continue; - } - tmp = checkType(tmp, val, literalSearch); - if (literalSearch === true) { - if (tmp === true) { - return true; - } - continue; - } - lev_distance = Math.min(tmp, lev_distance); - if (lev_distance === 0) { - return 0; - } - } - } - return literalSearch === true ? false : lev_distance; - } - - function checkReturned(obj, val, literalSearch, typeFilter) { - var lev_distance = MAX_LEV_DISTANCE + 1; - - if (obj && obj.type && obj.type.length > OUTPUT_DATA) { - var ret = obj.type[OUTPUT_DATA]; - if (typeof ret[0] === "string") { - ret = [ret]; - } - for (var x = 0, len = ret.length; x < len; ++x) { - var tmp = ret[x]; - if (typePassesFilter(typeFilter, tmp[1]) === false) { - continue; - } - tmp = checkType(tmp, val, literalSearch); - if (literalSearch === true) { - if (tmp === true) { - return true; - } - continue; - } - lev_distance = Math.min(tmp, lev_distance); - if (lev_distance === 0) { - return 0; - } - } - } - return literalSearch === true ? false : lev_distance; - } - - function checkPath(contains, lastElem, ty) { - if (contains.length === 0) { - return 0; - } - var ret_lev = MAX_LEV_DISTANCE + 1; - var path = ty.path.split("::"); - - if (ty.parent && ty.parent.name) { - path.push(ty.parent.name.toLowerCase()); - } - - var length = path.length; - var clength = contains.length; - if (clength > length) { - return MAX_LEV_DISTANCE + 1; - } - for (var i = 0; i < length; ++i) { - if (i + clength > length) { - break; - } - var lev_total = 0; - var aborted = false; - for (var x = 0; x < clength; ++x) { - var lev = levenshtein(path[i + x], contains[x]); - if (lev > MAX_LEV_DISTANCE) { - aborted = true; - break; - } - lev_total += lev; - } - if (aborted === false) { - ret_lev = Math.min(ret_lev, Math.round(lev_total / clength)); - } - } - return ret_lev; - } - - function typePassesFilter(filter, type) { - // No filter - if (filter <= NO_TYPE_FILTER) return true; - - // Exact match - if (filter === type) return true; - - // Match related items - var name = itemTypes[type]; - switch (itemTypes[filter]) { - case "constant": - return name === "associatedconstant"; - case "fn": - return name === "method" || name === "tymethod"; - case "type": - return name === "primitive" || name === "associatedtype"; - case "trait": - return name === "traitalias"; - } - - // No match - return false; - } - - function createAliasFromItem(item) { - return { - crate: item.crate, - name: item.name, - path: item.path, - desc: item.desc, - ty: item.ty, - parent: item.parent, - type: item.type, - is_alias: true, - }; - } - - function handleAliases(ret, query, filterCrates) { - // We separate aliases and crate aliases because we want to have current crate - // aliases to be before the others in the displayed results. - var aliases = []; - var crateAliases = []; - if (filterCrates !== undefined) { - if (ALIASES[filterCrates] && ALIASES[filterCrates][query.search]) { - var query_aliases = ALIASES[filterCrates][query.search]; - var len = query_aliases.length; - for (var i = 0; i < len; ++i) { - aliases.push(createAliasFromItem(searchIndex[query_aliases[i]])); - } - } - } else { - Object.keys(ALIASES).forEach(function(crate) { - if (ALIASES[crate][query.search]) { - var pushTo = crate === window.currentCrate ? crateAliases : aliases; - var query_aliases = ALIASES[crate][query.search]; - var len = query_aliases.length; - for (var i = 0; i < len; ++i) { - pushTo.push(createAliasFromItem(searchIndex[query_aliases[i]])); - } - } - }); - } - - var sortFunc = function(aaa, bbb) { - if (aaa.path < bbb.path) { - return 1; - } else if (aaa.path === bbb.path) { - return 0; - } - return -1; - }; - crateAliases.sort(sortFunc); - aliases.sort(sortFunc); - - var pushFunc = function(alias) { - alias.alias = query.raw; - var res = buildHrefAndPath(alias); - alias.displayPath = pathSplitter(res[0]); - alias.fullPath = alias.displayPath + alias.name; - alias.href = res[1]; - - ret.others.unshift(alias); - if (ret.others.length > MAX_RESULTS) { - ret.others.pop(); - } - }; - onEach(aliases, pushFunc); - onEach(crateAliases, pushFunc); - } - - // quoted values mean literal search - var nSearchWords = searchWords.length; - var i, it; - var ty; - var fullId; - var returned; - var in_args; - var len; - if ((val.charAt(0) === "\"" || val.charAt(0) === "'") && - val.charAt(val.length - 1) === val.charAt(0)) - { - val = extractGenerics(val.substr(1, val.length - 2)); - for (i = 0; i < nSearchWords; ++i) { - if (filterCrates !== undefined && searchIndex[i].crate !== filterCrates) { - continue; - } - in_args = findArg(searchIndex[i], val, true, typeFilter); - returned = checkReturned(searchIndex[i], val, true, typeFilter); - ty = searchIndex[i]; - fullId = ty.id; - - if (searchWords[i] === val.name - && typePassesFilter(typeFilter, searchIndex[i].ty) - && results[fullId] === undefined) { - results[fullId] = { - id: i, - index: -1, - dontValidate: true, - }; - } - if (in_args === true && results_in_args[fullId] === undefined) { - results_in_args[fullId] = { - id: i, - index: -1, - dontValidate: true, - }; - } - if (returned === true && results_returned[fullId] === undefined) { - results_returned[fullId] = { - id: i, - index: -1, - dontValidate: true, - }; - } - } - query.inputs = [val]; - query.output = val; - query.search = val; - // searching by type - } else if (val.search("->") > -1) { - var trimmer = function(s) { return s.trim(); }; - var parts = val.split("->").map(trimmer); - var input = parts[0]; - // sort inputs so that order does not matter - var inputs = input.split(",").map(trimmer).sort(); - for (i = 0, len = inputs.length; i < len; ++i) { - inputs[i] = extractGenerics(inputs[i]); - } - var output = extractGenerics(parts[1]); - - for (i = 0; i < nSearchWords; ++i) { - if (filterCrates !== undefined && searchIndex[i].crate !== filterCrates) { - continue; - } - var type = searchIndex[i].type; - ty = searchIndex[i]; - if (!type) { - continue; - } - fullId = ty.id; - - returned = checkReturned(ty, output, true, NO_TYPE_FILTER); - if (output.name === "*" || returned === true) { - in_args = false; - var is_module = false; - - if (input === "*") { - is_module = true; - } else { - var allFound = true; - for (it = 0, len = inputs.length; allFound === true && it < len; it++) { - allFound = checkType(type, inputs[it], true); - } - in_args = allFound; - } - if (in_args === true) { - results_in_args[fullId] = { - id: i, - index: -1, - dontValidate: true, - }; - } - if (returned === true) { - results_returned[fullId] = { - id: i, - index: -1, - dontValidate: true, - }; - } - if (is_module === true) { - results[fullId] = { - id: i, - index: -1, - dontValidate: true, - }; - } - } - } - query.inputs = inputs.map(function(input) { - return input.name; - }); - query.output = output.name; - } else { - query.inputs = [val]; - query.output = val; - query.search = val; - // gather matching search results up to a certain maximum - val = val.replace(/\_/g, ""); - - var valGenerics = extractGenerics(val); - - var paths = valLower.split("::"); - removeEmptyStringsFromArray(paths); - val = paths[paths.length - 1]; - var contains = paths.slice(0, paths.length > 1 ? paths.length - 1 : 1); - - var lev, j; - for (j = 0; j < nSearchWords; ++j) { - ty = searchIndex[j]; - if (!ty || (filterCrates !== undefined && ty.crate !== filterCrates)) { - continue; - } - var lev_add = 0; - if (paths.length > 1) { - lev = checkPath(contains, paths[paths.length - 1], ty); - if (lev > MAX_LEV_DISTANCE) { - continue; - } else if (lev > 0) { - lev_add = lev / 10; - } - } - - returned = MAX_LEV_DISTANCE + 1; - in_args = MAX_LEV_DISTANCE + 1; - var index = -1; - // we want lev results to go lower than others - lev = MAX_LEV_DISTANCE + 1; - fullId = ty.id; - - if (searchWords[j].indexOf(split[i]) > -1 || - searchWords[j].indexOf(val) > -1 || - ty.normalizedName.indexOf(val) > -1) - { - // filter type: ... queries - if (typePassesFilter(typeFilter, ty.ty) && results[fullId] === undefined) { - index = ty.normalizedName.indexOf(val); - } - } - if ((lev = levenshtein(searchWords[j], val)) <= MAX_LEV_DISTANCE) { - if (typePassesFilter(typeFilter, ty.ty) === false) { - lev = MAX_LEV_DISTANCE + 1; - } else { - lev += 1; - } - } - in_args = findArg(ty, valGenerics, false, typeFilter); - returned = checkReturned(ty, valGenerics, false, typeFilter); - - lev += lev_add; - if (lev > 0 && val.length > 3 && searchWords[j].indexOf(val) > -1) { - if (val.length < 6) { - lev -= 1; - } else { - lev = 0; - } - } - if (in_args <= MAX_LEV_DISTANCE) { - if (results_in_args[fullId] === undefined) { - results_in_args[fullId] = { - id: j, - index: index, - lev: in_args, - }; - } - results_in_args[fullId].lev = - Math.min(results_in_args[fullId].lev, in_args); - } - if (returned <= MAX_LEV_DISTANCE) { - if (results_returned[fullId] === undefined) { - results_returned[fullId] = { - id: j, - index: index, - lev: returned, - }; - } - results_returned[fullId].lev = - Math.min(results_returned[fullId].lev, returned); - } - if (index !== -1 || lev <= MAX_LEV_DISTANCE) { - if (index !== -1 && paths.length < 2) { - lev = 0; - } - if (results[fullId] === undefined) { - results[fullId] = { - id: j, - index: index, - lev: lev, - }; - } - results[fullId].lev = Math.min(results[fullId].lev, lev); - } - } - } - - var ret = { - "in_args": sortResults(results_in_args, true), - "returned": sortResults(results_returned, true), - "others": sortResults(results), - }; - handleAliases(ret, query, filterCrates); - return ret; - } - - /** - * Validate performs the following boolean logic. For example: - * "File::open" will give IF A PARENT EXISTS => ("file" && "open") - * exists in (name || path || parent) OR => ("file" && "open") exists in - * (name || path ) - * - * This could be written functionally, but I wanted to minimise - * functions on stack. - * - * @param {[string]} name [The name of the result] - * @param {[string]} path [The path of the result] - * @param {[string]} keys [The keys to be used (["file", "open"])] - * @param {[object]} parent [The parent of the result] - * @return {[boolean]} [Whether the result is valid or not] - */ - function validateResult(name, path, keys, parent) { - for (var i = 0, len = keys.length; i < len; ++i) { - // each check is for validation so we negate the conditions and invalidate - if (!( - // check for an exact name match - name.indexOf(keys[i]) > -1 || - // then an exact path match - path.indexOf(keys[i]) > -1 || - // next if there is a parent, check for exact parent match - (parent !== undefined && parent.name !== undefined && - parent.name.toLowerCase().indexOf(keys[i]) > -1) || - // lastly check to see if the name was a levenshtein match - levenshtein(name, keys[i]) <= MAX_LEV_DISTANCE)) { - return false; - } - } - return true; - } - - function getQuery(raw) { - var matches, type, query; - query = raw; - - matches = query.match(/^(fn|mod|struct|enum|trait|type|const|macro)\s*:\s*/i); - if (matches) { - type = matches[1].replace(/^const$/, "constant"); - query = query.substring(matches[0].length); - } - - return { - raw: raw, - query: query, - type: type, - id: query + type - }; - } - - function initSearchNav() { - var hoverTimeout; - - var click_func = function(e) { - var el = e.target; - // to retrieve the real "owner" of the event. - while (el.tagName !== "TR") { - el = el.parentNode; - } - var dst = e.target.getElementsByTagName("a"); - if (dst.length < 1) { - return; - } - dst = dst[0]; - if (window.location.pathname === dst.pathname) { - hideSearchResults(); - document.location.href = dst.href; - } - }; - var mouseover_func = function(e) { - if (mouseMovedAfterSearch) { - var el = e.target; - // to retrieve the real "owner" of the event. - while (el.tagName !== "TR") { - el = el.parentNode; - } - clearTimeout(hoverTimeout); - hoverTimeout = setTimeout(function() { - onEachLazy(document.getElementsByClassName("search-results"), function(e) { - onEachLazy(e.getElementsByClassName("result"), function(i_e) { - removeClass(i_e, "highlighted"); - }); - }); - addClass(el, "highlighted"); - }, 20); - } - }; - onEachLazy(document.getElementsByClassName("search-results"), function(e) { - onEachLazy(e.getElementsByClassName("result"), function(i_e) { - i_e.onclick = click_func; - i_e.onmouseover = mouseover_func; - }); - }); - - search_input.onkeydown = function(e) { - // "actives" references the currently highlighted item in each search tab. - // Each array in "actives" represents a tab. - var actives = [[], [], []]; - // "current" is used to know which tab we're looking into. - var current = 0; - onEachLazy(document.getElementById("results").childNodes, function(e) { - onEachLazy(e.getElementsByClassName("highlighted"), function(h_e) { - actives[current].push(h_e); - }); - current += 1; - }); - - if (e.which === 38) { // up - if (e.ctrlKey) { // Going through result tabs. - printTab(currentTab > 0 ? currentTab - 1 : 2); - } else { - if (!actives[currentTab].length || - !actives[currentTab][0].previousElementSibling) { - return; - } - addClass(actives[currentTab][0].previousElementSibling, "highlighted"); - removeClass(actives[currentTab][0], "highlighted"); - } - e.preventDefault(); - } else if (e.which === 40) { // down - if (e.ctrlKey) { // Going through result tabs. - printTab(currentTab > 1 ? 0 : currentTab + 1); - } else if (!actives[currentTab].length) { - var results = document.getElementById("results").childNodes; - if (results.length > 0) { - var res = results[currentTab].getElementsByClassName("result"); - if (res.length > 0) { - addClass(res[0], "highlighted"); - } - } - } else if (actives[currentTab][0].nextElementSibling) { - addClass(actives[currentTab][0].nextElementSibling, "highlighted"); - removeClass(actives[currentTab][0], "highlighted"); - } - e.preventDefault(); - } else if (e.which === 13) { // return - if (actives[currentTab].length) { - document.location.href = - actives[currentTab][0].getElementsByTagName("a")[0].href; - } - } else if (e.which === 16) { // shift - // Does nothing, it's just to avoid losing "focus" on the highlighted element. - } else if (actives[currentTab].length > 0) { - removeClass(actives[currentTab][0], "highlighted"); - } - }; - } - - function buildHrefAndPath(item) { - var displayPath; - var href; - var type = itemTypes[item.ty]; - var name = item.name; - var path = item.path; - - if (type === "mod") { - displayPath = path + "::"; - href = window.rootPath + path.replace(/::/g, "/") + "/" + - name + "/index.html"; - } else if (type === "primitive" || type === "keyword") { - displayPath = ""; - href = window.rootPath + path.replace(/::/g, "/") + - "/" + type + "." + name + ".html"; - } else if (type === "externcrate") { - displayPath = ""; - href = window.rootPath + name + "/index.html"; - } else if (item.parent !== undefined) { - var myparent = item.parent; - var anchor = "#" + type + "." + name; - var parentType = itemTypes[myparent.ty]; - var pageType = parentType; - var pageName = myparent.name; - - if (parentType === "primitive") { - displayPath = myparent.name + "::"; - } else if (type === "structfield" && parentType === "variant") { - // Structfields belonging to variants are special: the - // final path element is the enum name. - var enumNameIdx = item.path.lastIndexOf("::"); - var enumName = item.path.substr(enumNameIdx + 2); - path = item.path.substr(0, enumNameIdx); - displayPath = path + "::" + enumName + "::" + myparent.name + "::"; - anchor = "#variant." + myparent.name + ".field." + name; - pageType = "enum"; - pageName = enumName; - } else { - displayPath = path + "::" + myparent.name + "::"; - } - href = window.rootPath + path.replace(/::/g, "/") + - "/" + pageType + - "." + pageName + - ".html" + anchor; - } else { - displayPath = item.path + "::"; - href = window.rootPath + item.path.replace(/::/g, "/") + - "/" + type + "." + name + ".html"; - } - return [displayPath, href]; - } - - function escape(content) { - var h1 = document.createElement("h1"); - h1.textContent = content; - return h1.innerHTML; - } - - function pathSplitter(path) { - var tmp = "" + path.replace(/::/g, "::"); - if (tmp.endsWith("")) { - return tmp.slice(0, tmp.length - 6); - } - return tmp; - } - - function addTab(array, query, display) { - var extraStyle = ""; - if (display === false) { - extraStyle = " style=\"display: none;\""; - } - - var output = ""; - var duplicates = {}; - var length = 0; - if (array.length > 0) { - output = ""; - - array.forEach(function(item) { - var name, type; - - name = item.name; - type = itemTypes[item.ty]; - - if (item.is_alias !== true) { - if (duplicates[item.fullPath]) { - return; - } - duplicates[item.fullPath] = true; - } - length += 1; - - output += ""; - }); - output += "
" + - "" + - (item.is_alias === true ? - ("" + item.alias + "  - see ") : "") + - item.displayPath + "" + - name + "" + - "" + - "" + item.desc + - " 
"; - } else { - output = "
No results :(
" + - "Try on DuckDuckGo?

" + - "Or try looking in one of these:
"; - } - return [output, length]; - } - - function makeTabHeader(tabNb, text, nbElems) { - if (currentTab === tabNb) { - return ""; - } - return ""; - } - - function showResults(results) { - var search = getSearchElement(); - if (results.others.length === 1 - && getSettingValue("go-to-only-result") === "true" - // By default, the search DOM element is "empty" (meaning it has no children not - // text content). Once a search has been run, it won't be empty, even if you press - // ESC or empty the search input (which also "cancels" the search). - && (!search.firstChild || search.firstChild.innerText !== getSearchLoadingText())) - { - var elem = document.createElement("a"); - elem.href = results.others[0].href; - elem.style.display = "none"; - // For firefox, we need the element to be in the DOM so it can be clicked. - document.body.appendChild(elem); - elem.click(); - return; - } - var query = getQuery(search_input.value); - - currentResults = query.id; - - var ret_others = addTab(results.others, query); - var ret_in_args = addTab(results.in_args, query, false); - var ret_returned = addTab(results.returned, query, false); - - // Navigate to the relevant tab if the current tab is empty, like in case users search - // for "-> String". If they had selected another tab previously, they have to click on - // it again. - if ((currentTab === 0 && ret_others[1] === 0) || - (currentTab === 1 && ret_in_args[1] === 0) || - (currentTab === 2 && ret_returned[1] === 0)) { - if (ret_others[1] !== 0) { - currentTab = 0; - } else if (ret_in_args[1] !== 0) { - currentTab = 1; - } else if (ret_returned[1] !== 0) { - currentTab = 2; - } - } - - var output = "

Results for " + escape(query.query) + - (query.type ? " (type: " + escape(query.type) + ")" : "") + "

" + - "
" + - makeTabHeader(0, "In Names", ret_others[1]) + - makeTabHeader(1, "In Parameters", ret_in_args[1]) + - makeTabHeader(2, "In Return Types", ret_returned[1]) + - "
" + - ret_others[0] + ret_in_args[0] + ret_returned[0] + "
"; - - search.innerHTML = output; - showSearchResults(search); - initSearchNav(); - var elems = document.getElementById("titles").childNodes; - elems[0].onclick = function() { printTab(0); }; - elems[1].onclick = function() { printTab(1); }; - elems[2].onclick = function() { printTab(2); }; - printTab(currentTab); - } - - function execSearch(query, searchWords, filterCrates) { - function getSmallest(arrays, positions, notDuplicates) { - var start = null; - - for (var it = 0, len = positions.length; it < len; ++it) { - if (arrays[it].length > positions[it] && - (start === null || start > arrays[it][positions[it]].lev) && - !notDuplicates[arrays[it][positions[it]].fullPath]) { - start = arrays[it][positions[it]].lev; - } - } - return start; - } - - function mergeArrays(arrays) { - var ret = []; - var positions = []; - var notDuplicates = {}; - - for (var x = 0, arrays_len = arrays.length; x < arrays_len; ++x) { - positions.push(0); - } - while (ret.length < MAX_RESULTS) { - var smallest = getSmallest(arrays, positions, notDuplicates); - - if (smallest === null) { - break; - } - for (x = 0; x < arrays_len && ret.length < MAX_RESULTS; ++x) { - if (arrays[x].length > positions[x] && - arrays[x][positions[x]].lev === smallest && - !notDuplicates[arrays[x][positions[x]].fullPath]) { - ret.push(arrays[x][positions[x]]); - notDuplicates[arrays[x][positions[x]].fullPath] = true; - positions[x] += 1; - } - } - } - return ret; - } - - var queries = query.raw.split(","); - var results = { - "in_args": [], - "returned": [], - "others": [], - }; - - for (var i = 0, len = queries.length; i < len; ++i) { - query = queries[i].trim(); - if (query.length !== 0) { - var tmp = execQuery(getQuery(query), searchWords, filterCrates); - - results.in_args.push(tmp.in_args); - results.returned.push(tmp.returned); - results.others.push(tmp.others); - } - } - if (queries.length > 1) { - return { - "in_args": mergeArrays(results.in_args), - "returned": mergeArrays(results.returned), - "others": mergeArrays(results.others), - }; - } - return { - "in_args": results.in_args[0], - "returned": results.returned[0], - "others": results.others[0], - }; - } - - function getFilterCrates() { - var elem = document.getElementById("crate-search"); - - if (elem && elem.value !== "All crates" && hasOwnProperty(rawSearchIndex, elem.value)) { - return elem.value; - } - return undefined; - } - - function search(e, forced) { - var params = getQueryStringParams(); - var query = getQuery(search_input.value.trim()); - - if (e) { - e.preventDefault(); - } - - if (query.query.length === 0) { - return; - } - if (forced !== true && query.id === currentResults) { - if (query.query.length > 0) { - putBackSearch(search_input); - } - return; - } - - // Update document title to maintain a meaningful browser history - searchTitle = "Results for " + query.query + " - Rust"; - - // Because searching is incremental by character, only the most - // recent search query is added to the browser history. - if (browserSupportsHistoryApi()) { - var newURL = getNakedUrl() + "?search=" + encodeURIComponent(query.raw) + - window.location.hash; - if (!history.state && !params.search) { - history.pushState(query, "", newURL); - } else { - history.replaceState(query, "", newURL); - } - } - - var filterCrates = getFilterCrates(); - showResults(execSearch(query, index, filterCrates)); - } - - function buildIndex(rawSearchIndex) { - searchIndex = []; - var searchWords = []; - var i, word; - var currentIndex = 0; - var id = 0; - - for (var crate in rawSearchIndex) { - if (!hasOwnProperty(rawSearchIndex, crate)) { continue; } - - var crateSize = 0; - - searchWords.push(crate); - var normalizedName = crate.indexOf("_") === -1 - ? crate - : crate.replace(/_/g, ""); - // This object should have exactly the same set of fields as the "row" - // object defined below. Your JavaScript runtime will thank you. - // https://mathiasbynens.be/notes/shapes-ics - var crateRow = { - crate: crate, - ty: 1, // == ExternCrate - name: crate, - path: "", - desc: rawSearchIndex[crate].doc, - parent: undefined, - type: null, - id: id, - normalizedName: normalizedName, - }; - id += 1; - searchIndex.push(crateRow); - currentIndex += 1; - - // an array of (Number) item types - var itemTypes = rawSearchIndex[crate].t; - // an array of (String) item names - var itemNames = rawSearchIndex[crate].n; - // an array of (String) full paths (or empty string for previous path) - var itemPaths = rawSearchIndex[crate].q; - // an array of (String) descriptions - var itemDescs = rawSearchIndex[crate].d; - // an array of (Number) the parent path index + 1 to `paths`, or 0 if none - var itemParentIdxs = rawSearchIndex[crate].i; - // an array of (Object | null) the type of the function, if any - var itemFunctionSearchTypes = rawSearchIndex[crate].f; - // an array of [(Number) item type, - // (String) name] - var paths = rawSearchIndex[crate].p; - // a array of [(String) alias name - // [Number] index to items] - var aliases = rawSearchIndex[crate].a; - - // convert `rawPaths` entries into object form - var len = paths.length; - for (i = 0; i < len; ++i) { - paths[i] = {ty: paths[i][0], name: paths[i][1]}; - } - - // convert `item*` into an object form, and construct word indices. - // - // before any analysis is performed lets gather the search terms to - // search against apart from the rest of the data. This is a quick - // operation that is cached for the life of the page state so that - // all other search operations have access to this cached data for - // faster analysis operations - len = itemTypes.length; - var lastPath = ""; - for (i = 0; i < len; ++i) { - // This object should have exactly the same set of fields as the "crateRow" - // object defined above. - if (typeof itemNames[i] === "string") { - word = itemNames[i].toLowerCase(); - searchWords.push(word); - } else { - word = ""; - searchWords.push(""); - } - var normalizedName = word.indexOf("_") === -1 - ? word - : word.replace(/_/g, ""); - var row = { - crate: crate, - ty: itemTypes[i], - name: itemNames[i], - path: itemPaths[i] ? itemPaths[i] : lastPath, - desc: itemDescs[i], - parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined, - type: itemFunctionSearchTypes[i], - id: id, - normalizedName: normalizedName, - }; - id += 1; - searchIndex.push(row); - lastPath = row.path; - crateSize += 1; - } - - if (aliases) { - ALIASES[crate] = {}; - var j, local_aliases; - for (var alias_name in aliases) { - if (!aliases.hasOwnProperty(alias_name)) { continue; } - - if (!ALIASES[crate].hasOwnProperty(alias_name)) { - ALIASES[crate][alias_name] = []; - } - local_aliases = aliases[alias_name]; - for (j = 0, len = local_aliases.length; j < len; ++j) { - ALIASES[crate][alias_name].push(local_aliases[j] + currentIndex); - } - } - } - currentIndex += crateSize; - } - return searchWords; - } - - function registerSearchEvents() { - var searchAfter500ms = function() { - clearInputTimeout(); - if (search_input.value.length === 0) { - if (browserSupportsHistoryApi()) { - history.replaceState("", window.currentCrate + " - Rust", - getNakedUrl() + window.location.hash); - } - hideSearchResults(); - } else { - searchTimeout = setTimeout(search, 500); - } - }; - search_input.onkeyup = searchAfter500ms; - search_input.oninput = searchAfter500ms; - document.getElementsByClassName("search-form")[0].onsubmit = function(e) { - e.preventDefault(); - clearInputTimeout(); - search(); - }; - search_input.onchange = function(e) { - if (e.target !== document.activeElement) { - // To prevent doing anything when it's from a blur event. - return; - } - // Do NOT e.preventDefault() here. It will prevent pasting. - clearInputTimeout(); - // zero-timeout necessary here because at the time of event handler execution the - // pasted content is not in the input field yet. Shouldn’t make any difference for - // change, though. - setTimeout(search, 0); - }; - search_input.onpaste = search_input.onchange; - - var selectCrate = document.getElementById("crate-search"); - if (selectCrate) { - selectCrate.onchange = function() { - updateLocalStorage("rustdoc-saved-filter-crate", selectCrate.value); - search(undefined, true); - }; - } - - // Push and pop states are used to add search results to the browser - // history. - if (browserSupportsHistoryApi()) { - // Store the previous so we can revert back to it later. - var previousTitle = document.title; - - window.addEventListener("popstate", function(e) { - var params = getQueryStringParams(); - // Revert to the previous title manually since the History - // API ignores the title parameter. - document.title = previousTitle; - // When browsing forward to search results the previous - // search will be repeated, so the currentResults are - // cleared to ensure the search is successful. - currentResults = null; - // Synchronize search bar with query string state and - // perform the search. This will empty the bar if there's - // nothing there, which lets you really go back to a - // previous state with nothing in the bar. - if (params.search && params.search.length > 0) { - search_input.value = params.search; - // Some browsers fire "onpopstate" for every page load - // (Chrome), while others fire the event only when actually - // popping a state (Firefox), which is why search() is - // called both here and at the end of the startSearch() - // function. - search(e); - } else { - search_input.value = ""; - // When browsing back from search results the main page - // visibility must be reset. - hideSearchResults(); - } - }); - } - - // This is required in firefox to avoid this problem: Navigating to a search result - // with the keyboard, hitting enter, and then hitting back would take you back to - // the doc page, rather than the search that should overlay it. - // This was an interaction between the back-forward cache and our handlers - // that try to sync state between the URL and the search input. To work around it, - // do a small amount of re-init on page show. - window.onpageshow = function(){ - var qSearch = getQueryStringParams().search; - if (search_input.value === "" && qSearch) { - search_input.value = qSearch; - } - search(); - }; - } - - index = buildIndex(rawSearchIndex); - registerSearchEvents(); - // If there's a search term in the URL, execute the search now. - if (getQueryStringParams().search) { - search(); - } - }; - function addSidebarCrates(crates) { // Draw a convenient sidebar of known crates if we have a listing if (window.rootPath === "../" || window.rootPath === "./") { @@ -2847,31 +1366,7 @@ function hideThemeButtonState() { }; }); - // In the search display, allows to switch between tabs. - function printTab(nb) { - if (nb === 0 || nb === 1 || nb === 2) { - currentTab = nb; - } - var nb_copy = nb; - onEachLazy(document.getElementById("titles").childNodes, function(elem) { - if (nb_copy === 0) { - addClass(elem, "selected"); - } else { - removeClass(elem, "selected"); - } - nb_copy -= 1; - }); - onEachLazy(document.getElementById("results").childNodes, function(elem) { - if (nb === 0) { - elem.style.display = ""; - } else { - elem.style.display = "none"; - } - nb -= 1; - }); - } - - function putBackSearch(search_input) { + window.putBackSearch = function(search_input) { var search = getSearchElement(); if (search_input.value !== "" && hasClass(search, "hidden")) { showSearchResults(search); @@ -2882,7 +1377,7 @@ function hideThemeButtonState() { } document.title = searchTitle; } - } + }; function getSearchLoadingText() { return "Loading search results..."; diff --git a/src/librustdoc/html/static/search.js b/src/librustdoc/html/static/search.js new file mode 100644 index 0000000000000..c06de66d7f70d --- /dev/null +++ b/src/librustdoc/html/static/search.js @@ -0,0 +1,1512 @@ +(function() { +var searchIndex = JSON.parse('{\ +"SEARCH_INDEX_PLACEHOLDER": {}\ +}'); + +// This mapping table should match the discriminants of +// `rustdoc::html::item_type::ItemType` type in Rust. +var itemTypes = ["mod", + "externcrate", + "import", + "struct", + "enum", + "fn", + "type", + "static", + "trait", + "impl", + "tymethod", + "method", + "structfield", + "variant", + "macro", + "primitive", + "associatedtype", + "constant", + "associatedconstant", + "union", + "foreigntype", + "keyword", + "existential", + "attr", + "derive", + "traitalias"]; + +// used for special search precedence +var TY_PRIMITIVE = itemTypes.indexOf("primitive"); +var TY_KEYWORD = itemTypes.indexOf("keyword"); + +// In the search display, allows to switch between tabs. +function printTab(nb) { + if (nb === 0 || nb === 1 || nb === 2) { + currentTab = nb; + } + var nb_copy = nb; + onEachLazy(document.getElementById("titles").childNodes, function(elem) { + if (nb_copy === 0) { + addClass(elem, "selected"); + } else { + removeClass(elem, "selected"); + } + nb_copy -= 1; + }); + onEachLazy(document.getElementById("results").childNodes, function(elem) { + if (nb === 0) { + elem.style.display = ""; + } else { + elem.style.display = "none"; + } + nb -= 1; + }); +} + +function removeEmptyStringsFromArray(x) { + for (var i = 0, len = x.length; i < len; ++i) { + if (x[i] === "") { + x.splice(i, 1); + i -= 1; + } + } +} + +/** + * A function to compute the Levenshtein distance between two strings + * Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported + * Full License can be found at http://creativecommons.org/licenses/by-sa/3.0/legalcode + * This code is an unmodified version of the code written by Marco de Wit + * and was found at http://stackoverflow.com/a/18514751/745719 + */ +var levenshtein_row2 = []; +function levenshtein(s1, s2) { + if (s1 === s2) { + return 0; + } + var s1_len = s1.length, s2_len = s2.length; + if (s1_len && s2_len) { + var i1 = 0, i2 = 0, a, b, c, c2, row = levenshtein_row2; + while (i1 < s1_len) { + row[i1] = ++i1; + } + while (i2 < s2_len) { + c2 = s2.charCodeAt(i2); + a = i2; + ++i2; + b = i2; + for (i1 = 0; i1 < s1_len; ++i1) { + c = a + (s1.charCodeAt(i1) !== c2 ? 1 : 0); + a = row[i1]; + b = b < a ? (b < c ? b + 1 : c) : (a < c ? a + 1 : c); + row[i1] = b; + } + } + return b; + } + return s1_len + s2_len; +} + +function initSearch(rawSearchIndex) { + var MAX_LEV_DISTANCE = 3; + var MAX_RESULTS = 200; + var GENERICS_DATA = 1; + var NAME = 0; + var INPUTS_DATA = 0; + var OUTPUT_DATA = 1; + var NO_TYPE_FILTER = -1; + var currentResults, index, searchIndex; + var ALIASES = {}; + var params = getQueryStringParams(); + + // Populate search bar with query string search term when provided, + // but only if the input bar is empty. This avoid the obnoxious issue + // where you start trying to do a search, and the index loads, and + // suddenly your search is gone! + if (search_input.value === "") { + search_input.value = params.search || ""; + } + + /** + * Executes the query and builds an index of results + * @param {[Object]} query [The user query] + * @param {[type]} searchWords [The list of search words to query + * against] + * @param {[type]} filterCrates [Crate to search in if defined] + * @return {[type]} [A search index of results] + */ + function execQuery(query, searchWords, filterCrates) { + function itemTypeFromName(typename) { + for (var i = 0, len = itemTypes.length; i < len; ++i) { + if (itemTypes[i] === typename) { + return i; + } + } + return NO_TYPE_FILTER; + } + + var valLower = query.query.toLowerCase(), + val = valLower, + typeFilter = itemTypeFromName(query.type), + results = {}, results_in_args = {}, results_returned = {}, + split = valLower.split("::"); + + removeEmptyStringsFromArray(split); + + function transformResults(results, isType) { + var out = []; + for (var i = 0, len = results.length; i < len; ++i) { + if (results[i].id > -1) { + var obj = searchIndex[results[i].id]; + obj.lev = results[i].lev; + if (isType !== true || obj.type) { + var res = buildHrefAndPath(obj); + obj.displayPath = pathSplitter(res[0]); + obj.fullPath = obj.displayPath + obj.name; + // To be sure than it some items aren't considered as duplicate. + obj.fullPath += "|" + obj.ty; + obj.href = res[1]; + out.push(obj); + if (out.length >= MAX_RESULTS) { + break; + } + } + } + } + return out; + } + + function sortResults(results, isType) { + var ar = []; + for (var entry in results) { + if (hasOwnProperty(results, entry)) { + ar.push(results[entry]); + } + } + results = ar; + var i, len, result; + for (i = 0, len = results.length; i < len; ++i) { + result = results[i]; + result.word = searchWords[result.id]; + result.item = searchIndex[result.id] || {}; + } + // if there are no results then return to default and fail + if (results.length === 0) { + return []; + } + + results.sort(function(aaa, bbb) { + var a, b; + + // sort by exact match with regard to the last word (mismatch goes later) + a = (aaa.word !== val); + b = (bbb.word !== val); + if (a !== b) { return a - b; } + + // Sort by non levenshtein results and then levenshtein results by the distance + // (less changes required to match means higher rankings) + a = (aaa.lev); + b = (bbb.lev); + if (a !== b) { return a - b; } + + // sort by crate (non-current crate goes later) + a = (aaa.item.crate !== window.currentCrate); + b = (bbb.item.crate !== window.currentCrate); + if (a !== b) { return a - b; } + + // sort by item name length (longer goes later) + a = aaa.word.length; + b = bbb.word.length; + if (a !== b) { return a - b; } + + // sort by item name (lexicographically larger goes later) + a = aaa.word; + b = bbb.word; + if (a !== b) { return (a > b ? +1 : -1); } + + // sort by index of keyword in item name (no literal occurrence goes later) + a = (aaa.index < 0); + b = (bbb.index < 0); + if (a !== b) { return a - b; } + // (later literal occurrence, if any, goes later) + a = aaa.index; + b = bbb.index; + if (a !== b) { return a - b; } + + // special precedence for primitive and keyword pages + if ((aaa.item.ty === TY_PRIMITIVE && bbb.item.ty !== TY_KEYWORD) || + (aaa.item.ty === TY_KEYWORD && bbb.item.ty !== TY_PRIMITIVE)) { + return -1; + } + if ((bbb.item.ty === TY_PRIMITIVE && aaa.item.ty !== TY_PRIMITIVE) || + (bbb.item.ty === TY_KEYWORD && aaa.item.ty !== TY_KEYWORD)) { + return 1; + } + + // sort by description (no description goes later) + a = (aaa.item.desc === ""); + b = (bbb.item.desc === ""); + if (a !== b) { return a - b; } + + // sort by type (later occurrence in `itemTypes` goes later) + a = aaa.item.ty; + b = bbb.item.ty; + if (a !== b) { return a - b; } + + // sort by path (lexicographically larger goes later) + a = aaa.item.path; + b = bbb.item.path; + if (a !== b) { return (a > b ? +1 : -1); } + + // que sera, sera + return 0; + }); + + for (i = 0, len = results.length; i < len; ++i) { + var result = results[i]; + + // this validation does not make sense when searching by types + if (result.dontValidate) { + continue; + } + var name = result.item.name.toLowerCase(), + path = result.item.path.toLowerCase(), + parent = result.item.parent; + + if (isType !== true && + validateResult(name, path, split, parent) === false) + { + result.id = -1; + } + } + return transformResults(results); + } + + function extractGenerics(val) { + val = val.toLowerCase(); + if (val.indexOf("<") !== -1) { + var values = val.substring(val.indexOf("<") + 1, val.lastIndexOf(">")); + return { + name: val.substring(0, val.indexOf("<")), + generics: values.split(/\s*,\s*/), + }; + } + return { + name: val, + generics: [], + }; + } + + function getObjectNameFromId(id) { + if (typeof id === "number") { + return searchIndex[id].name; + } + return id; + } + + function checkGenerics(obj, val) { + // The names match, but we need to be sure that all generics kinda + // match as well. + var tmp_lev, elem_name; + if (val.generics.length > 0) { + if (obj.length > GENERICS_DATA && + obj[GENERICS_DATA].length >= val.generics.length) { + var elems = Object.create(null); + var elength = object[GENERICS_DATA].length; + for (var x = 0; x < elength; ++x) { + elems[getObjectNameFromId(obj[GENERICS_DATA][x])] += 1; + } + var total = 0; + var done = 0; + // We need to find the type that matches the most to remove it in order + // to move forward. + var vlength = val.generics.length; + for (x = 0; x < vlength; ++x) { + var lev = MAX_LEV_DISTANCE + 1; + var firstGeneric = getObjectNameFromId(val.generics[x]); + var match = null; + if (elems[firstGeneric]) { + match = firstGeneric; + lev = 0; + } else { + for (elem_name in elems) { + tmp_lev = levenshtein(elem_name, firstGeneric); + if (tmp_lev < lev) { + lev = tmp_lev; + match = elem_name; + } + } + } + if (match !== null) { + elems[match] -= 1; + if (elems[match] == 0) { + delete elems[match]; + } + total += lev; + done += 1; + } else { + return MAX_LEV_DISTANCE + 1; + } + } + return Math.ceil(total / done); + } + } + return MAX_LEV_DISTANCE + 1; + } + + // Check for type name and type generics (if any). + function checkType(obj, val, literalSearch) { + var lev_distance = MAX_LEV_DISTANCE + 1; + var len, x, firstGeneric; + if (obj[NAME] === val.name) { + if (literalSearch === true) { + if (val.generics && val.generics.length !== 0) { + if (obj.length > GENERICS_DATA && + obj[GENERICS_DATA].length >= val.generics.length) { + var elems = Object.create(null); + len = obj[GENERICS_DATA].length; + for (x = 0; x < len; ++x) { + elems[getObjectNameFromId(obj[GENERICS_DATA][x])] += 1; + } + + var allFound = true; + len = val.generics.length; + for (x = 0; x < len; ++x) { + firstGeneric = getObjectNameFromId(val.generics[x]); + if (elems[firstGeneric]) { + elems[firstGeneric] -= 1; + } else { + allFound = false; + break; + } + } + if (allFound === true) { + return true; + } + } else { + return false; + } + } + return true; + } + // If the type has generics but don't match, then it won't return at this point. + // Otherwise, `checkGenerics` will return 0 and it'll return. + if (obj.length > GENERICS_DATA && obj[GENERICS_DATA].length !== 0) { + var tmp_lev = checkGenerics(obj, val); + if (tmp_lev <= MAX_LEV_DISTANCE) { + return tmp_lev; + } + } else { + return 0; + } + } + // Names didn't match so let's check if one of the generic types could. + if (literalSearch === true) { + if (obj.length > GENERICS_DATA && obj[GENERICS_DATA].length > 0) { + return obj[GENERICS_DATA].some( + function(name) { + return name === val.name; + }); + } + return false; + } + lev_distance = Math.min(levenshtein(obj[NAME], val.name), lev_distance); + if (lev_distance <= MAX_LEV_DISTANCE) { + // The generics didn't match but the name kinda did so we give it + // a levenshtein distance value that isn't *this* good so it goes + // into the search results but not too high. + lev_distance = Math.ceil((checkGenerics(obj, val) + lev_distance) / 2); + } else if (obj.length > GENERICS_DATA && obj[GENERICS_DATA].length > 0) { + // We can check if the type we're looking for is inside the generics! + var olength = obj[GENERICS_DATA].length; + for (x = 0; x < olength; ++x) { + lev_distance = Math.min(levenshtein(obj[GENERICS_DATA][x], val.name), + lev_distance); + } + } + // Now whatever happens, the returned distance is "less good" so we should mark it + // as such, and so we add 1 to the distance to make it "less good". + return lev_distance + 1; + } + + function findArg(obj, val, literalSearch, typeFilter) { + var lev_distance = MAX_LEV_DISTANCE + 1; + + if (obj && obj.type && obj.type[INPUTS_DATA] && obj.type[INPUTS_DATA].length > 0) { + var length = obj.type[INPUTS_DATA].length; + for (var i = 0; i < length; i++) { + var tmp = obj.type[INPUTS_DATA][i]; + if (typePassesFilter(typeFilter, tmp[1]) === false) { + continue; + } + tmp = checkType(tmp, val, literalSearch); + if (literalSearch === true) { + if (tmp === true) { + return true; + } + continue; + } + lev_distance = Math.min(tmp, lev_distance); + if (lev_distance === 0) { + return 0; + } + } + } + return literalSearch === true ? false : lev_distance; + } + + function checkReturned(obj, val, literalSearch, typeFilter) { + var lev_distance = MAX_LEV_DISTANCE + 1; + + if (obj && obj.type && obj.type.length > OUTPUT_DATA) { + var ret = obj.type[OUTPUT_DATA]; + if (typeof ret[0] === "string") { + ret = [ret]; + } + for (var x = 0, len = ret.length; x < len; ++x) { + var tmp = ret[x]; + if (typePassesFilter(typeFilter, tmp[1]) === false) { + continue; + } + tmp = checkType(tmp, val, literalSearch); + if (literalSearch === true) { + if (tmp === true) { + return true; + } + continue; + } + lev_distance = Math.min(tmp, lev_distance); + if (lev_distance === 0) { + return 0; + } + } + } + return literalSearch === true ? false : lev_distance; + } + + function checkPath(contains, lastElem, ty) { + if (contains.length === 0) { + return 0; + } + var ret_lev = MAX_LEV_DISTANCE + 1; + var path = ty.path.split("::"); + + if (ty.parent && ty.parent.name) { + path.push(ty.parent.name.toLowerCase()); + } + + var length = path.length; + var clength = contains.length; + if (clength > length) { + return MAX_LEV_DISTANCE + 1; + } + for (var i = 0; i < length; ++i) { + if (i + clength > length) { + break; + } + var lev_total = 0; + var aborted = false; + for (var x = 0; x < clength; ++x) { + var lev = levenshtein(path[i + x], contains[x]); + if (lev > MAX_LEV_DISTANCE) { + aborted = true; + break; + } + lev_total += lev; + } + if (aborted === false) { + ret_lev = Math.min(ret_lev, Math.round(lev_total / clength)); + } + } + return ret_lev; + } + + function typePassesFilter(filter, type) { + // No filter + if (filter <= NO_TYPE_FILTER) return true; + + // Exact match + if (filter === type) return true; + + // Match related items + var name = itemTypes[type]; + switch (itemTypes[filter]) { + case "constant": + return name === "associatedconstant"; + case "fn": + return name === "method" || name === "tymethod"; + case "type": + return name === "primitive" || name === "associatedtype"; + case "trait": + return name === "traitalias"; + } + + // No match + return false; + } + + function createAliasFromItem(item) { + return { + crate: item.crate, + name: item.name, + path: item.path, + desc: item.desc, + ty: item.ty, + parent: item.parent, + type: item.type, + is_alias: true, + }; + } + + function handleAliases(ret, query, filterCrates) { + // We separate aliases and crate aliases because we want to have current crate + // aliases to be before the others in the displayed results. + var aliases = []; + var crateAliases = []; + if (filterCrates !== undefined) { + if (ALIASES[filterCrates] && ALIASES[filterCrates][query.search]) { + var query_aliases = ALIASES[filterCrates][query.search]; + var len = query_aliases.length; + for (var i = 0; i < len; ++i) { + aliases.push(createAliasFromItem(searchIndex[query_aliases[i]])); + } + } + } else { + Object.keys(ALIASES).forEach(function(crate) { + if (ALIASES[crate][query.search]) { + var pushTo = crate === window.currentCrate ? crateAliases : aliases; + var query_aliases = ALIASES[crate][query.search]; + var len = query_aliases.length; + for (var i = 0; i < len; ++i) { + pushTo.push(createAliasFromItem(searchIndex[query_aliases[i]])); + } + } + }); + } + + var sortFunc = function(aaa, bbb) { + if (aaa.path < bbb.path) { + return 1; + } else if (aaa.path === bbb.path) { + return 0; + } + return -1; + }; + crateAliases.sort(sortFunc); + aliases.sort(sortFunc); + + var pushFunc = function(alias) { + alias.alias = query.raw; + var res = buildHrefAndPath(alias); + alias.displayPath = pathSplitter(res[0]); + alias.fullPath = alias.displayPath + alias.name; + alias.href = res[1]; + + ret.others.unshift(alias); + if (ret.others.length > MAX_RESULTS) { + ret.others.pop(); + } + }; + onEach(aliases, pushFunc); + onEach(crateAliases, pushFunc); + } + + // quoted values mean literal search + var nSearchWords = searchWords.length; + var i, it; + var ty; + var fullId; + var returned; + var in_args; + var len; + if ((val.charAt(0) === "\"" || val.charAt(0) === "'") && + val.charAt(val.length - 1) === val.charAt(0)) + { + val = extractGenerics(val.substr(1, val.length - 2)); + for (i = 0; i < nSearchWords; ++i) { + if (filterCrates !== undefined && searchIndex[i].crate !== filterCrates) { + continue; + } + in_args = findArg(searchIndex[i], val, true, typeFilter); + returned = checkReturned(searchIndex[i], val, true, typeFilter); + ty = searchIndex[i]; + fullId = ty.id; + + if (searchWords[i] === val.name + && typePassesFilter(typeFilter, searchIndex[i].ty) + && results[fullId] === undefined) { + results[fullId] = { + id: i, + index: -1, + dontValidate: true, + }; + } + if (in_args === true && results_in_args[fullId] === undefined) { + results_in_args[fullId] = { + id: i, + index: -1, + dontValidate: true, + }; + } + if (returned === true && results_returned[fullId] === undefined) { + results_returned[fullId] = { + id: i, + index: -1, + dontValidate: true, + }; + } + } + query.inputs = [val]; + query.output = val; + query.search = val; + // searching by type + } else if (val.search("->") > -1) { + var trimmer = function(s) { return s.trim(); }; + var parts = val.split("->").map(trimmer); + var input = parts[0]; + // sort inputs so that order does not matter + var inputs = input.split(",").map(trimmer).sort(); + for (i = 0, len = inputs.length; i < len; ++i) { + inputs[i] = extractGenerics(inputs[i]); + } + var output = extractGenerics(parts[1]); + + for (i = 0; i < nSearchWords; ++i) { + if (filterCrates !== undefined && searchIndex[i].crate !== filterCrates) { + continue; + } + var type = searchIndex[i].type; + ty = searchIndex[i]; + if (!type) { + continue; + } + fullId = ty.id; + + returned = checkReturned(ty, output, true, NO_TYPE_FILTER); + if (output.name === "*" || returned === true) { + in_args = false; + var is_module = false; + + if (input === "*") { + is_module = true; + } else { + var allFound = true; + for (it = 0, len = inputs.length; allFound === true && it < len; it++) { + allFound = checkType(type, inputs[it], true); + } + in_args = allFound; + } + if (in_args === true) { + results_in_args[fullId] = { + id: i, + index: -1, + dontValidate: true, + }; + } + if (returned === true) { + results_returned[fullId] = { + id: i, + index: -1, + dontValidate: true, + }; + } + if (is_module === true) { + results[fullId] = { + id: i, + index: -1, + dontValidate: true, + }; + } + } + } + query.inputs = inputs.map(function(input) { + return input.name; + }); + query.output = output.name; + } else { + query.inputs = [val]; + query.output = val; + query.search = val; + // gather matching search results up to a certain maximum + val = val.replace(/\_/g, ""); + + var valGenerics = extractGenerics(val); + + var paths = valLower.split("::"); + removeEmptyStringsFromArray(paths); + val = paths[paths.length - 1]; + var contains = paths.slice(0, paths.length > 1 ? paths.length - 1 : 1); + + var lev, j; + for (j = 0; j < nSearchWords; ++j) { + ty = searchIndex[j]; + if (!ty || (filterCrates !== undefined && ty.crate !== filterCrates)) { + continue; + } + var lev_add = 0; + if (paths.length > 1) { + lev = checkPath(contains, paths[paths.length - 1], ty); + if (lev > MAX_LEV_DISTANCE) { + continue; + } else if (lev > 0) { + lev_add = lev / 10; + } + } + + returned = MAX_LEV_DISTANCE + 1; + in_args = MAX_LEV_DISTANCE + 1; + var index = -1; + // we want lev results to go lower than others + lev = MAX_LEV_DISTANCE + 1; + fullId = ty.id; + + if (searchWords[j].indexOf(split[i]) > -1 || + searchWords[j].indexOf(val) > -1 || + ty.normalizedName.indexOf(val) > -1) + { + // filter type: ... queries + if (typePassesFilter(typeFilter, ty.ty) && results[fullId] === undefined) { + index = ty.normalizedName.indexOf(val); + } + } + if ((lev = levenshtein(searchWords[j], val)) <= MAX_LEV_DISTANCE) { + if (typePassesFilter(typeFilter, ty.ty) === false) { + lev = MAX_LEV_DISTANCE + 1; + } else { + lev += 1; + } + } + in_args = findArg(ty, valGenerics, false, typeFilter); + returned = checkReturned(ty, valGenerics, false, typeFilter); + + lev += lev_add; + if (lev > 0 && val.length > 3 && searchWords[j].indexOf(val) > -1) { + if (val.length < 6) { + lev -= 1; + } else { + lev = 0; + } + } + if (in_args <= MAX_LEV_DISTANCE) { + if (results_in_args[fullId] === undefined) { + results_in_args[fullId] = { + id: j, + index: index, + lev: in_args, + }; + } + results_in_args[fullId].lev = + Math.min(results_in_args[fullId].lev, in_args); + } + if (returned <= MAX_LEV_DISTANCE) { + if (results_returned[fullId] === undefined) { + results_returned[fullId] = { + id: j, + index: index, + lev: returned, + }; + } + results_returned[fullId].lev = + Math.min(results_returned[fullId].lev, returned); + } + if (index !== -1 || lev <= MAX_LEV_DISTANCE) { + if (index !== -1 && paths.length < 2) { + lev = 0; + } + if (results[fullId] === undefined) { + results[fullId] = { + id: j, + index: index, + lev: lev, + }; + } + results[fullId].lev = Math.min(results[fullId].lev, lev); + } + } + } + + var ret = { + "in_args": sortResults(results_in_args, true), + "returned": sortResults(results_returned, true), + "others": sortResults(results), + }; + handleAliases(ret, query, filterCrates); + return ret; + } + + /** + * Validate performs the following boolean logic. For example: + * "File::open" will give IF A PARENT EXISTS => ("file" && "open") + * exists in (name || path || parent) OR => ("file" && "open") exists in + * (name || path ) + * + * This could be written functionally, but I wanted to minimise + * functions on stack. + * + * @param {[string]} name [The name of the result] + * @param {[string]} path [The path of the result] + * @param {[string]} keys [The keys to be used (["file", "open"])] + * @param {[object]} parent [The parent of the result] + * @return {[boolean]} [Whether the result is valid or not] + */ + function validateResult(name, path, keys, parent) { + for (var i = 0, len = keys.length; i < len; ++i) { + // each check is for validation so we negate the conditions and invalidate + if (!( + // check for an exact name match + name.indexOf(keys[i]) > -1 || + // then an exact path match + path.indexOf(keys[i]) > -1 || + // next if there is a parent, check for exact parent match + (parent !== undefined && parent.name !== undefined && + parent.name.toLowerCase().indexOf(keys[i]) > -1) || + // lastly check to see if the name was a levenshtein match + levenshtein(name, keys[i]) <= MAX_LEV_DISTANCE)) { + return false; + } + } + return true; + } + + function getQuery(raw) { + var matches, type, query; + query = raw; + + matches = query.match(/^(fn|mod|struct|enum|trait|type|const|macro)\s*:\s*/i); + if (matches) { + type = matches[1].replace(/^const$/, "constant"); + query = query.substring(matches[0].length); + } + + return { + raw: raw, + query: query, + type: type, + id: query + type + }; + } + + function initSearchNav() { + var hoverTimeout; + + var click_func = function(e) { + var el = e.target; + // to retrieve the real "owner" of the event. + while (el.tagName !== "TR") { + el = el.parentNode; + } + var dst = e.target.getElementsByTagName("a"); + if (dst.length < 1) { + return; + } + dst = dst[0]; + if (window.location.pathname === dst.pathname) { + hideSearchResults(); + document.location.href = dst.href; + } + }; + var mouseover_func = function(e) { + if (mouseMovedAfterSearch) { + var el = e.target; + // to retrieve the real "owner" of the event. + while (el.tagName !== "TR") { + el = el.parentNode; + } + clearTimeout(hoverTimeout); + hoverTimeout = setTimeout(function() { + onEachLazy(document.getElementsByClassName("search-results"), function(e) { + onEachLazy(e.getElementsByClassName("result"), function(i_e) { + removeClass(i_e, "highlighted"); + }); + }); + addClass(el, "highlighted"); + }, 20); + } + }; + onEachLazy(document.getElementsByClassName("search-results"), function(e) { + onEachLazy(e.getElementsByClassName("result"), function(i_e) { + i_e.onclick = click_func; + i_e.onmouseover = mouseover_func; + }); + }); + + search_input.onkeydown = function(e) { + // "actives" references the currently highlighted item in each search tab. + // Each array in "actives" represents a tab. + var actives = [[], [], []]; + // "current" is used to know which tab we're looking into. + var current = 0; + onEachLazy(document.getElementById("results").childNodes, function(e) { + onEachLazy(e.getElementsByClassName("highlighted"), function(h_e) { + actives[current].push(h_e); + }); + current += 1; + }); + + if (e.which === 38) { // up + if (e.ctrlKey) { // Going through result tabs. + printTab(currentTab > 0 ? currentTab - 1 : 2); + } else { + if (!actives[currentTab].length || + !actives[currentTab][0].previousElementSibling) { + return; + } + addClass(actives[currentTab][0].previousElementSibling, "highlighted"); + removeClass(actives[currentTab][0], "highlighted"); + } + e.preventDefault(); + } else if (e.which === 40) { // down + if (e.ctrlKey) { // Going through result tabs. + printTab(currentTab > 1 ? 0 : currentTab + 1); + } else if (!actives[currentTab].length) { + var results = document.getElementById("results").childNodes; + if (results.length > 0) { + var res = results[currentTab].getElementsByClassName("result"); + if (res.length > 0) { + addClass(res[0], "highlighted"); + } + } + } else if (actives[currentTab][0].nextElementSibling) { + addClass(actives[currentTab][0].nextElementSibling, "highlighted"); + removeClass(actives[currentTab][0], "highlighted"); + } + e.preventDefault(); + } else if (e.which === 13) { // return + if (actives[currentTab].length) { + document.location.href = + actives[currentTab][0].getElementsByTagName("a")[0].href; + } + } else if (e.which === 16) { // shift + // Does nothing, it's just to avoid losing "focus" on the highlighted element. + } else if (actives[currentTab].length > 0) { + removeClass(actives[currentTab][0], "highlighted"); + } + }; + } + + function buildHrefAndPath(item) { + var displayPath; + var href; + var type = itemTypes[item.ty]; + var name = item.name; + var path = item.path; + + if (type === "mod") { + displayPath = path + "::"; + href = window.rootPath + path.replace(/::/g, "/") + "/" + + name + "/index.html"; + } else if (type === "primitive" || type === "keyword") { + displayPath = ""; + href = window.rootPath + path.replace(/::/g, "/") + + "/" + type + "." + name + ".html"; + } else if (type === "externcrate") { + displayPath = ""; + href = window.rootPath + name + "/index.html"; + } else if (item.parent !== undefined) { + var myparent = item.parent; + var anchor = "#" + type + "." + name; + var parentType = itemTypes[myparent.ty]; + var pageType = parentType; + var pageName = myparent.name; + + if (parentType === "primitive") { + displayPath = myparent.name + "::"; + } else if (type === "structfield" && parentType === "variant") { + // Structfields belonging to variants are special: the + // final path element is the enum name. + var enumNameIdx = item.path.lastIndexOf("::"); + var enumName = item.path.substr(enumNameIdx + 2); + path = item.path.substr(0, enumNameIdx); + displayPath = path + "::" + enumName + "::" + myparent.name + "::"; + anchor = "#variant." + myparent.name + ".field." + name; + pageType = "enum"; + pageName = enumName; + } else { + displayPath = path + "::" + myparent.name + "::"; + } + href = window.rootPath + path.replace(/::/g, "/") + + "/" + pageType + + "." + pageName + + ".html" + anchor; + } else { + displayPath = item.path + "::"; + href = window.rootPath + item.path.replace(/::/g, "/") + + "/" + type + "." + name + ".html"; + } + return [displayPath, href]; + } + + function escape(content) { + var h1 = document.createElement("h1"); + h1.textContent = content; + return h1.innerHTML; + } + + function pathSplitter(path) { + var tmp = "<span>" + path.replace(/::/g, "::</span><span>"); + if (tmp.endsWith("<span>")) { + return tmp.slice(0, tmp.length - 6); + } + return tmp; + } + + function addTab(array, query, display) { + var extraStyle = ""; + if (display === false) { + extraStyle = " style=\"display: none;\""; + } + + var output = ""; + var duplicates = {}; + var length = 0; + if (array.length > 0) { + output = "<table class=\"search-results\"" + extraStyle + ">"; + + array.forEach(function(item) { + var name, type; + + name = item.name; + type = itemTypes[item.ty]; + + if (item.is_alias !== true) { + if (duplicates[item.fullPath]) { + return; + } + duplicates[item.fullPath] = true; + } + length += 1; + + output += "<tr class=\"" + type + " result\"><td>" + + "<a href=\"" + item.href + "\">" + + (item.is_alias === true ? + ("<span class=\"alias\"><b>" + item.alias + " </b></span><span " + + "class=\"grey\"><i> - see </i></span>") : "") + + item.displayPath + "<span class=\"" + type + "\">" + + name + "</span></a></td><td>" + + "<a href=\"" + item.href + "\">" + + "<span class=\"desc\">" + item.desc + + " </span></a></td></tr>"; + }); + output += "</table>"; + } else { + output = "<div class=\"search-failed\"" + extraStyle + ">No results :(<br/>" + + "Try on <a href=\"https://duckduckgo.com/?q=" + + encodeURIComponent("rust " + query.query) + + "\">DuckDuckGo</a>?<br/><br/>" + + "Or try looking in one of these:<ul><li>The <a " + + "href=\"https://doc.rust-lang.org/reference/index.html\">Rust Reference</a> " + + " for technical details about the language.</li><li><a " + + "href=\"https://doc.rust-lang.org/rust-by-example/index.html\">Rust By " + + "Example</a> for expository code examples.</a></li><li>The <a " + + "href=\"https://doc.rust-lang.org/book/index.html\">Rust Book</a> for " + + "introductions to language features and the language itself.</li><li><a " + + "href=\"https://docs.rs\">Docs.rs</a> for documentation of crates released on" + + " <a href=\"https://crates.io/\">crates.io</a>.</li></ul></div>"; + } + return [output, length]; + } + + function makeTabHeader(tabNb, text, nbElems) { + if (currentTab === tabNb) { + return "<button class=\"selected\">" + text + + " <div class=\"count\">(" + nbElems + ")</div></button>"; + } + return "<button>" + text + " <div class=\"count\">(" + nbElems + ")</div></button>"; + } + + function showResults(results) { + var search = getSearchElement(); + if (results.others.length === 1 + && getSettingValue("go-to-only-result") === "true" + // By default, the search DOM element is "empty" (meaning it has no children not + // text content). Once a search has been run, it won't be empty, even if you press + // ESC or empty the search input (which also "cancels" the search). + && (!search.firstChild || search.firstChild.innerText !== getSearchLoadingText())) + { + var elem = document.createElement("a"); + elem.href = results.others[0].href; + elem.style.display = "none"; + // For firefox, we need the element to be in the DOM so it can be clicked. + document.body.appendChild(elem); + elem.click(); + return; + } + var query = getQuery(search_input.value); + + currentResults = query.id; + + var ret_others = addTab(results.others, query); + var ret_in_args = addTab(results.in_args, query, false); + var ret_returned = addTab(results.returned, query, false); + + // Navigate to the relevant tab if the current tab is empty, like in case users search + // for "-> String". If they had selected another tab previously, they have to click on + // it again. + if ((currentTab === 0 && ret_others[1] === 0) || + (currentTab === 1 && ret_in_args[1] === 0) || + (currentTab === 2 && ret_returned[1] === 0)) { + if (ret_others[1] !== 0) { + currentTab = 0; + } else if (ret_in_args[1] !== 0) { + currentTab = 1; + } else if (ret_returned[1] !== 0) { + currentTab = 2; + } + } + + var output = "<h1>Results for " + escape(query.query) + + (query.type ? " (type: " + escape(query.type) + ")" : "") + "</h1>" + + "<div id=\"titles\">" + + makeTabHeader(0, "In Names", ret_others[1]) + + makeTabHeader(1, "In Parameters", ret_in_args[1]) + + makeTabHeader(2, "In Return Types", ret_returned[1]) + + "</div><div id=\"results\">" + + ret_others[0] + ret_in_args[0] + ret_returned[0] + "</div>"; + + search.innerHTML = output; + showSearchResults(search); + initSearchNav(); + var elems = document.getElementById("titles").childNodes; + elems[0].onclick = function() { printTab(0); }; + elems[1].onclick = function() { printTab(1); }; + elems[2].onclick = function() { printTab(2); }; + printTab(currentTab); + } + + function execSearch(query, searchWords, filterCrates) { + function getSmallest(arrays, positions, notDuplicates) { + var start = null; + + for (var it = 0, len = positions.length; it < len; ++it) { + if (arrays[it].length > positions[it] && + (start === null || start > arrays[it][positions[it]].lev) && + !notDuplicates[arrays[it][positions[it]].fullPath]) { + start = arrays[it][positions[it]].lev; + } + } + return start; + } + + function mergeArrays(arrays) { + var ret = []; + var positions = []; + var notDuplicates = {}; + + for (var x = 0, arrays_len = arrays.length; x < arrays_len; ++x) { + positions.push(0); + } + while (ret.length < MAX_RESULTS) { + var smallest = getSmallest(arrays, positions, notDuplicates); + + if (smallest === null) { + break; + } + for (x = 0; x < arrays_len && ret.length < MAX_RESULTS; ++x) { + if (arrays[x].length > positions[x] && + arrays[x][positions[x]].lev === smallest && + !notDuplicates[arrays[x][positions[x]].fullPath]) { + ret.push(arrays[x][positions[x]]); + notDuplicates[arrays[x][positions[x]].fullPath] = true; + positions[x] += 1; + } + } + } + return ret; + } + + var queries = query.raw.split(","); + var results = { + "in_args": [], + "returned": [], + "others": [], + }; + + for (var i = 0, len = queries.length; i < len; ++i) { + query = queries[i].trim(); + if (query.length !== 0) { + var tmp = execQuery(getQuery(query), searchWords, filterCrates); + + results.in_args.push(tmp.in_args); + results.returned.push(tmp.returned); + results.others.push(tmp.others); + } + } + if (queries.length > 1) { + return { + "in_args": mergeArrays(results.in_args), + "returned": mergeArrays(results.returned), + "others": mergeArrays(results.others), + }; + } + return { + "in_args": results.in_args[0], + "returned": results.returned[0], + "others": results.others[0], + }; + } + + function getFilterCrates() { + var elem = document.getElementById("crate-search"); + + if (elem && elem.value !== "All crates" && hasOwnProperty(rawSearchIndex, elem.value)) { + return elem.value; + } + return undefined; + } + + function search(e, forced) { + var params = getQueryStringParams(); + var query = getQuery(search_input.value.trim()); + + if (e) { + e.preventDefault(); + } + + if (query.query.length === 0) { + return; + } + if (forced !== true && query.id === currentResults) { + if (query.query.length > 0) { + putBackSearch(search_input); + } + return; + } + + // Update document title to maintain a meaningful browser history + searchTitle = "Results for " + query.query + " - Rust"; + + // Because searching is incremental by character, only the most + // recent search query is added to the browser history. + if (browserSupportsHistoryApi()) { + var newURL = getNakedUrl() + "?search=" + encodeURIComponent(query.raw) + + window.location.hash; + if (!history.state && !params.search) { + history.pushState(query, "", newURL); + } else { + history.replaceState(query, "", newURL); + } + } + + var filterCrates = getFilterCrates(); + showResults(execSearch(query, index, filterCrates)); + } + + function buildIndex(rawSearchIndex) { + searchIndex = []; + var searchWords = []; + var i, word; + var currentIndex = 0; + var id = 0; + + for (var crate in rawSearchIndex) { + if (!hasOwnProperty(rawSearchIndex, crate)) { continue; } + + var crateSize = 0; + + searchWords.push(crate); + var normalizedName = crate.indexOf("_") === -1 + ? crate + : crate.replace(/_/g, ""); + // This object should have exactly the same set of fields as the "row" + // object defined below. Your JavaScript runtime will thank you. + // https://mathiasbynens.be/notes/shapes-ics + var crateRow = { + crate: crate, + ty: 1, // == ExternCrate + name: crate, + path: "", + desc: rawSearchIndex[crate].doc, + parent: undefined, + type: null, + id: id, + normalizedName: normalizedName, + }; + id += 1; + searchIndex.push(crateRow); + currentIndex += 1; + + // an array of (Number) item types + var itemTypes = rawSearchIndex[crate].t; + // an array of (String) item names + var itemNames = rawSearchIndex[crate].n; + // an array of (String) full paths (or empty string for previous path) + var itemPaths = rawSearchIndex[crate].q; + // an array of (String) descriptions + var itemDescs = rawSearchIndex[crate].d; + // an array of (Number) the parent path index + 1 to `paths`, or 0 if none + var itemParentIdxs = rawSearchIndex[crate].i; + // an array of (Object | null) the type of the function, if any + var itemFunctionSearchTypes = rawSearchIndex[crate].f; + // an array of [(Number) item type, + // (String) name] + var paths = rawSearchIndex[crate].p; + // a array of [(String) alias name + // [Number] index to items] + var aliases = rawSearchIndex[crate].a; + + // convert `rawPaths` entries into object form + var len = paths.length; + for (i = 0; i < len; ++i) { + paths[i] = {ty: paths[i][0], name: paths[i][1]}; + } + + // convert `item*` into an object form, and construct word indices. + // + // before any analysis is performed lets gather the search terms to + // search against apart from the rest of the data. This is a quick + // operation that is cached for the life of the page state so that + // all other search operations have access to this cached data for + // faster analysis operations + len = itemTypes.length; + var lastPath = ""; + for (i = 0; i < len; ++i) { + // This object should have exactly the same set of fields as the "crateRow" + // object defined above. + if (typeof itemNames[i] === "string") { + word = itemNames[i].toLowerCase(); + searchWords.push(word); + } else { + word = ""; + searchWords.push(""); + } + var normalizedName = word.indexOf("_") === -1 + ? word + : word.replace(/_/g, ""); + var row = { + crate: crate, + ty: itemTypes[i], + name: itemNames[i], + path: itemPaths[i] ? itemPaths[i] : lastPath, + desc: itemDescs[i], + parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined, + type: itemFunctionSearchTypes[i], + id: id, + normalizedName: normalizedName, + }; + id += 1; + searchIndex.push(row); + lastPath = row.path; + crateSize += 1; + } + + if (aliases) { + ALIASES[crate] = {}; + var j, local_aliases; + for (var alias_name in aliases) { + if (!aliases.hasOwnProperty(alias_name)) { continue; } + + if (!ALIASES[crate].hasOwnProperty(alias_name)) { + ALIASES[crate][alias_name] = []; + } + local_aliases = aliases[alias_name]; + for (j = 0, len = local_aliases.length; j < len; ++j) { + ALIASES[crate][alias_name].push(local_aliases[j] + currentIndex); + } + } + } + currentIndex += crateSize; + } + return searchWords; + } + + function registerSearchEvents() { + var searchAfter500ms = function() { + clearInputTimeout(); + if (search_input.value.length === 0) { + if (browserSupportsHistoryApi()) { + history.replaceState("", window.currentCrate + " - Rust", + getNakedUrl() + window.location.hash); + } + hideSearchResults(); + } else { + searchTimeout = setTimeout(search, 500); + } + }; + search_input.onkeyup = searchAfter500ms; + search_input.oninput = searchAfter500ms; + document.getElementsByClassName("search-form")[0].onsubmit = function(e) { + e.preventDefault(); + clearInputTimeout(); + search(); + }; + search_input.onchange = function(e) { + if (e.target !== document.activeElement) { + // To prevent doing anything when it's from a blur event. + return; + } + // Do NOT e.preventDefault() here. It will prevent pasting. + clearInputTimeout(); + // zero-timeout necessary here because at the time of event handler execution the + // pasted content is not in the input field yet. Shouldn’t make any difference for + // change, though. + setTimeout(search, 0); + }; + search_input.onpaste = search_input.onchange; + + var selectCrate = document.getElementById("crate-search"); + if (selectCrate) { + selectCrate.onchange = function() { + updateLocalStorage("rustdoc-saved-filter-crate", selectCrate.value); + search(undefined, true); + }; + } + + // Push and pop states are used to add search results to the browser + // history. + if (browserSupportsHistoryApi()) { + // Store the previous <title> so we can revert back to it later. + var previousTitle = document.title; + + window.addEventListener("popstate", function(e) { + var params = getQueryStringParams(); + // Revert to the previous title manually since the History + // API ignores the title parameter. + document.title = previousTitle; + // When browsing forward to search results the previous + // search will be repeated, so the currentResults are + // cleared to ensure the search is successful. + currentResults = null; + // Synchronize search bar with query string state and + // perform the search. This will empty the bar if there's + // nothing there, which lets you really go back to a + // previous state with nothing in the bar. + if (params.search && params.search.length > 0) { + search_input.value = params.search; + // Some browsers fire "onpopstate" for every page load + // (Chrome), while others fire the event only when actually + // popping a state (Firefox), which is why search() is + // called both here and at the end of the startSearch() + // function. + search(e); + } else { + search_input.value = ""; + // When browsing back from search results the main page + // visibility must be reset. + hideSearchResults(); + } + }); + } + + // This is required in firefox to avoid this problem: Navigating to a search result + // with the keyboard, hitting enter, and then hitting back would take you back to + // the doc page, rather than the search that should overlay it. + // This was an interaction between the back-forward cache and our handlers + // that try to sync state between the URL and the search input. To work around it, + // do a small amount of re-init on page show. + window.onpageshow = function(){ + var qSearch = getQueryStringParams().search; + if (search_input.value === "" && qSearch) { + search_input.value = qSearch; + } + search(); + }; + } + + index = buildIndex(rawSearchIndex); + registerSearchEvents(); + // If there's a search term in the URL, execute the search now. + if (getQueryStringParams().search) { + search(); + } +}; + + +initSearch(searchIndex); +})(); diff --git a/src/librustdoc/html/static_files.rs b/src/librustdoc/html/static_files.rs index b3ac865d55ea1..a9c23a24c1481 100644 --- a/src/librustdoc/html/static_files.rs +++ b/src/librustdoc/html/static_files.rs @@ -24,6 +24,10 @@ crate static NORMALIZE_CSS: &str = include_str!("static/normalize.css"); /// including search behavior and docblock folding, among others. crate static MAIN_JS: &str = include_str!("static/main.js"); +/// The file contents of `search-index.js`, which contains the search behavior, +/// and into which is interpolated the search index. +crate static SEARCH_JS: &str = include_str!("static/search.js"); + /// The file contents of `settings.js`, which contains the JavaScript used to handle the settings /// page. crate static SETTINGS_JS: &str = include_str!("static/settings.js"); From 95d652ebce010d3b090af81508e9cf3d7096d1e4 Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews <github@hoffman-andrews.com> Date: Mon, 12 Apr 2021 23:50:18 -0700 Subject: [PATCH 02/13] Consolidate search-related vars and functions. This allows sharing across main.js and search.js without exporting too many symbols into the global namespace. --- src/librustdoc/html/static/main.js | 266 +++++++++++++-------------- src/librustdoc/html/static/search.js | 76 ++++---- 2 files changed, 167 insertions(+), 175 deletions(-) diff --git a/src/librustdoc/html/static/main.js b/src/librustdoc/html/static/main.js index 7d7825f0f65d9..cb7408502eba3 100644 --- a/src/librustdoc/html/static/main.js +++ b/src/librustdoc/html/static/main.js @@ -80,10 +80,6 @@ function getSearchInput() { return document.getElementsByClassName("search-input")[0]; } -function getSearchElement() { - return document.getElementById("search"); -} - var THEME_PICKER_ELEMENT_ID = "theme-picker"; var THEMES_ELEMENT_ID = "theme-choices"; @@ -172,30 +168,123 @@ function hideThemeButtonState() { (function() { "use strict"; - var disableShortcuts = getSettingValue("disable-shortcuts") === "true"; - window.search_input = getSearchInput(); - var searchTimeout = null; - var toggleAllDocsId = "toggle-all-docs"; + window.searchState = { + loadingText: "Loading search results...", + input: getSearchInput(), + outputElement: function() { + return document.getElementById("search"); + }, + title: null, + titleBeforeSearch: document.title, + timeout: null, + // On the search screen, so you remain on the last tab you opened. + // + // 0 for "In Names" + // 1 for "In Parameters" + // 2 for "In Return Types" + currentTab: 0, + mouseMovedAfterSearch: true, + clearInputTimeout: function() { + if (searchState.timeout !== null) { + clearTimeout(searchState.timeout); + searchState.timeout = null; + } + }, + showResults: function(search) { + if (search === null || typeof search === 'undefined') { + search = searchState.outputElement(); + } + addClass(main, "hidden"); + removeClass(search, "hidden"); + searchState.mouseMovedAfterSearch = false; + document.title = searchState.title; + }, + hideResults: function(search) { + if (search === null || typeof search === 'undefined') { + search = searchState.outputElement(); + } + addClass(search, "hidden"); + removeClass(main, "hidden"); + document.title = searchState.titleBeforeSearch; + // We also remove the query parameter from the URL. + if (searchState.browserSupportsHistoryApi()) { + history.replaceState("", window.currentCrate + " - Rust", + getNakedUrl() + window.location.hash); + } + }, + getQueryStringParams: function() { + var params = {}; + window.location.search.substring(1).split("&"). + map(function(s) { + var pair = s.split("="); + params[decodeURIComponent(pair[0])] = + typeof pair[1] === "undefined" ? null : decodeURIComponent(pair[1]); + }); + return params; + }, + putBackSearch: function(search_input) { + var search = searchState.outputElement(); + if (search_input.value !== "" && hasClass(search, "hidden")) { + searchState.showResults(search); + if (searchState.browserSupportsHistoryApi()) { + var extra = "?search=" + encodeURIComponent(search_input.value); + history.replaceState(search_input.value, "", + getNakedUrl() + extra + window.location.hash); + } + document.title = searchState.title; + } + }, + browserSupportsHistoryApi: function() { + return window.history && typeof window.history.pushState === "function"; + }, + setupLoader: function() { + function loadScript(url) { + var script = document.createElement('script'); + script.src = url; + document.head.append(script); + } - // On the search screen, so you remain on the last tab you opened. - // - // 0 for "In Names" - // 1 for "In Parameters" - // 2 for "In Return Types" - window.currentTab = 0; + var searchLoaded = false; + function loadSearch() { + if (!searchLoaded) { + searchLoaded = true; + loadScript(window.searchJS); + } + } - window.mouseMovedAfterSearch = true; + // `crates{version}.js` should always be loaded before this script, so we can use it safely. + addSearchOptions(window.ALL_CRATES); + addSidebarCrates(window.ALL_CRATES); - var titleBeforeSearch = document.title; - window.searchTitle = null; + searchState.input.addEventListener("focus", function() { + searchState.input.origPlaceholder = searchState.input.placeholder; + searchState.input.placeholder = "Type your search here."; + loadSearch(); + }); + searchState.input.addEventListener("blur", function() { + searchState.input.placeholder = searchState.input.origPlaceholder; + }); + searchState.input.removeAttribute('disabled'); - window.clearInputTimeout = function() { - if (searchTimeout !== null) { - clearTimeout(searchTimeout); - searchTimeout = null; + var crateSearchDropDown = document.getElementById("crate-search"); + // `crateSearchDropDown` can be null in case there is only crate because in that case, the + // crate filter dropdown is removed. + if (crateSearchDropDown) { + crateSearchDropDown.addEventListener("focus", loadSearch); + } + var params = searchState.getQueryStringParams(); + if (params.search !== undefined) { + loadSearch(); } + }, }; + if (searchState.input) { + searchState.input.onfocus = function() { + searchState.putBackSearch(this); + }; + } + function getPageId() { if (window.location.hash) { var tmp = window.location.hash.replace(/^#/, ""); @@ -237,61 +326,23 @@ function hideThemeButtonState() { document.getElementsByTagName("body")[0].style.marginTop = ""; } - window.showSearchResults = function(search) { - if (search === null || typeof search === 'undefined') { - search = getSearchElement(); - } - addClass(main, "hidden"); - removeClass(search, "hidden"); - mouseMovedAfterSearch = false; - document.title = searchTitle; - }; - - window.hideSearchResults = function(search) { - if (search === null || typeof search === 'undefined') { - search = getSearchElement(); - } - addClass(search, "hidden"); - removeClass(main, "hidden"); - document.title = titleBeforeSearch; - // We also remove the query parameter from the URL. - if (browserSupportsHistoryApi()) { - history.replaceState("", window.currentCrate + " - Rust", - getNakedUrl() + window.location.hash); - } - }; - - window.getQueryStringParams = function() { - var params = {}; - window.location.search.substring(1).split("&"). - map(function(s) { - var pair = s.split("="); - params[decodeURIComponent(pair[0])] = - typeof pair[1] === "undefined" ? null : decodeURIComponent(pair[1]); - }); - return params; - }; - - window.browserSupportsHistoryApi = function() { - return window.history && typeof window.history.pushState === "function"; - }; - function isHidden(elem) { return elem.offsetHeight === 0; } + var toggleAllDocsId = "toggle-all-docs"; var main = document.getElementById("main"); var savedHash = ""; function handleHashes(ev) { var elem; - var search = getSearchElement(); + var search = searchState.outputElement(); if (ev !== null && search && !hasClass(search, "hidden") && ev.newURL) { // This block occurs when clicking on an element in the navbar while // in a search. - hideSearchResults(search); + searchState.hideResults(search); var hash = ev.newURL.slice(ev.newURL.indexOf("#") + 1); - if (browserSupportsHistoryApi()) { + if (searchState.browserSupportsHistoryApi()) { // `window.location.search`` contains all the query parameters, not just `search`. history.replaceState(hash, "", getNakedUrl() + window.location.search + "#" + hash); @@ -432,18 +483,19 @@ function hideThemeButtonState() { function handleEscape(ev) { var help = getHelpElement(false); - var search = getSearchElement(); + var search = searchState.outputElement(); if (hasClass(help, "hidden") === false) { displayHelp(false, ev, help); } else if (hasClass(search, "hidden") === false) { - clearInputTimeout(); + searchState.clearInputTimeout(); ev.preventDefault(); - hideSearchResults(search); + searchState.hideResults(search); } defocusSearchBar(); hideThemeButtonState(); } + var disableShortcuts = getSettingValue("disable-shortcuts") === "true"; function handleShortcut(ev) { // Don't interfere with browser shortcuts if (ev.ctrlKey || ev.altKey || ev.metaKey || disableShortcuts === true) { @@ -553,7 +605,9 @@ function hideThemeButtonState() { document.addEventListener("keypress", handleShortcut); document.addEventListener("keydown", handleShortcut); - document.addEventListener("mousemove", function() { mouseMovedAfterSearch = true; }); + document.addEventListener("mousemove", function() { + searchState.mouseMovedAfterSearch = true; + }); var handleSourceHighlight = (function() { var prev_line_id = 0; @@ -561,7 +615,7 @@ function hideThemeButtonState() { var set_fragment = function(name) { var x = window.scrollX, y = window.scrollY; - if (browserSupportsHistoryApi()) { + if (searchState.browserSupportsHistoryApi()) { history.replaceState(null, null, "#" + name); highlightSourceLines(); } else { @@ -1366,34 +1420,11 @@ function hideThemeButtonState() { }; }); - window.putBackSearch = function(search_input) { - var search = getSearchElement(); - if (search_input.value !== "" && hasClass(search, "hidden")) { - showSearchResults(search); - if (browserSupportsHistoryApi()) { - var extra = "?search=" + encodeURIComponent(search_input.value); - history.replaceState(search_input.value, "", - getNakedUrl() + extra + window.location.hash); - } - document.title = searchTitle; - } - }; - - function getSearchLoadingText() { - return "Loading search results..."; - } - - if (search_input) { - search_input.onfocus = function() { - putBackSearch(this); - }; - } - - var params = getQueryStringParams(); + var params = searchState.getQueryStringParams(); if (params && params.search) { - var search = getSearchElement(); - search.innerHTML = "<h3 style=\"text-align: center;\">" + getSearchLoadingText() + "</h3>"; - showSearchResults(search); + var search = searchState.outputElement(); + search.innerHTML = "<h3 style=\"text-align: center;\">" + searchState.loadingText + "</h3>"; + searchState.showResults(search); } var sidebar_menu = document.getElementsByClassName("sidebar-menu")[0]; @@ -1509,55 +1540,14 @@ function hideThemeButtonState() { container.appendChild(div_infos); popup.appendChild(container); - insertAfter(popup, getSearchElement()); + insertAfter(popup, searchState.outputElement()); // So that it's only built once and then it'll do nothing when called! buildHelperPopup = function() {}; } - function loadScript(url) { - var script = document.createElement('script'); - script.src = url; - document.head.append(script); - } - - function setupSearchLoader() { - var searchLoaded = false; - function loadSearch() { - if (!searchLoaded) { - searchLoaded = true; - loadScript(window.searchJS); - } - } - - // `crates{version}.js` should always be loaded before this script, so we can use it safely. - addSearchOptions(window.ALL_CRATES); - addSidebarCrates(window.ALL_CRATES); - - search_input.addEventListener("focus", function() { - search_input.origPlaceholder = search_input.placeholder; - search_input.placeholder = "Type your search here."; - loadSearch(); - }); - search_input.addEventListener("blur", function() { - search_input.placeholder = search_input.origPlaceholder; - }); - search_input.removeAttribute('disabled'); - - var crateSearchDropDown = document.getElementById("crate-search"); - // `crateSearchDropDown` can be null in case there is only crate because in that case, the - // crate filter dropdown is removed. - if (crateSearchDropDown) { - crateSearchDropDown.addEventListener("focus", loadSearch); - } - var params = getQueryStringParams(); - if (params.search !== undefined) { - loadSearch(); - } - } - onHashChange(null); window.onhashchange = onHashChange; - setupSearchLoader(); + searchState.setupLoader(); }()); function copy_path(but) { diff --git a/src/librustdoc/html/static/search.js b/src/librustdoc/html/static/search.js index c06de66d7f70d..b8afbd175c519 100644 --- a/src/librustdoc/html/static/search.js +++ b/src/librustdoc/html/static/search.js @@ -39,7 +39,7 @@ var TY_KEYWORD = itemTypes.indexOf("keyword"); // In the search display, allows to switch between tabs. function printTab(nb) { if (nb === 0 || nb === 1 || nb === 2) { - currentTab = nb; + searchState.currentTab = nb; } var nb_copy = nb; onEachLazy(document.getElementById("titles").childNodes, function(elem) { @@ -114,14 +114,14 @@ function initSearch(rawSearchIndex) { var NO_TYPE_FILTER = -1; var currentResults, index, searchIndex; var ALIASES = {}; - var params = getQueryStringParams(); + var params = searchState.getQueryStringParams(); // Populate search bar with query string search term when provided, // but only if the input bar is empty. This avoid the obnoxious issue // where you start trying to do a search, and the index loads, and // suddenly your search is gone! - if (search_input.value === "") { - search_input.value = params.search || ""; + if (searchState.input.value === "") { + searchState.input.value = params.search || ""; } /** @@ -898,12 +898,12 @@ function initSearch(rawSearchIndex) { } dst = dst[0]; if (window.location.pathname === dst.pathname) { - hideSearchResults(); + searchState.hideResults(); document.location.href = dst.href; } }; var mouseover_func = function(e) { - if (mouseMovedAfterSearch) { + if (searchState.mouseMovedAfterSearch) { var el = e.target; // to retrieve the real "owner" of the event. while (el.tagName !== "TR") { @@ -927,7 +927,7 @@ function initSearch(rawSearchIndex) { }); }); - search_input.onkeydown = function(e) { + searchState.input.onkeydown = function(e) { // "actives" references the currently highlighted item in each search tab. // Each array in "actives" represents a tab. var actives = [[], [], []]; @@ -940,6 +940,7 @@ function initSearch(rawSearchIndex) { current += 1; }); + var currentTab = searchState.currentTab; if (e.which === 38) { // up if (e.ctrlKey) { // Going through result tabs. printTab(currentTab > 0 ? currentTab - 1 : 2); @@ -1104,7 +1105,7 @@ function initSearch(rawSearchIndex) { } function makeTabHeader(tabNb, text, nbElems) { - if (currentTab === tabNb) { + if (searchState.currentTab === tabNb) { return "<button class=\"selected\">" + text + " <div class=\"count\">(" + nbElems + ")</div></button>"; } @@ -1112,13 +1113,13 @@ function initSearch(rawSearchIndex) { } function showResults(results) { - var search = getSearchElement(); + var search = searchState.outputElement(); if (results.others.length === 1 && getSettingValue("go-to-only-result") === "true" // By default, the search DOM element is "empty" (meaning it has no children not // text content). Once a search has been run, it won't be empty, even if you press // ESC or empty the search input (which also "cancels" the search). - && (!search.firstChild || search.firstChild.innerText !== getSearchLoadingText())) + && (!search.firstChild || search.firstChild.innerText !== searchState.loadingText)) { var elem = document.createElement("a"); elem.href = results.others[0].href; @@ -1128,7 +1129,7 @@ function initSearch(rawSearchIndex) { elem.click(); return; } - var query = getQuery(search_input.value); + var query = getQuery(searchState.input.value); currentResults = query.id; @@ -1139,6 +1140,7 @@ function initSearch(rawSearchIndex) { // Navigate to the relevant tab if the current tab is empty, like in case users search // for "-> String". If they had selected another tab previously, they have to click on // it again. + var currentTab = searchState.currentTab; if ((currentTab === 0 && ret_others[1] === 0) || (currentTab === 1 && ret_in_args[1] === 0) || (currentTab === 2 && ret_returned[1] === 0)) { @@ -1161,7 +1163,7 @@ function initSearch(rawSearchIndex) { ret_others[0] + ret_in_args[0] + ret_returned[0] + "</div>"; search.innerHTML = output; - showSearchResults(search); + searchState.showResults(search); initSearchNav(); var elems = document.getElementById("titles").childNodes; elems[0].onclick = function() { printTab(0); }; @@ -1252,8 +1254,8 @@ function initSearch(rawSearchIndex) { } function search(e, forced) { - var params = getQueryStringParams(); - var query = getQuery(search_input.value.trim()); + var params = searchState.getQueryStringParams(); + var query = getQuery(searchState.input.value.trim()); if (e) { e.preventDefault(); @@ -1264,17 +1266,17 @@ function initSearch(rawSearchIndex) { } if (forced !== true && query.id === currentResults) { if (query.query.length > 0) { - putBackSearch(search_input); + searchState.putBackSearch(searchState.input); } return; } // Update document title to maintain a meaningful browser history - searchTitle = "Results for " + query.query + " - Rust"; + searchState.title = "Results for " + query.query + " - Rust"; // Because searching is incremental by character, only the most // recent search query is added to the browser history. - if (browserSupportsHistoryApi()) { + if (searchState.browserSupportsHistoryApi()) { var newURL = getNakedUrl() + "?search=" + encodeURIComponent(query.raw) + window.location.hash; if (!history.state && !params.search) { @@ -1408,37 +1410,37 @@ function initSearch(rawSearchIndex) { function registerSearchEvents() { var searchAfter500ms = function() { - clearInputTimeout(); - if (search_input.value.length === 0) { - if (browserSupportsHistoryApi()) { + searchState.clearInputTimeout(); + if (searchState.input.value.length === 0) { + if (searchState.browserSupportsHistoryApi()) { history.replaceState("", window.currentCrate + " - Rust", getNakedUrl() + window.location.hash); } - hideSearchResults(); + searchState.hideResults(); } else { - searchTimeout = setTimeout(search, 500); + searchState.timeout = setTimeout(search, 500); } }; - search_input.onkeyup = searchAfter500ms; - search_input.oninput = searchAfter500ms; + searchState.input.onkeyup = searchAfter500ms; + searchState.input.oninput = searchAfter500ms; document.getElementsByClassName("search-form")[0].onsubmit = function(e) { e.preventDefault(); - clearInputTimeout(); + searchState.clearInputTimeout(); search(); }; - search_input.onchange = function(e) { + searchState.input.onchange = function(e) { if (e.target !== document.activeElement) { // To prevent doing anything when it's from a blur event. return; } // Do NOT e.preventDefault() here. It will prevent pasting. - clearInputTimeout(); + searchState.clearInputTimeout(); // zero-timeout necessary here because at the time of event handler execution the // pasted content is not in the input field yet. Shouldn’t make any difference for // change, though. setTimeout(search, 0); }; - search_input.onpaste = search_input.onchange; + searchState.input.onpaste = searchState.input.onchange; var selectCrate = document.getElementById("crate-search"); if (selectCrate) { @@ -1450,12 +1452,12 @@ function initSearch(rawSearchIndex) { // Push and pop states are used to add search results to the browser // history. - if (browserSupportsHistoryApi()) { + if (searchState.browserSupportsHistoryApi()) { // Store the previous <title> so we can revert back to it later. var previousTitle = document.title; window.addEventListener("popstate", function(e) { - var params = getQueryStringParams(); + var params = searchState.getQueryStringParams(); // Revert to the previous title manually since the History // API ignores the title parameter. document.title = previousTitle; @@ -1468,7 +1470,7 @@ function initSearch(rawSearchIndex) { // nothing there, which lets you really go back to a // previous state with nothing in the bar. if (params.search && params.search.length > 0) { - search_input.value = params.search; + searchState.input.value = params.search; // Some browsers fire "onpopstate" for every page load // (Chrome), while others fire the event only when actually // popping a state (Firefox), which is why search() is @@ -1476,10 +1478,10 @@ function initSearch(rawSearchIndex) { // function. search(e); } else { - search_input.value = ""; + searchState.input.value = ""; // When browsing back from search results the main page // visibility must be reset. - hideSearchResults(); + searchState.hideResults(); } }); } @@ -1491,9 +1493,9 @@ function initSearch(rawSearchIndex) { // that try to sync state between the URL and the search input. To work around it, // do a small amount of re-init on page show. window.onpageshow = function(){ - var qSearch = getQueryStringParams().search; - if (search_input.value === "" && qSearch) { - search_input.value = qSearch; + var qSearch = searchState.getQueryStringParams().search; + if (searchState.input.value === "" && qSearch) { + searchState.input.value = qSearch; } search(); }; @@ -1502,7 +1504,7 @@ function initSearch(rawSearchIndex) { index = buildIndex(rawSearchIndex); registerSearchEvents(); // If there's a search term in the URL, execute the search now. - if (getQueryStringParams().search) { + if (searchState.getQueryStringParams().search) { search(); } }; From 11f854db4fd100abda22662c26871a5f890c79d1 Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews <github@hoffman-andrews.com> Date: Tue, 13 Apr 2021 00:35:36 -0700 Subject: [PATCH 03/13] Further consolidate search-related vars and funcs --- src/librustdoc/html/static/main.js | 137 +++++++++++++---------------- 1 file changed, 61 insertions(+), 76 deletions(-) diff --git a/src/librustdoc/html/static/main.js b/src/librustdoc/html/static/main.js index cb7408502eba3..80d6997751a51 100644 --- a/src/librustdoc/html/static/main.js +++ b/src/librustdoc/html/static/main.js @@ -76,10 +76,6 @@ function getVirtualKey(ev) { return String.fromCharCode(c); } -function getSearchInput() { - return document.getElementsByClassName("search-input")[0]; -} - var THEME_PICKER_ELEMENT_ID = "theme-picker"; var THEMES_ELEMENT_ID = "theme-choices"; @@ -96,16 +92,6 @@ function getNakedUrl() { return window.location.href.split("?")[0].split("#")[0]; } -// Sets the focus on the search bar at the top of the page -function focusSearchBar() { - getSearchInput().focus(); -} - -// Removes the focus from the search bar. -function defocusSearchBar() { - getSearchInput().blur(); -} - function showThemeButtonState() { var themePicker = getThemePickerElement(); var themeChoices = getThemesElement(); @@ -170,7 +156,7 @@ function hideThemeButtonState() { window.searchState = { loadingText: "Loading search results...", - input: getSearchInput(), + input: document.getElementsByClassName("search-input")[0], outputElement: function() { return document.getElementById("search"); }, @@ -190,6 +176,14 @@ function hideThemeButtonState() { searchState.timeout = null; } }, + // Sets the focus on the search bar at the top of the page + focus: function() { + searchState.input.focus(); + }, + // Removes the focus from the search bar. + defocus: function() { + searchState.input.blur(); + }, showResults: function(search) { if (search === null || typeof search === 'undefined') { search = searchState.outputElement(); @@ -237,7 +231,11 @@ function hideThemeButtonState() { browserSupportsHistoryApi: function() { return window.history && typeof window.history.pushState === "function"; }, - setupLoader: function() { + setup: function() { + var search_input = searchState.input; + if (!searchState.input) { + return; + } function loadScript(url) { var script = document.createElement('script'); script.src = url; @@ -252,38 +250,57 @@ function hideThemeButtonState() { } } - // `crates{version}.js` should always be loaded before this script, so we can use it safely. - addSearchOptions(window.ALL_CRATES); - addSidebarCrates(window.ALL_CRATES); - - searchState.input.addEventListener("focus", function() { - searchState.input.origPlaceholder = searchState.input.placeholder; - searchState.input.placeholder = "Type your search here."; + search_input.addEventListener("focus", function() { + searchState.putBackSearch(this); + search_input.origPlaceholder = searchState.input.placeholder; + search_input.placeholder = "Type your search here."; loadSearch(); }); - searchState.input.addEventListener("blur", function() { - searchState.input.placeholder = searchState.input.origPlaceholder; + search_input.addEventListener("blur", function() { + search_input.placeholder = searchState.input.origPlaceholder; }); - searchState.input.removeAttribute('disabled'); - var crateSearchDropDown = document.getElementById("crate-search"); - // `crateSearchDropDown` can be null in case there is only crate because in that case, the - // crate filter dropdown is removed. - if (crateSearchDropDown) { - crateSearchDropDown.addEventListener("focus", loadSearch); - } + document.addEventListener("mousemove", function() { + searchState.mouseMovedAfterSearch = true; + }); + + search_input.removeAttribute('disabled'); + + // `crates{version}.js` should always be loaded before this script, so we can use it safely. + searchState.addCrateDropdown(window.ALL_CRATES); var params = searchState.getQueryStringParams(); if (params.search !== undefined) { + var search = searchState.outputElement(); + search.innerHTML = "<h3 style=\"text-align: center;\">" + + searchState.loadingText + "</h3>"; + searchState.showResults(search); loadSearch(); } }, - }; + addCrateDropdown: function(crates) { + var elem = document.getElementById("crate-search"); - if (searchState.input) { - searchState.input.onfocus = function() { - searchState.putBackSearch(this); - }; - } + if (!elem) { + return; + } + var savedCrate = getSettingValue("saved-filter-crate"); + for (var i = 0, len = crates.length; i < len; ++i) { + var option = document.createElement("option"); + option.value = crates[i]; + option.innerText = crates[i]; + elem.appendChild(option); + // Set the crate filter from saved storage, if the current page has the saved crate + // filter. + // + // If not, ignore the crate filter -- we want to support filtering for crates on sites + // like doc.rust-lang.org where the crates may differ from page to page while on the + // same domain. + if (crates[i] === savedCrate) { + elem.value = savedCrate; + } + } + }, + }; function getPageId() { if (window.location.hash) { @@ -491,7 +508,7 @@ function hideThemeButtonState() { ev.preventDefault(); searchState.hideResults(search); } - defocusSearchBar(); + searchState.defocus(); hideThemeButtonState(); } @@ -518,7 +535,7 @@ function hideThemeButtonState() { case "S": displayHelp(false, ev); ev.preventDefault(); - focusSearchBar(); + searchState.focus(); break; case "+": @@ -605,10 +622,6 @@ function hideThemeButtonState() { document.addEventListener("keypress", handleShortcut); document.addEventListener("keydown", handleShortcut); - document.addEventListener("mousemove", function() { - searchState.mouseMovedAfterSearch = true; - }); - var handleSourceHighlight = (function() { var prev_line_id = 0; @@ -789,6 +802,9 @@ function hideThemeButtonState() { block("foreigntype", "Foreign Types"); block("keyword", "Keywords"); block("traitalias", "Trait Aliases"); + + // `crates{version}.js` should always be loaded before this script, so we can use it safely. + addSidebarCrates(window.ALL_CRATES); }; window.register_implementors = function(imp) { @@ -1420,13 +1436,6 @@ function hideThemeButtonState() { }; }); - var params = searchState.getQueryStringParams(); - if (params && params.search) { - var search = searchState.outputElement(); - search.innerHTML = "<h3 style=\"text-align: center;\">" + searchState.loadingText + "</h3>"; - searchState.showResults(search); - } - var sidebar_menu = document.getElementsByClassName("sidebar-menu")[0]; if (sidebar_menu) { sidebar_menu.onclick = function() { @@ -1459,30 +1468,6 @@ function hideThemeButtonState() { }); } - function addSearchOptions(crates) { - var elem = document.getElementById("crate-search"); - - if (!elem) { - return; - } - var savedCrate = getSettingValue("saved-filter-crate"); - for (var i = 0, len = crates.length; i < len; ++i) { - var option = document.createElement("option"); - option.value = crates[i]; - option.innerText = crates[i]; - elem.appendChild(option); - // Set the crate filter from saved storage, if the current page has the saved crate - // filter. - // - // If not, ignore the crate filter -- we want to support filtering for crates on sites - // like doc.rust-lang.org where the crates may differ from page to page while on the - // same domain. - if (crates[i] === savedCrate) { - elem.value = savedCrate; - } - } - }; - function buildHelperPopup() { var popup = document.createElement("aside"); addClass(popup, "hidden"); @@ -1547,7 +1532,7 @@ function hideThemeButtonState() { onHashChange(null); window.onhashchange = onHashChange; - searchState.setupLoader(); + searchState.setup(); }()); function copy_path(but) { From 52789498e93693594b387e1bc457dec5bb5097d2 Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews <github@hoffman-andrews.com> Date: Tue, 13 Apr 2021 14:59:54 -0700 Subject: [PATCH 04/13] Split search.js from search-index.js. --- src/librustdoc/html/layout.rs | 3 ++- src/librustdoc/html/render/write_shared.rs | 6 ++++-- src/librustdoc/html/static/main.js | 2 ++ src/librustdoc/html/static/search.js | 10 ++++------ 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs index 68d70f27c8c78..2ab10d5e6aa4d 100644 --- a/src/librustdoc/html/layout.rs +++ b/src/librustdoc/html/layout.rs @@ -113,7 +113,8 @@ crate fn render<T: Print, S: Print>( <section class=\"footer\"></section>\ {after_content}\ <div id=\"rustdoc-vars\" data-root-path=\"{root_path}\" data-current-crate=\"{krate}\" \ - data-search-js=\"{root_path}search-index{suffix}.js\"></div> + data-search-index-js=\"{root_path}search-index{suffix}.js\" \ + data-search-js=\"{root_path}search{suffix}.js\"></div> <script src=\"{static_root_path}main{suffix}.js\"></script>\ {extra_scripts}\ </body>\ diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 4240c986f5779..7a9d337d9638f 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -223,6 +223,7 @@ pub(super) fn write_shared( &format!(" = {}", serde_json::to_string(&themes).unwrap()), ), )?; + write_minify("search.js", static_files::SEARCH_JS)?; write_minify("settings.js", static_files::SETTINGS_JS)?; if cx.shared.include_sources { write_minify("source-script.js", static_files::sidebar::SOURCE_SCRIPT)?; @@ -408,8 +409,9 @@ pub(super) fn write_shared( // with rustdoc running in parallel. all_indexes.sort(); write_crate("search-index.js", &|| { - let v = static_files::SEARCH_JS - .replace(r#""SEARCH_INDEX_PLACEHOLDER": {}"#, &all_indexes.join(",\\\n")); + let mut v = String::from("var searchIndex = JSON.parse('{\\\n"); + v.push_str(&all_indexes.join(",\\\n")); + v.push_str("\\\n}');\nif (window.initSearch) {window.initSearch(searchIndex)};"); Ok(v.into_bytes()) })?; diff --git a/src/librustdoc/html/static/main.js b/src/librustdoc/html/static/main.js index 80d6997751a51..0162a61558702 100644 --- a/src/librustdoc/html/static/main.js +++ b/src/librustdoc/html/static/main.js @@ -43,6 +43,7 @@ if (!DOMTokenList.prototype.remove) { window.rootPath = rustdocVars.attributes["data-root-path"].value; window.currentCrate = rustdocVars.attributes["data-current-crate"].value; window.searchJS = rustdocVars.attributes["data-search-js"].value; + window.searchIndexJS = rustdocVars.attributes["data-search-index-js"].value; } var sidebarVars = document.getElementById("sidebar-vars"); if (sidebarVars) { @@ -247,6 +248,7 @@ function hideThemeButtonState() { if (!searchLoaded) { searchLoaded = true; loadScript(window.searchJS); + loadScript(window.searchIndexJS); } } diff --git a/src/librustdoc/html/static/search.js b/src/librustdoc/html/static/search.js index b8afbd175c519..538c811c71097 100644 --- a/src/librustdoc/html/static/search.js +++ b/src/librustdoc/html/static/search.js @@ -1,8 +1,4 @@ (function() { -var searchIndex = JSON.parse('{\ -"SEARCH_INDEX_PLACEHOLDER": {}\ -}'); - // This mapping table should match the discriminants of // `rustdoc::html::item_type::ItemType` type in Rust. var itemTypes = ["mod", @@ -104,7 +100,7 @@ function levenshtein(s1, s2) { return s1_len + s2_len; } -function initSearch(rawSearchIndex) { +window.initSearch = function(rawSearchIndex) { var MAX_LEV_DISTANCE = 3; var MAX_RESULTS = 200; var GENERICS_DATA = 1; @@ -1509,6 +1505,8 @@ function initSearch(rawSearchIndex) { } }; +if (window.searchIndex !== undefined) { + initSearch(window.searchIndex); +} -initSearch(searchIndex); })(); From d44c6ed35111bdabe67f0a54fd99c4671b6c55f8 Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews <github@hoffman-andrews.com> Date: Tue, 13 Apr 2021 16:43:14 -0700 Subject: [PATCH 05/13] Updated tester.js for separate search.js --- src/tools/rustdoc-js/tester.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/tools/rustdoc-js/tester.js b/src/tools/rustdoc-js/tester.js index a551a97bda55d..e583bd225a9eb 100644 --- a/src/tools/rustdoc-js/tester.js +++ b/src/tools/rustdoc-js/tester.js @@ -246,7 +246,7 @@ function lookForEntry(entry, data) { return null; } -function loadMainJsAndIndex(mainJs, searchIndex, storageJs, crate) { +function loadSearchJsAndIndex(searchJs, searchIndex, storageJs, crate) { if (searchIndex[searchIndex.length - 1].length === 0) { searchIndex.pop(); } @@ -270,9 +270,9 @@ function loadMainJsAndIndex(mainJs, searchIndex, storageJs, crate) { ALIASES = {}; finalJS += 'window = { "currentCrate": "' + crate + '", rootPath: "../" };\n'; finalJS += loadThings(["hasOwnProperty", "onEach"], 'function', extractFunction, storageJs); - finalJS += loadThings(arraysToLoad, 'array', extractArrayVariable, mainJs); - finalJS += loadThings(variablesToLoad, 'variable', extractVariable, mainJs); - finalJS += loadThings(functionsToLoad, 'function', extractFunction, mainJs); + finalJS += loadThings(arraysToLoad, 'array', extractArrayVariable, searchJs); + finalJS += loadThings(variablesToLoad, 'variable', extractVariable, searchJs); + finalJS += loadThings(functionsToLoad, 'function', extractFunction, searchJs); var loaded = loadContent(finalJS); var index = loaded.buildIndex(searchIndex.rawSearchIndex); @@ -382,12 +382,12 @@ function runChecks(testFile, loaded, index) { } function load_files(doc_folder, resource_suffix, crate) { - var mainJs = readFile(path.join(doc_folder, "main" + resource_suffix + ".js")); + var searchJs = readFile(path.join(doc_folder, "search" + resource_suffix + ".js")); var storageJs = readFile(path.join(doc_folder, "storage" + resource_suffix + ".js")); var searchIndex = readFile( path.join(doc_folder, "search-index" + resource_suffix + ".js")).split("\n"); - return loadMainJsAndIndex(mainJs, searchIndex, storageJs, crate); + return loadSearchJsAndIndex(searchJs, searchIndex, storageJs, crate); } function showHelp() { From a245642cabda4bc0fbb8139e383294b2796ad35f Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews <github@hoffman-andrews.com> Date: Wed, 14 Apr 2021 11:56:53 -0700 Subject: [PATCH 06/13] Change root_path to static_root_path Co-authored-by: Guillaume Gomez <guillaume1.gomez@gmail.com> --- src/librustdoc/html/layout.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs index 2ab10d5e6aa4d..dc67a63d33399 100644 --- a/src/librustdoc/html/layout.rs +++ b/src/librustdoc/html/layout.rs @@ -114,7 +114,7 @@ crate fn render<T: Print, S: Print>( {after_content}\ <div id=\"rustdoc-vars\" data-root-path=\"{root_path}\" data-current-crate=\"{krate}\" \ data-search-index-js=\"{root_path}search-index{suffix}.js\" \ - data-search-js=\"{root_path}search{suffix}.js\"></div> + data-search-js=\"{static_root_path}search{suffix}.js\"></div> <script src=\"{static_root_path}main{suffix}.js\"></script>\ {extra_scripts}\ </body>\ From 6f1f3eb516a433f4b32ab7298409015555b1cef6 Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews <github@hoffman-andrews.com> Date: Wed, 14 Apr 2021 12:17:24 -0700 Subject: [PATCH 07/13] Update comment on search.js static file. --- src/librustdoc/html/static_files.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/librustdoc/html/static_files.rs b/src/librustdoc/html/static_files.rs index a9c23a24c1481..2b73bd5d52ee6 100644 --- a/src/librustdoc/html/static_files.rs +++ b/src/librustdoc/html/static_files.rs @@ -24,8 +24,7 @@ crate static NORMALIZE_CSS: &str = include_str!("static/normalize.css"); /// including search behavior and docblock folding, among others. crate static MAIN_JS: &str = include_str!("static/main.js"); -/// The file contents of `search-index.js`, which contains the search behavior, -/// and into which is interpolated the search index. +/// The file contents of `search.js`, which contains the search behavior. crate static SEARCH_JS: &str = include_str!("static/search.js"); /// The file contents of `settings.js`, which contains the JavaScript used to handle the settings From 9aa4d068a10ea03dffc349485456ae342667a142 Mon Sep 17 00:00:00 2001 From: Josh Triplett <josh@joshtriplett.org> Date: Fri, 16 Apr 2021 12:57:20 -0700 Subject: [PATCH 08/13] Add documentation to help people find `Ipv4Addr::UNSPECIFIED` People looking for `INADDR_ANY` don't always find `Ipv4Addr::UNSPECIFIED`; add some documentation and an alias to help. --- library/std/src/net/ip.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/std/src/net/ip.rs b/library/std/src/net/ip.rs index da2415e361077..9b629e19be53d 100644 --- a/library/std/src/net/ip.rs +++ b/library/std/src/net/ip.rs @@ -334,6 +334,8 @@ impl Ipv4Addr { /// An IPv4 address representing an unspecified address: 0.0.0.0 /// + /// This corresponds to the constant `INADDR_ANY` in other languages. + /// /// # Examples /// /// ``` @@ -342,6 +344,7 @@ impl Ipv4Addr { /// let addr = Ipv4Addr::UNSPECIFIED; /// assert_eq!(addr, Ipv4Addr::new(0, 0, 0, 0)); /// ``` + #[doc(alias = "INADDR_ANY")] #[stable(feature = "ip_constructors", since = "1.30.0")] pub const UNSPECIFIED: Self = Ipv4Addr::new(0, 0, 0, 0); From 6516f9408ef47137e070c2635b9f4a67d848f129 Mon Sep 17 00:00:00 2001 From: Michael Howell <michael@notriddle.com> Date: Wed, 17 Mar 2021 11:41:01 -0700 Subject: [PATCH 09/13] rustdoc: use more precise relative URLS Instead of using a depth counter and adding "../" to get to the top, this commit makes rustdoc actually compare the path of what it's linking from to the path that it's linking to. This makes the resulting HTML shorter. Here's a comparison of one of the largest (non-source) files in the Rust standard library docs (about 4% improvement before gzipping). $ wc -c struct.Wrapping.old.html struct.Wrapping.new.html 2387389 struct.Wrapping.old.html 2298538 struct.Wrapping.new.html Most if it can be efficiently gzipped away. $ wc -c struct.Wrapping.old.html.gz struct.Wrapping.new.html.gz 70679 struct.Wrapping.old.html.gz 70050 struct.Wrapping.new.html.gz But it also makes a difference in the final DOM size, reducing it from 91MiB to 82MiB. --- src/librustdoc/clean/mod.rs | 7 + src/librustdoc/clean/types.rs | 33 +- src/librustdoc/html/format.rs | 517 +++++++++--------- src/librustdoc/html/mod.rs | 3 + src/librustdoc/html/render/context.rs | 8 +- src/librustdoc/html/render/mod.rs | 145 ++--- src/librustdoc/html/render/print_item.rs | 137 +++-- src/librustdoc/html/render/write_shared.rs | 3 +- src/librustdoc/html/tests.rs | 44 ++ src/librustdoc/lib.rs | 8 +- src/test/rustdoc/assoc-types.rs | 8 +- src/test/rustdoc/auxiliary/primitive-doc.rs | 6 + src/test/rustdoc/check-styled-link.rs | 2 +- src/test/rustdoc/cross-crate-primitive-doc.rs | 9 + src/test/rustdoc/default-trait-method-link.rs | 4 +- src/test/rustdoc/intra-doc-crate/self.rs | 4 +- src/test/rustdoc/intra-doc/anchors.rs | 2 +- .../rustdoc/intra-doc/associated-defaults.rs | 8 +- .../rustdoc/intra-doc/associated-items.rs | 10 +- src/test/rustdoc/intra-doc/basic.rs | 44 +- .../intra-doc/cross-crate/additional_doc.rs | 2 +- .../rustdoc/intra-doc/cross-crate/hidden.rs | 2 +- .../intra-doc/cross-crate/submodule-outer.rs | 4 +- .../intra-doc/disambiguators-removed.rs | 26 +- .../rustdoc/intra-doc/enum-struct-field.rs | 2 +- src/test/rustdoc/intra-doc/extern-type.rs | 2 +- src/test/rustdoc/intra-doc/issue-82209.rs | 2 +- src/test/rustdoc/intra-doc/mod-ambiguity.rs | 4 +- src/test/rustdoc/intra-doc/prim-precedence.rs | 4 +- src/test/rustdoc/intra-doc/private.rs | 6 +- src/test/rustdoc/intra-doc/proc-macro.rs | 10 +- src/test/rustdoc/intra-doc/pub-use.rs | 2 +- src/test/rustdoc/intra-doc/raw-ident-self.rs | 2 +- .../intra-doc/reexport-additional-docs.rs | 8 +- src/test/rustdoc/intra-doc/self.rs | 28 +- src/test/rustdoc/intra-doc/trait-impl.rs | 6 +- src/test/rustdoc/intra-doc/trait-item.rs | 2 +- src/test/rustdoc/intra-link-self-cache.rs | 4 +- src/test/rustdoc/issue-28478.rs | 8 +- src/test/rustdoc/issue-55364.rs | 28 +- src/test/rustdoc/issue-72340.rs | 2 +- src/test/rustdoc/link-assoc-const.rs | 4 +- src/test/rustdoc/proc-macro.rs | 6 +- .../rustdoc/raw-ident-eliminate-r-hashtag.rs | 6 +- src/test/rustdoc/struct-field.rs | 6 +- .../trait-impl-items-links-and-anchors.rs | 10 +- src/test/rustdoc/trait-self-link.rs | 2 +- 47 files changed, 613 insertions(+), 577 deletions(-) create mode 100644 src/librustdoc/html/tests.rs create mode 100644 src/test/rustdoc/auxiliary/primitive-doc.rs create mode 100644 src/test/rustdoc/cross-crate-primitive-doc.rs diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 7d33cf210139c..f5cd298dac4ff 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -2284,6 +2284,13 @@ impl Clean<Item> for (&hir::MacroDef<'_>, Option<Symbol>) { let vis = item.vis.clean(cx); let def_id = item.def_id.to_def_id(); + // Since this occurs in `clean()`, there are no cache entries for vis.print_with_space, + // so no links can be made. + // + // It's important that we maintain this invariant, because this is supposed to generate + // source code, not HTML. + assert!(cx.cache.paths.is_empty()); + if matchers.len() <= 1 { format!( "{}macro {}{} {{\n ...\n}}", diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index f3c9b987eb02a..2b25c6a26bcc4 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -41,6 +41,7 @@ use crate::core::DocContext; use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; use crate::html::render::cache::ExternalLocation; +use crate::html::render::Context; use self::FnRetTy::*; use self::ItemKind::*; @@ -193,19 +194,18 @@ impl Item { self.attrs.collapsed_doc_value() } - crate fn links(&self, cache: &Cache) -> Vec<RenderedLink> { + crate fn links(&self, cx: &Context<'_>) -> Vec<RenderedLink> { use crate::html::format::href; - use crate::html::render::CURRENT_DEPTH; - cache + cx.cache() .intra_doc_links .get(&self.def_id) .map_or(&[][..], |v| v.as_slice()) .iter() - .filter_map(|ItemLink { link: s, link_text, did, fragment }| { + .filter_map(|ItemLink { link: s, link_text, did, ref fragment }| { match *did { Some(did) => { - if let Some((mut href, ..)) = href(did, cache) { + if let Some((mut href, ..)) = href(did, cx) { if let Some(ref fragment) = *fragment { href.push('#'); href.push_str(fragment); @@ -219,16 +219,26 @@ impl Item { None } } + // FIXME(83083): using fragments as a side-channel for + // primitive names is very unfortunate None => { + let relative_to = &cx.current; if let Some(ref fragment) = *fragment { - let url = match cache.extern_locations.get(&self.def_id.krate) { + let url = match cx.cache().extern_locations.get(&self.def_id.krate) { Some(&(_, _, ExternalLocation::Local)) => { - let depth = CURRENT_DEPTH.with(|l| l.get()); - "../".repeat(depth) + if relative_to[0] == "std" { + let depth = relative_to.len() - 1; + "../".repeat(depth) + } else { + let depth = relative_to.len(); + format!("{}std/", "../".repeat(depth)) + } + } + Some(&(_, _, ExternalLocation::Remote(ref s))) => { + format!("{}/std/", s.trim_end_matches('/')) } - Some(&(_, _, ExternalLocation::Remote(ref s))) => s.to_string(), Some(&(_, _, ExternalLocation::Unknown)) | None => format!( - "https://doc.rust-lang.org/{}", + "https://doc.rust-lang.org/{}/std/", crate::doc_rust_lang_org_channel(), ), }; @@ -238,9 +248,8 @@ impl Item { original_text: s.clone(), new_text: link_text.clone(), href: format!( - "{}{}std/primitive.{}.html{}", + "{}primitive.{}.html{}", url, - if !url.ends_with('/') { "/" } else { "" }, &fragment[..tail], &fragment[tail..] ), diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 29d468e3d231c..191f29f40e72e 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -20,6 +20,7 @@ use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; use crate::html::escape::Escape; use crate::html::render::cache::ExternalLocation; +use crate::html::render::Context; use crate::html::render::CURRENT_DEPTH; crate trait Print { @@ -123,32 +124,30 @@ fn comma_sep<T: fmt::Display>(items: impl Iterator<Item = T>) -> impl fmt::Displ }) } -crate fn print_generic_bounds<'a, 'tcx: 'a>( +crate fn print_generic_bounds<'a, 'cx: 'a>( + cx: &'a Context<'cx>, bounds: &'a [clean::GenericBound], - cache: &'a Cache, - tcx: TyCtxt<'tcx>, -) -> impl fmt::Display + 'a + Captures<'tcx> { +) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(move |f| { let mut bounds_dup = FxHashSet::default(); for (i, bound) in - bounds.iter().filter(|b| bounds_dup.insert(b.print(cache, tcx).to_string())).enumerate() + bounds.iter().filter(|b| bounds_dup.insert(b.print(cx).to_string())).enumerate() { if i > 0 { f.write_str(" + ")?; } - fmt::Display::fmt(&bound.print(cache, tcx), f)?; + fmt::Display::fmt(&bound.print(cx), f)?; } Ok(()) }) } impl clean::GenericParamDef { - crate fn print<'a, 'tcx: 'a>( + crate fn print<'a, 'cx: 'a>( &'a self, - cache: &'a Cache, - tcx: TyCtxt<'tcx>, - ) -> impl fmt::Display + 'a + Captures<'tcx> { + cx: &'a Context<'cx>, + ) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(move |f| match self.kind { clean::GenericParamDefKind::Lifetime => write!(f, "{}", self.name), clean::GenericParamDefKind::Type { ref bounds, ref default, .. } => { @@ -156,17 +155,17 @@ impl clean::GenericParamDef { if !bounds.is_empty() { if f.alternate() { - write!(f, ": {:#}", print_generic_bounds(bounds, cache, tcx))?; + write!(f, ": {:#}", print_generic_bounds(cx, bounds))?; } else { - write!(f, ": {}", print_generic_bounds(bounds, cache, tcx))?; + write!(f, ": {}", print_generic_bounds(cx, bounds))?; } } if let Some(ref ty) = default { if f.alternate() { - write!(f, " = {:#}", ty.print(cache, tcx))?; + write!(f, " = {:#}", ty.print(cx))?; } else { - write!(f, " = {}", ty.print(cache, tcx))?; + write!(f, " = {}", ty.print(cx))?; } } @@ -174,9 +173,9 @@ impl clean::GenericParamDef { } clean::GenericParamDefKind::Const { ref ty, .. } => { if f.alternate() { - write!(f, "const {}: {:#}", self.name, ty.print(cache, tcx)) + write!(f, "const {}: {:#}", self.name, ty.print(cx)) } else { - write!(f, "const {}: {}", self.name, ty.print(cache, tcx)) + write!(f, "const {}: {}", self.name, ty.print(cx)) } } }) @@ -184,11 +183,10 @@ impl clean::GenericParamDef { } impl clean::Generics { - crate fn print<'a, 'tcx: 'a>( + crate fn print<'a, 'cx: 'a>( &'a self, - cache: &'a Cache, - tcx: TyCtxt<'tcx>, - ) -> impl fmt::Display + 'a + Captures<'tcx> { + cx: &'a Context<'cx>, + ) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(move |f| { let real_params = self.params.iter().filter(|p| !p.is_synthetic_type_param()).collect::<Vec<_>>(); @@ -196,9 +194,9 @@ impl clean::Generics { return Ok(()); } if f.alternate() { - write!(f, "<{:#}>", comma_sep(real_params.iter().map(|g| g.print(cache, tcx)))) + write!(f, "<{:#}>", comma_sep(real_params.iter().map(|g| g.print(cx)))) } else { - write!(f, "<{}>", comma_sep(real_params.iter().map(|g| g.print(cache, tcx)))) + write!(f, "<{}>", comma_sep(real_params.iter().map(|g| g.print(cx)))) } }) } @@ -207,13 +205,12 @@ impl clean::Generics { /// * The Generics from which to emit a where-clause. /// * The number of spaces to indent each line with. /// * Whether the where-clause needs to add a comma and newline after the last bound. -crate fn print_where_clause<'a, 'tcx: 'a>( +crate fn print_where_clause<'a, 'cx: 'a>( gens: &'a clean::Generics, - cache: &'a Cache, - tcx: TyCtxt<'tcx>, + cx: &'a Context<'cx>, indent: usize, end_newline: bool, -) -> impl fmt::Display + 'a + Captures<'tcx> { +) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(move |f| { if gens.where_predicates.is_empty() { return Ok(()); @@ -241,14 +238,14 @@ crate fn print_where_clause<'a, 'tcx: 'a>( if f.alternate() { clause.push_str(&format!( "{:#}: {:#}", - ty.print(cache, tcx), - print_generic_bounds(bounds, cache, tcx) + ty.print(cx), + print_generic_bounds(cx, bounds) )); } else { clause.push_str(&format!( "{}: {}", - ty.print(cache, tcx), - print_generic_bounds(bounds, cache, tcx) + ty.print(cx), + print_generic_bounds(cx, bounds) )); } } @@ -258,24 +255,16 @@ crate fn print_where_clause<'a, 'tcx: 'a>( lifetime.print(), bounds .iter() - .map(|b| b.print(cache, tcx).to_string()) + .map(|b| b.print(cx).to_string()) .collect::<Vec<_>>() .join(" + ") )); } clean::WherePredicate::EqPredicate { lhs, rhs } => { if f.alternate() { - clause.push_str(&format!( - "{:#} == {:#}", - lhs.print(cache, tcx), - rhs.print(cache, tcx), - )); + clause.push_str(&format!("{:#} == {:#}", lhs.print(cx), rhs.print(cx),)); } else { - clause.push_str(&format!( - "{} == {}", - lhs.print(cache, tcx), - rhs.print(cache, tcx), - )); + clause.push_str(&format!("{} == {}", lhs.print(cx), rhs.print(cx),)); } } } @@ -325,42 +314,40 @@ impl clean::Constant { } impl clean::PolyTrait { - fn print<'a, 'tcx: 'a>( + fn print<'a, 'cx: 'a>( &'a self, - cache: &'a Cache, - tcx: TyCtxt<'tcx>, - ) -> impl fmt::Display + 'a + Captures<'tcx> { + cx: &'a Context<'cx>, + ) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(move |f| { if !self.generic_params.is_empty() { if f.alternate() { write!( f, "for<{:#}> ", - comma_sep(self.generic_params.iter().map(|g| g.print(cache, tcx))) + comma_sep(self.generic_params.iter().map(|g| g.print(cx))) )?; } else { write!( f, "for<{}> ", - comma_sep(self.generic_params.iter().map(|g| g.print(cache, tcx))) + comma_sep(self.generic_params.iter().map(|g| g.print(cx))) )?; } } if f.alternate() { - write!(f, "{:#}", self.trait_.print(cache, tcx)) + write!(f, "{:#}", self.trait_.print(cx)) } else { - write!(f, "{}", self.trait_.print(cache, tcx)) + write!(f, "{}", self.trait_.print(cx)) } }) } } impl clean::GenericBound { - crate fn print<'a, 'tcx: 'a>( + crate fn print<'a, 'cx: 'a>( &'a self, - cache: &'a Cache, - tcx: TyCtxt<'tcx>, - ) -> impl fmt::Display + 'a + Captures<'tcx> { + cx: &'a Context<'cx>, + ) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(move |f| match self { clean::GenericBound::Outlives(lt) => write!(f, "{}", lt.print()), clean::GenericBound::TraitBound(ty, modifier) => { @@ -370,9 +357,9 @@ impl clean::GenericBound { hir::TraitBoundModifier::MaybeConst => "?const", }; if f.alternate() { - write!(f, "{}{:#}", modifier_str, ty.print(cache, tcx)) + write!(f, "{}{:#}", modifier_str, ty.print(cx)) } else { - write!(f, "{}{}", modifier_str, ty.print(cache, tcx)) + write!(f, "{}{}", modifier_str, ty.print(cx)) } } }) @@ -380,11 +367,10 @@ impl clean::GenericBound { } impl clean::GenericArgs { - fn print<'a, 'tcx: 'a>( + fn print<'a, 'cx: 'a>( &'a self, - cache: &'a Cache, - tcx: TyCtxt<'tcx>, - ) -> impl fmt::Display + 'a + Captures<'tcx> { + cx: &'a Context<'cx>, + ) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(move |f| { match self { clean::GenericArgs::AngleBracketed { args, bindings } => { @@ -401,9 +387,9 @@ impl clean::GenericArgs { } comma = true; if f.alternate() { - write!(f, "{:#}", arg.print(cache, tcx))?; + write!(f, "{:#}", arg.print(cx))?; } else { - write!(f, "{}", arg.print(cache, tcx))?; + write!(f, "{}", arg.print(cx))?; } } for binding in bindings { @@ -412,9 +398,9 @@ impl clean::GenericArgs { } comma = true; if f.alternate() { - write!(f, "{:#}", binding.print(cache, tcx))?; + write!(f, "{:#}", binding.print(cx))?; } else { - write!(f, "{}", binding.print(cache, tcx))?; + write!(f, "{}", binding.print(cx))?; } } if f.alternate() { @@ -433,17 +419,17 @@ impl clean::GenericArgs { } comma = true; if f.alternate() { - write!(f, "{:#}", ty.print(cache, tcx))?; + write!(f, "{:#}", ty.print(cx))?; } else { - write!(f, "{}", ty.print(cache, tcx))?; + write!(f, "{}", ty.print(cx))?; } } f.write_str(")")?; if let Some(ref ty) = *output { if f.alternate() { - write!(f, " -> {:#}", ty.print(cache, tcx))?; + write!(f, " -> {:#}", ty.print(cx))?; } else { - write!(f, " -> {}", ty.print(cache, tcx))?; + write!(f, " -> {}", ty.print(cx))?; } } } @@ -453,56 +439,89 @@ impl clean::GenericArgs { } } -crate fn href(did: DefId, cache: &Cache) -> Option<(String, ItemType, Vec<String>)> { +crate fn href(did: DefId, cx: &Context<'_>) -> Option<(String, ItemType, Vec<String>)> { + let cache = &cx.cache(); + let relative_to = &cx.current; + fn to_module_fqp(shortty: ItemType, fqp: &[String]) -> &[String] { + if shortty == ItemType::Module { &fqp[..] } else { &fqp[..fqp.len() - 1] } + } + if !did.is_local() && !cache.access_levels.is_public(did) && !cache.document_private { return None; } - let depth = CURRENT_DEPTH.with(|l| l.get()); - let (fqp, shortty, mut url) = match cache.paths.get(&did) { - Some(&(ref fqp, shortty)) => (fqp, shortty, "../".repeat(depth)), + let (fqp, shortty, mut url_parts) = match cache.paths.get(&did) { + Some(&(ref fqp, shortty)) => (fqp, shortty, { + let module_fqp = to_module_fqp(shortty, fqp); + href_relative_parts(module_fqp, relative_to) + }), None => { let &(ref fqp, shortty) = cache.external_paths.get(&did)?; + let module_fqp = to_module_fqp(shortty, fqp); ( fqp, shortty, match cache.extern_locations[&did.krate] { - (.., ExternalLocation::Remote(ref s)) => s.to_string(), - (.., ExternalLocation::Local) => "../".repeat(depth), + (.., ExternalLocation::Remote(ref s)) => { + let s = s.trim_end_matches('/'); + let mut s = vec![&s[..]]; + s.extend(module_fqp[..].iter().map(String::as_str)); + s + } + (.., ExternalLocation::Local) => href_relative_parts(module_fqp, relative_to), (.., ExternalLocation::Unknown) => return None, }, ) } }; - for component in &fqp[..fqp.len() - 1] { - url.push_str(component); - url.push('/'); - } + let last = &fqp.last().unwrap()[..]; + let filename; match shortty { ItemType::Module => { - url.push_str(fqp.last().unwrap()); - url.push_str("/index.html"); + url_parts.push("index.html"); } _ => { - url.push_str(shortty.as_str()); - url.push('.'); - url.push_str(fqp.last().unwrap()); - url.push_str(".html"); + filename = format!("{}.{}.html", shortty.as_str(), last); + url_parts.push(&filename); + } + } + Some((url_parts.join("/"), shortty, fqp.to_vec())) +} + +/// Both paths should only be modules. +/// This is because modules get their own directories; that is, `std::vec` and `std::vec::Vec` will +/// both need `../iter/trait.Iterator.html` to get at the iterator trait. +crate fn href_relative_parts<'a>(fqp: &'a [String], relative_to_fqp: &'a [String]) -> Vec<&'a str> { + for (i, (f, r)) in fqp.iter().zip(relative_to_fqp.iter()).enumerate() { + // e.g. linking to std::iter from std::vec (`dissimilar_part_count` will be 1) + if f != r { + let dissimilar_part_count = relative_to_fqp.len() - i; + let fqp_module = fqp[i..fqp.len()].iter().map(String::as_str); + return std::iter::repeat("..").take(dissimilar_part_count).chain(fqp_module).collect(); } } - Some((url, shortty, fqp.to_vec())) + // e.g. linking to std::sync::atomic from std::sync + if relative_to_fqp.len() < fqp.len() { + fqp[relative_to_fqp.len()..fqp.len()].iter().map(String::as_str).collect() + // e.g. linking to std::sync from std::sync::atomic + } else if fqp.len() < relative_to_fqp.len() { + let dissimilar_part_count = relative_to_fqp.len() - fqp.len(); + std::iter::repeat("..").take(dissimilar_part_count).collect() + // linking to the same module + } else { + Vec::new() + } } /// Used when rendering a `ResolvedPath` structure. This invokes the `path` /// rendering function with the necessary arguments for linking to a local path. -fn resolved_path<'a, 'tcx: 'a>( +fn resolved_path<'a, 'cx: 'a>( w: &mut fmt::Formatter<'_>, did: DefId, path: &clean::Path, print_all: bool, use_absolute: bool, - cache: &Cache, - tcx: TyCtxt<'tcx>, + cx: &'cx Context<'_>, ) -> fmt::Result { let last = path.segments.last().unwrap(); @@ -512,22 +531,22 @@ fn resolved_path<'a, 'tcx: 'a>( } } if w.alternate() { - write!(w, "{}{:#}", &last.name, last.args.print(cache, tcx))?; + write!(w, "{}{:#}", &last.name, last.args.print(cx))?; } else { let path = if use_absolute { - if let Some((_, _, fqp)) = href(did, cache) { + if let Some((_, _, fqp)) = href(did, cx) { format!( "{}::{}", fqp[..fqp.len() - 1].join("::"), - anchor(did, fqp.last().unwrap(), cache) + anchor(did, fqp.last().unwrap(), cx) ) } else { last.name.to_string() } } else { - anchor(did, &*last.name.as_str(), cache).to_string() + anchor(did, &*last.name.as_str(), cx).to_string() }; - write!(w, "{}{}", path, last.args.print(cache, tcx))?; + write!(w, "{}{}", path, last.args.print(cx))?; } Ok(()) } @@ -583,16 +602,15 @@ fn primitive_link( } /// Helper to render type parameters -fn tybounds<'a, 'tcx: 'a>( +fn tybounds<'a, 'cx: 'a>( param_names: &'a Option<Vec<clean::GenericBound>>, - cache: &'a Cache, - tcx: TyCtxt<'tcx>, -) -> impl fmt::Display + 'a + Captures<'tcx> { + cx: &'a Context<'cx>, +) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(move |f| match *param_names { Some(ref params) => { for param in params { write!(f, " + ")?; - fmt::Display::fmt(¶m.print(cache, tcx), f)?; + fmt::Display::fmt(¶m.print(cx), f)?; } Ok(()) } @@ -600,9 +618,14 @@ fn tybounds<'a, 'tcx: 'a>( }) } -crate fn anchor<'a>(did: DefId, text: &'a str, cache: &'a Cache) -> impl fmt::Display + 'a { +crate fn anchor<'a, 'cx: 'a>( + did: DefId, + text: &'a str, + cx: &'cx Context<'_>, +) -> impl fmt::Display + 'a { + let parts = href(did, cx); display_fn(move |f| { - if let Some((url, short_ty, fqp)) = href(did, cache) { + if let Some((url, short_ty, fqp)) = parts { write!( f, r#"<a class="{}" href="{}" title="{} {}">{}</a>"#, @@ -618,12 +641,11 @@ crate fn anchor<'a>(did: DefId, text: &'a str, cache: &'a Cache) -> impl fmt::Di }) } -fn fmt_type( +fn fmt_type<'cx>( t: &clean::Type, f: &mut fmt::Formatter<'_>, use_absolute: bool, - cache: &Cache, - tcx: TyCtxt<'_>, + cx: &'cx Context<'_>, ) -> fmt::Result { debug!("fmt_type(t = {:?})", t); @@ -634,69 +656,69 @@ fn fmt_type( f.write_str("dyn ")?; } // Paths like `T::Output` and `Self::Output` should be rendered with all segments. - resolved_path(f, did, path, is_generic, use_absolute, cache, tcx)?; - fmt::Display::fmt(&tybounds(param_names, cache, tcx), f) + resolved_path(f, did, path, is_generic, use_absolute, cx)?; + fmt::Display::fmt(&tybounds(param_names, cx), f) } clean::Infer => write!(f, "_"), - clean::Primitive(prim) => primitive_link(f, prim, prim.as_str(), cache), + clean::Primitive(prim) => primitive_link(f, prim, prim.as_str(), &cx.cache()), clean::BareFunction(ref decl) => { if f.alternate() { write!( f, "{:#}{}{:#}fn{:#}", - decl.print_hrtb_with_space(cache, tcx), + decl.print_hrtb_with_space(cx), decl.unsafety.print_with_space(), print_abi_with_space(decl.abi), - decl.decl.print(cache, tcx), + decl.decl.print(cx), ) } else { write!( f, "{}{}{}", - decl.print_hrtb_with_space(cache, tcx), + decl.print_hrtb_with_space(cx), decl.unsafety.print_with_space(), print_abi_with_space(decl.abi) )?; - primitive_link(f, PrimitiveType::Fn, "fn", cache)?; - write!(f, "{}", decl.decl.print(cache, tcx)) + primitive_link(f, PrimitiveType::Fn, "fn", &cx.cache())?; + write!(f, "{}", decl.decl.print(cx)) } } clean::Tuple(ref typs) => { match &typs[..] { - &[] => primitive_link(f, PrimitiveType::Unit, "()", cache), + &[] => primitive_link(f, PrimitiveType::Unit, "()", &cx.cache()), &[ref one] => { - primitive_link(f, PrimitiveType::Tuple, "(", cache)?; + primitive_link(f, PrimitiveType::Tuple, "(", &cx.cache())?; // Carry `f.alternate()` into this display w/o branching manually. - fmt::Display::fmt(&one.print(cache, tcx), f)?; - primitive_link(f, PrimitiveType::Tuple, ",)", cache) + fmt::Display::fmt(&one.print(cx), f)?; + primitive_link(f, PrimitiveType::Tuple, ",)", &cx.cache()) } many => { - primitive_link(f, PrimitiveType::Tuple, "(", cache)?; + primitive_link(f, PrimitiveType::Tuple, "(", &cx.cache())?; for (i, item) in many.iter().enumerate() { if i != 0 { write!(f, ", ")?; } - fmt::Display::fmt(&item.print(cache, tcx), f)?; + fmt::Display::fmt(&item.print(cx), f)?; } - primitive_link(f, PrimitiveType::Tuple, ")", cache) + primitive_link(f, PrimitiveType::Tuple, ")", &cx.cache()) } } } clean::Slice(ref t) => { - primitive_link(f, PrimitiveType::Slice, "[", cache)?; - fmt::Display::fmt(&t.print(cache, tcx), f)?; - primitive_link(f, PrimitiveType::Slice, "]", cache) + primitive_link(f, PrimitiveType::Slice, "[", &cx.cache())?; + fmt::Display::fmt(&t.print(cx), f)?; + primitive_link(f, PrimitiveType::Slice, "]", &cx.cache()) } clean::Array(ref t, ref n) => { - primitive_link(f, PrimitiveType::Array, "[", cache)?; - fmt::Display::fmt(&t.print(cache, tcx), f)?; + primitive_link(f, PrimitiveType::Array, "[", &cx.cache())?; + fmt::Display::fmt(&t.print(cx), f)?; if f.alternate() { - primitive_link(f, PrimitiveType::Array, &format!("; {}]", n), cache) + primitive_link(f, PrimitiveType::Array, &format!("; {}]", n), &cx.cache()) } else { - primitive_link(f, PrimitiveType::Array, &format!("; {}]", Escape(n)), cache) + primitive_link(f, PrimitiveType::Array, &format!("; {}]", Escape(n)), &cx.cache()) } } - clean::Never => primitive_link(f, PrimitiveType::Never, "!", cache), + clean::Never => primitive_link(f, PrimitiveType::Never, "!", &cx.cache()), clean::RawPointer(m, ref t) => { let m = match m { hir::Mutability::Mut => "mut", @@ -708,15 +730,15 @@ fn fmt_type( primitive_link( f, clean::PrimitiveType::RawPointer, - &format!("*{} {:#}", m, t.print(cache, tcx)), - cache, + &format!("*{} {:#}", m, t.print(cx)), + &cx.cache(), ) } else { primitive_link( f, clean::PrimitiveType::RawPointer, - &format!("*{} {}", m, t.print(cache, tcx)), - cache, + &format!("*{} {}", m, t.print(cx)), + &cx.cache(), ) } } @@ -725,9 +747,9 @@ fn fmt_type( f, clean::PrimitiveType::RawPointer, &format!("*{} ", m), - cache, + &cx.cache(), )?; - fmt::Display::fmt(&t.print(cache, tcx), f) + fmt::Display::fmt(&t.print(cx), f) } } } @@ -747,15 +769,15 @@ fn fmt_type( primitive_link( f, PrimitiveType::Slice, - &format!("{}{}{}[{:#}]", amp, lt, m, bt.print(cache, tcx)), - cache, + &format!("{}{}{}[{:#}]", amp, lt, m, bt.print(cx)), + &cx.cache(), ) } else { primitive_link( f, PrimitiveType::Slice, - &format!("{}{}{}[{}]", amp, lt, m, bt.print(cache, tcx)), - cache, + &format!("{}{}{}[{}]", amp, lt, m, bt.print(cx)), + &cx.cache(), ) } } @@ -764,20 +786,20 @@ fn fmt_type( f, PrimitiveType::Slice, &format!("{}{}{}[", amp, lt, m), - cache, + &cx.cache(), )?; if f.alternate() { - write!(f, "{:#}", bt.print(cache, tcx))?; + write!(f, "{:#}", bt.print(cx))?; } else { - write!(f, "{}", bt.print(cache, tcx))?; + write!(f, "{}", bt.print(cx))?; } - primitive_link(f, PrimitiveType::Slice, "]", cache) + primitive_link(f, PrimitiveType::Slice, "]", &cx.cache()) } } } clean::ResolvedPath { param_names: Some(ref v), .. } if !v.is_empty() => { write!(f, "{}{}{}(", amp, lt, m)?; - fmt_type(&ty, f, use_absolute, cache, tcx)?; + fmt_type(&ty, f, use_absolute, cx)?; write!(f, ")") } clean::Generic(..) => { @@ -785,21 +807,21 @@ fn fmt_type( f, PrimitiveType::Reference, &format!("{}{}{}", amp, lt, m), - cache, + &cx.cache(), )?; - fmt_type(&ty, f, use_absolute, cache, tcx) + fmt_type(&ty, f, use_absolute, cx) } _ => { write!(f, "{}{}{}", amp, lt, m)?; - fmt_type(&ty, f, use_absolute, cache, tcx) + fmt_type(&ty, f, use_absolute, cx) } } } clean::ImplTrait(ref bounds) => { if f.alternate() { - write!(f, "impl {:#}", print_generic_bounds(bounds, cache, tcx)) + write!(f, "impl {:#}", print_generic_bounds(cx, bounds)) } else { - write!(f, "impl {}", print_generic_bounds(bounds, cache, tcx)) + write!(f, "impl {}", print_generic_bounds(cx, bounds)) } } clean::QPath { ref name, ref self_type, ref trait_ } => { @@ -811,25 +833,15 @@ fn fmt_type( }; if f.alternate() { if should_show_cast { - write!( - f, - "<{:#} as {:#}>::", - self_type.print(cache, tcx), - trait_.print(cache, tcx) - )? + write!(f, "<{:#} as {:#}>::", self_type.print(cx), trait_.print(cx))? } else { - write!(f, "{:#}::", self_type.print(cache, tcx))? + write!(f, "{:#}::", self_type.print(cx))? } } else { if should_show_cast { - write!( - f, - "<{} as {}>::", - self_type.print(cache, tcx), - trait_.print(cache, tcx) - )? + write!(f, "<{} as {}>::", self_type.print(cx), trait_.print(cx))? } else { - write!(f, "{}::", self_type.print(cache, tcx))? + write!(f, "{}::", self_type.print(cx))? } }; match *trait_ { @@ -844,7 +856,7 @@ fn fmt_type( // everything comes in as a fully resolved QPath (hard to // look at). box clean::ResolvedPath { did, ref param_names, .. } => { - match href(did, cache) { + match href(did, cx) { Some((ref url, _, ref path)) if !f.alternate() => { write!( f, @@ -870,64 +882,61 @@ fn fmt_type( } impl clean::Type { - crate fn print<'b, 'a: 'b, 'tcx: 'a>( + crate fn print<'b, 'a: 'b, 'cx: 'a>( &'a self, - cache: &'b Cache, - tcx: TyCtxt<'tcx>, - ) -> impl fmt::Display + 'b + Captures<'tcx> { - display_fn(move |f| fmt_type(self, f, false, cache, tcx)) + cx: &'a Context<'cx>, + ) -> impl fmt::Display + 'b + Captures<'cx> { + display_fn(move |f| fmt_type(self, f, false, cx)) } } impl clean::Impl { - crate fn print<'a, 'tcx: 'a>( + crate fn print<'a, 'cx: 'a>( &'a self, - cache: &'a Cache, use_absolute: bool, - tcx: TyCtxt<'tcx>, - ) -> impl fmt::Display + 'a + Captures<'tcx> { + cx: &'a Context<'cx>, + ) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(move |f| { if f.alternate() { - write!(f, "impl{:#} ", self.generics.print(cache, tcx))?; + write!(f, "impl{:#} ", self.generics.print(cx))?; } else { - write!(f, "impl{} ", self.generics.print(cache, tcx))?; + write!(f, "impl{} ", self.generics.print(cx))?; } if let Some(ref ty) = self.trait_ { if self.negative_polarity { write!(f, "!")?; } - fmt::Display::fmt(&ty.print(cache, tcx), f)?; + fmt::Display::fmt(&ty.print(cx), f)?; write!(f, " for ")?; } if let Some(ref ty) = self.blanket_impl { - fmt_type(ty, f, use_absolute, cache, tcx)?; + fmt_type(ty, f, use_absolute, cx)?; } else { - fmt_type(&self.for_, f, use_absolute, cache, tcx)?; + fmt_type(&self.for_, f, use_absolute, cx)?; } - fmt::Display::fmt(&print_where_clause(&self.generics, cache, tcx, 0, true), f)?; + fmt::Display::fmt(&print_where_clause(&self.generics, cx, 0, true), f)?; Ok(()) }) } } impl clean::Arguments { - crate fn print<'a, 'tcx: 'a>( + crate fn print<'a, 'cx: 'a>( &'a self, - cache: &'a Cache, - tcx: TyCtxt<'tcx>, - ) -> impl fmt::Display + 'a + Captures<'tcx> { + cx: &'a Context<'cx>, + ) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(move |f| { for (i, input) in self.values.iter().enumerate() { if !input.name.is_empty() { write!(f, "{}: ", input.name)?; } if f.alternate() { - write!(f, "{:#}", input.type_.print(cache, tcx))?; + write!(f, "{:#}", input.type_.print(cx))?; } else { - write!(f, "{}", input.type_.print(cache, tcx))?; + write!(f, "{}", input.type_.print(cx))?; } if i + 1 < self.values.len() { write!(f, ", ")?; @@ -939,33 +948,29 @@ impl clean::Arguments { } impl clean::FnRetTy { - crate fn print<'a, 'tcx: 'a>( + crate fn print<'a, 'cx: 'a>( &'a self, - cache: &'a Cache, - tcx: TyCtxt<'tcx>, - ) -> impl fmt::Display + 'a + Captures<'tcx> { + cx: &'a Context<'cx>, + ) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(move |f| match self { clean::Return(clean::Tuple(tys)) if tys.is_empty() => Ok(()), - clean::Return(ty) if f.alternate() => write!(f, " -> {:#}", ty.print(cache, tcx)), - clean::Return(ty) => write!(f, " -> {}", ty.print(cache, tcx)), + clean::Return(ty) if f.alternate() => { + write!(f, " -> {:#}", ty.print(cx)) + } + clean::Return(ty) => write!(f, " -> {}", ty.print(cx)), clean::DefaultReturn => Ok(()), }) } } impl clean::BareFunctionDecl { - fn print_hrtb_with_space<'a, 'tcx: 'a>( + fn print_hrtb_with_space<'a, 'cx: 'a>( &'a self, - cache: &'a Cache, - tcx: TyCtxt<'tcx>, - ) -> impl fmt::Display + 'a + Captures<'tcx> { + cx: &'a Context<'cx>, + ) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(move |f| { if !self.generic_params.is_empty() { - write!( - f, - "for<{}> ", - comma_sep(self.generic_params.iter().map(|g| g.print(cache, tcx))) - ) + write!(f, "for<{}> ", comma_sep(self.generic_params.iter().map(|g| g.print(cx)))) } else { Ok(()) } @@ -974,28 +979,27 @@ impl clean::BareFunctionDecl { } impl clean::FnDecl { - crate fn print<'b, 'a: 'b, 'tcx: 'a>( + crate fn print<'b, 'a: 'b, 'cx: 'a>( &'a self, - cache: &'b Cache, - tcx: TyCtxt<'tcx>, - ) -> impl fmt::Display + 'b + Captures<'tcx> { + cx: &'a Context<'cx>, + ) -> impl fmt::Display + 'b + Captures<'cx> { display_fn(move |f| { let ellipsis = if self.c_variadic { ", ..." } else { "" }; if f.alternate() { write!( f, "({args:#}{ellipsis}){arrow:#}", - args = self.inputs.print(cache, tcx), + args = self.inputs.print(cx), ellipsis = ellipsis, - arrow = self.output.print(cache, tcx) + arrow = self.output.print(cx) ) } else { write!( f, "({args}{ellipsis}){arrow}", - args = self.inputs.print(cache, tcx), + args = self.inputs.print(cx), ellipsis = ellipsis, - arrow = self.output.print(cache, tcx) + arrow = self.output.print(cx) ) } }) @@ -1007,21 +1011,19 @@ impl clean::FnDecl { /// * `indent`: The number of spaces to indent each successive line with, if line-wrapping is /// necessary. /// * `asyncness`: Whether the function is async or not. - crate fn full_print<'b, 'a: 'b, 'tcx: 'a>( + crate fn full_print<'a, 'cx: 'a>( &'a self, - cache: &'b Cache, - tcx: TyCtxt<'tcx>, + cx: &'a Context<'cx>, header_len: usize, indent: usize, asyncness: hir::IsAsync, - ) -> impl fmt::Display + 'b + Captures<'tcx> { - display_fn(move |f| self.inner_full_print(cache, tcx, header_len, indent, asyncness, f)) + ) -> impl fmt::Display + 'a + Captures<'cx> { + display_fn(move |f| self.inner_full_print(cx, header_len, indent, asyncness, f)) } fn inner_full_print( &self, - cache: &Cache, - tcx: TyCtxt<'_>, + cx: &Context<'_>, header_len: usize, indent: usize, asyncness: hir::IsAsync, @@ -1060,11 +1062,11 @@ impl clean::FnDecl { } clean::SelfExplicit(ref typ) => { if f.alternate() { - args.push_str(&format!("self: {:#}", typ.print(cache, tcx))); + args.push_str(&format!("self: {:#}", typ.print(cx))); } else { - args.push_str(&format!("self: {}", typ.print(cache, tcx))); + args.push_str(&format!("self: {}", typ.print(cx))); } - args_plain.push_str(&format!("self: {:#}", typ.print(cache, tcx))); + args_plain.push_str(&format!("self: {:#}", typ.print(cx))); } } } else { @@ -1078,11 +1080,11 @@ impl clean::FnDecl { } if f.alternate() { - args.push_str(&format!("{:#}", input.type_.print(cache, tcx))); + args.push_str(&format!("{:#}", input.type_.print(cx))); } else { - args.push_str(&input.type_.print(cache, tcx).to_string()); + args.push_str(&input.type_.print(cx).to_string()); } - args_plain.push_str(&format!("{:#}", input.type_.print(cache, tcx))); + args_plain.push_str(&format!("{:#}", input.type_.print(cx))); } if i + 1 < self.inputs.values.len() { args.push(','); @@ -1100,19 +1102,11 @@ impl clean::FnDecl { let arrow_plain; let arrow = if let hir::IsAsync::Async = asyncness { let output = self.sugared_async_return_type(); - arrow_plain = format!("{:#}", output.print(cache, tcx)); - if f.alternate() { - arrow_plain.clone() - } else { - format!("{}", output.print(cache, tcx)) - } + arrow_plain = format!("{:#}", output.print(cx)); + if f.alternate() { arrow_plain.clone() } else { format!("{}", output.print(cx)) } } else { - arrow_plain = format!("{:#}", self.output.print(cache, tcx)); - if f.alternate() { - arrow_plain.clone() - } else { - format!("{}", self.output.print(cache, tcx)) - } + arrow_plain = format!("{:#}", self.output.print(cx)); + if f.alternate() { arrow_plain.clone() } else { format!("{}", self.output.print(cx)) } }; let declaration_len = header_len + args_plain.len() + arrow_plain.len(); @@ -1138,12 +1132,11 @@ impl clean::FnDecl { } impl clean::Visibility { - crate fn print_with_space<'a, 'tcx: 'a>( + crate fn print_with_space<'a, 'cx: 'a>( self, - tcx: TyCtxt<'tcx>, + cx: &'a Context<'cx>, item_did: DefId, - cache: &'a Cache, - ) -> impl fmt::Display + 'a + Captures<'tcx> { + ) -> impl fmt::Display + 'a + Captures<'cx> { let to_print = match self { clean::Public => "pub ".to_owned(), clean::Inherited => String::new(), @@ -1151,7 +1144,7 @@ impl clean::Visibility { // FIXME(camelid): This may not work correctly if `item_did` is a module. // However, rustdoc currently never displays a module's // visibility, so it shouldn't matter. - let parent_module = find_nearest_parent_module(tcx, item_did); + let parent_module = find_nearest_parent_module(cx.tcx(), item_did); if vis_did.index == CRATE_DEF_INDEX { "pub(crate) ".to_owned() @@ -1160,17 +1153,17 @@ impl clean::Visibility { // is the same as no visibility modifier String::new() } else if parent_module - .map(|parent| find_nearest_parent_module(tcx, parent)) + .map(|parent| find_nearest_parent_module(cx.tcx(), parent)) .flatten() == Some(vis_did) { "pub(super) ".to_owned() } else { - let path = tcx.def_path(vis_did); + let path = cx.tcx().def_path(vis_did); debug!("path={:?}", path); // modified from `resolved_path()` to work with `DefPathData` let last_name = path.data.last().unwrap().data.get_opt_name().unwrap(); - let anchor = anchor(vis_did, &last_name.as_str(), cache).to_string(); + let anchor = anchor(vis_did, &last_name.as_str(), cx).to_string(); let mut s = "pub(in ".to_owned(); for seg in &path.data[..path.data.len() - 1] { @@ -1263,24 +1256,23 @@ impl PrintWithSpace for hir::Mutability { } impl clean::Import { - crate fn print<'b, 'a: 'b, 'tcx: 'a>( + crate fn print<'a, 'cx: 'a>( &'a self, - cache: &'b Cache, - tcx: TyCtxt<'tcx>, - ) -> impl fmt::Display + 'b + Captures<'tcx> { + cx: &'a Context<'cx>, + ) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(move |f| match self.kind { clean::ImportKind::Simple(name) => { if name == self.source.path.last() { - write!(f, "use {};", self.source.print(cache, tcx)) + write!(f, "use {};", self.source.print(cx)) } else { - write!(f, "use {} as {};", self.source.print(cache, tcx), name) + write!(f, "use {} as {};", self.source.print(cx), name) } } clean::ImportKind::Glob => { if self.source.path.segments.is_empty() { write!(f, "use *;") } else { - write!(f, "use {}::*;", self.source.print(cache, tcx)) + write!(f, "use {}::*;", self.source.print(cx)) } } }) @@ -1288,20 +1280,19 @@ impl clean::Import { } impl clean::ImportSource { - crate fn print<'b, 'a: 'b, 'tcx: 'a>( + crate fn print<'a, 'cx: 'a>( &'a self, - cache: &'b Cache, - tcx: TyCtxt<'tcx>, - ) -> impl fmt::Display + 'b + Captures<'tcx> { + cx: &'a Context<'cx>, + ) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(move |f| match self.did { - Some(did) => resolved_path(f, did, &self.path, true, false, cache, tcx), + Some(did) => resolved_path(f, did, &self.path, true, false, cx), _ => { for seg in &self.path.segments[..self.path.segments.len() - 1] { write!(f, "{}::", seg.name)?; } let name = self.path.last_name(); if let hir::def::Res::PrimTy(p) = self.path.res { - primitive_link(f, PrimitiveType::from(p), &*name, cache)?; + primitive_link(f, PrimitiveType::from(p), &*name, &cx.cache())?; } else { write!(f, "{}", name)?; } @@ -1312,27 +1303,26 @@ impl clean::ImportSource { } impl clean::TypeBinding { - crate fn print<'b, 'a: 'b, 'tcx: 'a>( + crate fn print<'a, 'cx: 'a>( &'a self, - cache: &'b Cache, - tcx: TyCtxt<'tcx>, - ) -> impl fmt::Display + 'b + Captures<'tcx> { + cx: &'a Context<'cx>, + ) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(move |f| { f.write_str(&*self.name.as_str())?; match self.kind { clean::TypeBindingKind::Equality { ref ty } => { if f.alternate() { - write!(f, " = {:#}", ty.print(cache, tcx))?; + write!(f, " = {:#}", ty.print(cx))?; } else { - write!(f, " = {}", ty.print(cache, tcx))?; + write!(f, " = {}", ty.print(cx))?; } } clean::TypeBindingKind::Constraint { ref bounds } => { if !bounds.is_empty() { if f.alternate() { - write!(f, ": {:#}", print_generic_bounds(bounds, cache, tcx))?; + write!(f, ": {:#}", print_generic_bounds(cx, bounds))?; } else { - write!(f, ": {}", print_generic_bounds(bounds, cache, tcx))?; + write!(f, ": {}", print_generic_bounds(cx, bounds))?; } } } @@ -1357,15 +1347,14 @@ crate fn print_default_space<'a>(v: bool) -> &'a str { } impl clean::GenericArg { - crate fn print<'b, 'a: 'b, 'tcx: 'a>( + crate fn print<'a, 'cx: 'a>( &'a self, - cache: &'b Cache, - tcx: TyCtxt<'tcx>, - ) -> impl fmt::Display + 'b + Captures<'tcx> { + cx: &'a Context<'cx>, + ) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(move |f| match self { clean::GenericArg::Lifetime(lt) => fmt::Display::fmt(<.print(), f), - clean::GenericArg::Type(ty) => fmt::Display::fmt(&ty.print(cache, tcx), f), - clean::GenericArg::Const(ct) => fmt::Display::fmt(&ct.print(tcx), f), + clean::GenericArg::Type(ty) => fmt::Display::fmt(&ty.print(cx), f), + clean::GenericArg::Const(ct) => fmt::Display::fmt(&ct.print(cx.tcx()), f), }) } } diff --git a/src/librustdoc/html/mod.rs b/src/librustdoc/html/mod.rs index 4318be898ceb4..60ebdf5690d0d 100644 --- a/src/librustdoc/html/mod.rs +++ b/src/librustdoc/html/mod.rs @@ -8,3 +8,6 @@ crate mod render; crate mod sources; crate mod static_files; crate mod toc; + +#[cfg(test)] +mod tests; diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index df5ff6e106d7c..d866cf4f4cf04 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -40,7 +40,7 @@ use crate::html::{layout, sources}; crate struct Context<'tcx> { /// Current hierarchy of components leading down to what's currently being /// rendered - pub(super) current: Vec<String>, + pub(crate) current: Vec<String>, /// The current destination folder of where HTML artifacts should be placed. /// This changes as the context descends into the module hierarchy. pub(super) dst: PathBuf, @@ -144,10 +144,14 @@ impl SharedContext<'_> { } impl<'tcx> Context<'tcx> { - pub(super) fn tcx(&self) -> TyCtxt<'tcx> { + pub(crate) fn tcx(&self) -> TyCtxt<'tcx> { self.shared.tcx } + pub(crate) fn cache(&self) -> &Cache { + &self.cache + } + fn sess(&self) -> &'tcx Session { &self.shared.tcx.sess } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 9886b5ceeed39..9bd4f57051c87 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -51,7 +51,6 @@ use rustc_hir::def::CtorKind; use rustc_hir::def_id::DefId; use rustc_hir::Mutability; use rustc_middle::middle::stability; -use rustc_middle::ty::TyCtxt; use rustc_span::symbol::{kw, sym, Symbol}; use serde::ser::SerializeSeq; use serde::{Serialize, Serializer}; @@ -61,7 +60,7 @@ use crate::docfs::PathError; use crate::error::Error; use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; -use crate::formats::{AssocItemRender, FormatRenderer, Impl, RenderMode}; +use crate::formats::{AssocItemRender, Impl, RenderMode}; use crate::html::escape::Escape; use crate::html::format::{ href, print_abi_with_space, print_default_space, print_generic_bounds, print_where_clause, @@ -560,11 +559,10 @@ fn document_short( return; } if let Some(s) = item.doc_value() { - let mut summary_html = MarkdownSummaryLine(&s, &item.links(&cx.cache)).into_string(); + let mut summary_html = MarkdownSummaryLine(&s, &item.links(cx)).into_string(); if s.contains('\n') { - let link = - format!(r#" <a href="{}">Read more</a>"#, naive_assoc_href(item, link, cx.cache())); + let link = format!(r#" <a href="{}">Read more</a>"#, naive_assoc_href(item, link, cx)); if let Some(idx) = summary_html.rfind("</p>") { summary_html.insert_str(idx, &link); @@ -599,7 +597,7 @@ fn document_full( ) { if let Some(s) = cx.shared.maybe_collapsed_doc_value(item) { debug!("Doc block: =====\n{}\n=====", s); - render_markdown(w, cx, &*s, item.links(&cx.cache), prefix, is_hidden); + render_markdown(w, cx, &*s, item.links(cx), prefix, is_hidden); } else if !prefix.is_empty() { if is_hidden { w.write_str("<div class=\"docblock hidden\">"); @@ -785,7 +783,7 @@ fn render_impls( w.write_str(&impls.join("")); } -fn naive_assoc_href(it: &clean::Item, link: AssocItemLink<'_>, cache: &Cache) -> String { +fn naive_assoc_href(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>) -> String { use crate::formats::item_type::ItemType::*; let name = it.name.as_ref().unwrap(); @@ -799,7 +797,7 @@ fn naive_assoc_href(it: &clean::Item, link: AssocItemLink<'_>, cache: &Cache) -> AssocItemLink::Anchor(Some(ref id)) => format!("#{}", id), AssocItemLink::Anchor(None) => anchor, AssocItemLink::GotoSource(did, _) => { - href(did, cache).map(|p| format!("{}{}", p.0, anchor)).unwrap_or(anchor) + href(did, cx).map(|p| format!("{}{}", p.0, anchor)).unwrap_or(anchor) } } } @@ -813,16 +811,14 @@ fn assoc_const( extra: &str, cx: &Context<'_>, ) { - let cache = cx.cache(); - let tcx = cx.tcx(); write!( w, "{}{}const <a href=\"{}\" class=\"constant\"><b>{}</b></a>: {}", extra, - it.visibility.print_with_space(tcx, it.def_id, cache), - naive_assoc_href(it, link, cache), + it.visibility.print_with_space(cx, it.def_id), + naive_assoc_href(it, link, cx), it.name.as_ref().unwrap(), - ty.print(cache, tcx) + ty.print(cx) ); } @@ -833,21 +829,20 @@ fn assoc_type( default: Option<&clean::Type>, link: AssocItemLink<'_>, extra: &str, - cache: &Cache, - tcx: TyCtxt<'_>, + cx: &Context<'_>, ) { write!( w, "{}type <a href=\"{}\" class=\"type\">{}</a>", extra, - naive_assoc_href(it, link, cache), + naive_assoc_href(it, link, cx), it.name.as_ref().unwrap() ); if !bounds.is_empty() { - write!(w, ": {}", print_generic_bounds(bounds, cache, tcx)) + write!(w, ": {}", print_generic_bounds(cx, bounds)) } if let Some(default) = default { - write!(w, " = {}", default.print(cache, tcx)) + write!(w, " = {}", default.print(cx)) } } @@ -897,8 +892,6 @@ fn render_assoc_item( parent: ItemType, cx: &Context<'_>, ) { - let cache = cx.cache(); - let tcx = cx.tcx(); let name = meth.name.as_ref().unwrap(); let href = match link { AssocItemLink::Anchor(Some(ref id)) => format!("#{}", id), @@ -912,19 +905,19 @@ fn render_assoc_item( ItemType::TyMethod }; - href(did, cache) + href(did, cx) .map(|p| format!("{}#{}.{}", p.0, ty, name)) .unwrap_or_else(|| format!("#{}.{}", ty, name)) } }; - let vis = meth.visibility.print_with_space(tcx, meth.def_id, cache).to_string(); + let vis = meth.visibility.print_with_space(cx, meth.def_id).to_string(); let constness = header.constness.print_with_space(); let asyncness = header.asyncness.print_with_space(); let unsafety = header.unsafety.print_with_space(); let defaultness = print_default_space(meth.is_default()); let abi = print_abi_with_space(header.abi).to_string(); // NOTE: `{:#}` does not print HTML formatting, `{}` does. So `g.print` can't be reused between the length calculation and `write!`. - let generics_len = format!("{:#}", g.print(cache, tcx)).len(); + let generics_len = format!("{:#}", g.print(cx)).len(); let mut header_len = "fn ".len() + vis.len() + constness.len() @@ -958,10 +951,10 @@ fn render_assoc_item( abi, href = href, name = name, - generics = g.print(cache, tcx), - decl = d.full_print(cache, tcx, header_len, indent, header.asyncness), - notable_traits = notable_traits_decl(&d, cache, tcx), - where_clause = print_where_clause(g, cache, tcx, indent, end_newline), + generics = g.print(cx), + decl = d.full_print(cx, header_len, indent, header.asyncness), + notable_traits = notable_traits_decl(&d, cx), + where_clause = print_where_clause(g, cx, indent, end_newline), ) } match *item.kind { @@ -988,8 +981,7 @@ fn render_assoc_item( default.as_ref(), link, if parent == ItemType::Trait { " " } else { "" }, - cx.cache(), - cx.tcx(), + cx, ), _ => panic!("render_assoc_item called on non-associated-item"), } @@ -1076,11 +1068,9 @@ fn render_assoc_items( RenderMode::Normal } AssocItemRender::DerefFor { trait_, type_, deref_mut_ } => { - let id = cx.derive_id(small_url_encode(format!( - "deref-methods-{:#}", - type_.print(cache, tcx) - ))); - debug!("Adding {} to deref id map", type_.print(cache, tcx)); + let id = + cx.derive_id(small_url_encode(format!("deref-methods-{:#}", type_.print(cx)))); + debug!("Adding {} to deref id map", type_.print(cx)); cx.deref_id_map.borrow_mut().insert(type_.def_id_full(cache).unwrap(), id.clone()); write!( w, @@ -1089,8 +1079,8 @@ fn render_assoc_items( <a href=\"#{id}\" class=\"anchor\"></a>\ </h2>", id = id, - trait_ = trait_.print(cache, tcx), - type_ = type_.print(cache, tcx), + trait_ = trait_.print(cx), + type_ = type_.print(cx), ); RenderMode::ForDeref { mut_: deref_mut_ } } @@ -1242,36 +1232,34 @@ fn should_render_item(item: &clean::Item, deref_mut_: bool, cache: &Cache) -> bo } } -fn notable_traits_decl(decl: &clean::FnDecl, cache: &Cache, tcx: TyCtxt<'_>) -> String { +fn notable_traits_decl(decl: &clean::FnDecl, cx: &Context<'_>) -> String { let mut out = Buffer::html(); let mut trait_ = String::new(); - if let Some(did) = decl.output.def_id_full(cache) { - if let Some(impls) = cache.impls.get(&did) { + if let Some(did) = decl.output.def_id_full(cx.cache()) { + if let Some(impls) = cx.cache().impls.get(&did) { for i in impls { let impl_ = i.inner_impl(); - if impl_ - .trait_ - .def_id() - .map_or(false, |d| cache.traits.get(&d).map(|t| t.is_notable).unwrap_or(false)) - { + if impl_.trait_.def_id().map_or(false, |d| { + cx.cache().traits.get(&d).map(|t| t.is_notable).unwrap_or(false) + }) { if out.is_empty() { write!( &mut out, "<h3 class=\"notable\">Notable traits for {}</h3>\ <code class=\"content\">", - impl_.for_.print(cache, tcx) + impl_.for_.print(cx) ); - trait_.push_str(&impl_.for_.print(cache, tcx).to_string()); + trait_.push_str(&impl_.for_.print(cx).to_string()); } //use the "where" class here to make it small write!( &mut out, "<span class=\"where fmt-newline\">{}</span>", - impl_.print(cache, false, tcx) + impl_.print(false, cx) ); - let t_did = impl_.trait_.def_id_full(cache).unwrap(); + let t_did = impl_.trait_.def_id_full(cx.cache()).unwrap(); for it in &impl_.items { if let clean::TypedefItem(ref tydef, _) = *it.kind { out.push_str("<span class=\"where fmt-newline\"> "); @@ -1282,8 +1270,7 @@ fn notable_traits_decl(decl: &clean::FnDecl, cache: &Cache, tcx: TyCtxt<'_>) -> Some(&tydef.type_), AssocItemLink::GotoSource(t_did, &FxHashSet::default()), "", - cache, - tcx, + cx, ); out.push_str(";</span>"); } @@ -1322,18 +1309,18 @@ fn render_impl( // in documentation pages for trait with automatic implementations like "Send" and "Sync". aliases: &[String], ) { - let traits = &cx.cache.traits; let tcx = cx.tcx(); let cache = cx.cache(); + let traits = &cache.traits; let trait_ = i.trait_did_full(cache).map(|did| &traits[&did]); if render_mode == RenderMode::Normal { let id = cx.derive_id(match i.inner_impl().trait_ { Some(ref t) => { if is_on_foreign_type { - get_id_for_impl_on_foreign_type(&i.inner_impl().for_, t, cache, tcx) + get_id_for_impl_on_foreign_type(&i.inner_impl().for_, t, cx) } else { - format!("impl-{}", small_url_encode(format!("{:#}", t.print(cache, tcx)))) + format!("impl-{}", small_url_encode(format!("{:#}", t.print(cx)))) } } None => "impl".to_string(), @@ -1345,7 +1332,7 @@ fn render_impl( }; if let Some(use_absolute) = use_absolute { write!(w, "<h3 id=\"{}\" class=\"impl\"{}><code class=\"in-band\">", id, aliases); - write!(w, "{}", i.inner_impl().print(cache, use_absolute, tcx)); + write!(w, "{}", i.inner_impl().print(use_absolute, cx)); if show_def_docs { for it in &i.inner_impl().items { if let clean::TypedefItem(ref tydef, _) = *it.kind { @@ -1357,8 +1344,7 @@ fn render_impl( Some(&tydef.type_), AssocItemLink::Anchor(None), "", - cache, - tcx, + cx, ); w.write_str(";</span>"); } @@ -1371,7 +1357,7 @@ fn render_impl( "<h3 id=\"{}\" class=\"impl\"{}><code class=\"in-band\">{}</code>", id, aliases, - i.inner_impl().print(cache, false, tcx) + i.inner_impl().print(false, cx) ); } write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id); @@ -1398,7 +1384,7 @@ fn render_impl( "<div class=\"docblock\">{}</div>", Markdown( &*dox, - &i.impl_item.links(&cx.cache), + &i.impl_item.links(cx), &mut ids, cx.shared.codes, cx.shared.edition, @@ -1495,8 +1481,7 @@ fn render_impl( Some(&tydef.type_), link.anchor(if trait_.is_some() { &source_id } else { &id }), "", - cx.cache(), - tcx, + cx, ); w.write_str("</code>"); write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id); @@ -1546,8 +1531,7 @@ fn render_impl( default.as_ref(), link.anchor(if trait_.is_some() { &source_id } else { &id }), "", - cx.cache(), - tcx, + cx, ); w.write_str("</code>"); write!(w, "<a href=\"#{}\" class=\"anchor\"></a>", id); @@ -1853,7 +1837,6 @@ fn small_url_encode(s: String) -> String { fn sidebar_assoc_items(cx: &Context<'_>, out: &mut Buffer, it: &clean::Item) { if let Some(v) = cx.cache.impls.get(&it.def_id) { let mut used_links = FxHashSet::default(); - let tcx = cx.tcx(); let cache = cx.cache(); { @@ -1888,9 +1871,9 @@ fn sidebar_assoc_items(cx: &Context<'_>, out: &mut Buffer, it: &clean::Item) { .iter() .filter_map(|it| { if let Some(ref i) = it.inner_impl().trait_ { - let i_display = format!("{:#}", i.print(cache, tcx)); + let i_display = format!("{:#}", i.print(cx)); let out = Escape(&i_display); - let encoded = small_url_encode(format!("{:#}", i.print(cache, tcx))); + let encoded = small_url_encode(format!("{:#}", i.print(cx))); let generated = format!( "<a href=\"#impl-{}\">{}{}</a>", encoded, @@ -1962,7 +1945,6 @@ fn sidebar_assoc_items(cx: &Context<'_>, out: &mut Buffer, it: &clean::Item) { fn sidebar_deref_methods(cx: &Context<'_>, out: &mut Buffer, impl_: &Impl, v: &Vec<Impl>) { let c = cx.cache(); - let tcx = cx.tcx(); debug!("found Deref: {:?}", impl_); if let Some((target, real_target)) = @@ -2011,11 +1993,8 @@ fn sidebar_deref_methods(cx: &Context<'_>, out: &mut Buffer, impl_: &Impl, v: &V out, "<a class=\"sidebar-title\" href=\"#{}\">Methods from {}<Target={}></a>", id, - Escape(&format!( - "{:#}", - impl_.inner_impl().trait_.as_ref().unwrap().print(c, tcx) - )), - Escape(&format!("{:#}", real_target.print(c, tcx))), + Escape(&format!("{:#}", impl_.inner_impl().trait_.as_ref().unwrap().print(cx))), + Escape(&format!("{:#}", real_target.print(cx))), ); // We want links' order to be reproducible so we don't use unstable sort. ret.sort(); @@ -2071,27 +2050,20 @@ fn sidebar_struct(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, s: &clea fn get_id_for_impl_on_foreign_type( for_: &clean::Type, trait_: &clean::Type, - cache: &Cache, - tcx: TyCtxt<'_>, + cx: &Context<'_>, ) -> String { - small_url_encode(format!( - "impl-{:#}-for-{:#}", - trait_.print(cache, tcx), - for_.print(cache, tcx) - )) + small_url_encode(format!("impl-{:#}-for-{:#}", trait_.print(cx), for_.print(cx),)) } -fn extract_for_impl_name( - item: &clean::Item, - cache: &Cache, - tcx: TyCtxt<'_>, -) -> Option<(String, String)> { +fn extract_for_impl_name(item: &clean::Item, cx: &Context<'_>) -> Option<(String, String)> { match *item.kind { clean::ItemKind::ImplItem(ref i) => { if let Some(ref trait_) = i.trait_ { + // Alternative format produces no URLs, + // so this parameter does nothing. Some(( - format!("{:#}", i.for_.print(cache, tcx)), - get_id_for_impl_on_foreign_type(&i.for_, trait_, cache, tcx), + format!("{:#}", i.for_.print(cx)), + get_id_for_impl_on_foreign_type(&i.for_, trait_, cx), )) } else { None @@ -2172,7 +2144,6 @@ fn sidebar_trait(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, t: &clean if let Some(implementors) = cx.cache.implementors.get(&it.def_id) { let cache = cx.cache(); - let tcx = cx.tcx(); let mut res = implementors .iter() .filter(|i| { @@ -2181,7 +2152,7 @@ fn sidebar_trait(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, t: &clean .def_id_full(cache) .map_or(false, |d| !cx.cache.paths.contains_key(&d)) }) - .filter_map(|i| extract_for_impl_name(&i.impl_item, cache, tcx)) + .filter_map(|i| extract_for_impl_name(&i.impl_item, cx)) .collect::<Vec<_>>(); if !res.is_empty() { diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index ff9e4d031da18..3b939a255a4e7 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -15,9 +15,8 @@ use super::{ render_impl, render_stability_since_raw, write_srclink, AssocItemLink, Context, }; use crate::clean::{self, GetDefId}; -use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; -use crate::formats::{AssocItemRender, FormatRenderer, Impl, RenderMode}; +use crate::formats::{AssocItemRender, Impl, RenderMode}; use crate::html::escape::Escape; use crate::html::format::{print_abi_with_space, print_where_clause, Buffer, PrintWithSpace}; use crate::html::highlight; @@ -268,15 +267,15 @@ fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[cl Some(ref src) => write!( w, "<tr><td><code>{}extern crate {} as {};", - myitem.visibility.print_with_space(cx.tcx(), myitem.def_id, cx.cache()), - anchor(myitem.def_id, &*src.as_str(), cx.cache()), + myitem.visibility.print_with_space(cx, myitem.def_id), + anchor(myitem.def_id, &*src.as_str(), cx), myitem.name.as_ref().unwrap(), ), None => write!( w, "<tr><td><code>{}extern crate {};", - myitem.visibility.print_with_space(cx.tcx(), myitem.def_id, cx.cache()), - anchor(myitem.def_id, &*myitem.name.as_ref().unwrap().as_str(), cx.cache()), + myitem.visibility.print_with_space(cx, myitem.def_id), + anchor(myitem.def_id, &*myitem.name.as_ref().unwrap().as_str(), cx), ), } w.write_str("</code></td></tr>"); @@ -286,8 +285,8 @@ fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[cl write!( w, "<tr><td><code>{}{}</code></td></tr>", - myitem.visibility.print_with_space(cx.tcx(), myitem.def_id, cx.cache()), - import.print(cx.cache(), cx.tcx()), + myitem.visibility.print_with_space(cx, myitem.def_id), + import.print(cx), ); } @@ -318,7 +317,7 @@ fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[cl </tr>", name = *myitem.name.as_ref().unwrap(), stab_tags = extra_info_tags(myitem, item, cx.tcx()), - docs = MarkdownSummaryLine(&doc_value, &myitem.links(&cx.cache)).into_string(), + docs = MarkdownSummaryLine(&doc_value, &myitem.links(cx)).into_string(), class = myitem.type_(), add = add, stab = stab.unwrap_or_else(String::new), @@ -387,13 +386,13 @@ fn extra_info_tags(item: &clean::Item, parent: &clean::Item, tcx: TyCtxt<'_>) -> fn item_function(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, f: &clean::Function) { let header_len = format!( "{}{}{}{}{:#}fn {}{:#}", - it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), + it.visibility.print_with_space(cx, it.def_id), f.header.constness.print_with_space(), f.header.asyncness.print_with_space(), f.header.unsafety.print_with_space(), print_abi_with_space(f.header.abi), it.name.as_ref().unwrap(), - f.generics.print(cx.cache(), cx.tcx()) + f.generics.print(cx), ) .len(); w.write_str("<pre class=\"rust fn\">"); @@ -402,22 +401,22 @@ fn item_function(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, f: &clean:: w, "{vis}{constness}{asyncness}{unsafety}{abi}fn \ {name}{generics}{decl}{notable_traits}{where_clause}</pre>", - vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), + vis = it.visibility.print_with_space(cx, it.def_id), constness = f.header.constness.print_with_space(), asyncness = f.header.asyncness.print_with_space(), unsafety = f.header.unsafety.print_with_space(), abi = print_abi_with_space(f.header.abi), name = it.name.as_ref().unwrap(), - generics = f.generics.print(cx.cache(), cx.tcx()), - where_clause = print_where_clause(&f.generics, cx.cache(), cx.tcx(), 0, true), - decl = f.decl.full_print(cx.cache(), cx.tcx(), header_len, 0, f.header.asyncness), - notable_traits = notable_traits_decl(&f.decl, cx.cache(), cx.tcx()), + generics = f.generics.print(cx), + where_clause = print_where_clause(&f.generics, cx, 0, true), + decl = f.decl.full_print(cx, header_len, 0, f.header.asyncness), + notable_traits = notable_traits_decl(&f.decl, cx), ); document(w, cx, it, None) } fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) { - let bounds = bounds(&t.bounds, false, cx.cache(), cx.tcx()); + let bounds = bounds(&t.bounds, false, cx); let types = t.items.iter().filter(|m| m.is_associated_type()).collect::<Vec<_>>(); let consts = t.items.iter().filter(|m| m.is_associated_const()).collect::<Vec<_>>(); let required = t.items.iter().filter(|m| m.is_ty_method()).collect::<Vec<_>>(); @@ -430,16 +429,16 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra write!( w, "{}{}{}trait {}{}{}", - it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), + it.visibility.print_with_space(cx, it.def_id), t.unsafety.print_with_space(), if t.is_auto { "auto " } else { "" }, it.name.as_ref().unwrap(), - t.generics.print(cx.cache(), cx.tcx()), + t.generics.print(cx), bounds ); if !t.generics.where_predicates.is_empty() { - write!(w, "{}", print_where_clause(&t.generics, cx.cache(), cx.tcx(), 0, true)); + write!(w, "{}", print_where_clause(&t.generics, cx, 0, true)); } else { w.write_str(" "); } @@ -634,8 +633,8 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra let (mut synthetic, mut concrete): (Vec<&&Impl>, Vec<&&Impl>) = local.iter().partition(|i| i.inner_impl().synthetic); - synthetic.sort_by(|a, b| compare_impl(a, b, cx.cache(), cx.tcx())); - concrete.sort_by(|a, b| compare_impl(a, b, cx.cache(), cx.tcx())); + synthetic.sort_by(|a, b| compare_impl(a, b, cx)); + concrete.sort_by(|a, b| compare_impl(a, b, cx)); if !foreign.is_empty() { write_small_section_header(w, "foreign-impls", "Implementations on Foreign Types", ""); @@ -740,9 +739,9 @@ fn item_trait_alias(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clea w, "trait {}{}{} = {};</pre>", it.name.as_ref().unwrap(), - t.generics.print(cx.cache(), cx.tcx()), - print_where_clause(&t.generics, cx.cache(), cx.tcx(), 0, true), - bounds(&t.bounds, true, cx.cache(), cx.tcx()) + t.generics.print(cx), + print_where_clause(&t.generics, cx, 0, true), + bounds(&t.bounds, true, cx) ); document(w, cx, it, None); @@ -761,9 +760,9 @@ fn item_opaque_ty(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean: w, "type {}{}{where_clause} = impl {bounds};</pre>", it.name.as_ref().unwrap(), - t.generics.print(cx.cache(), cx.tcx()), - where_clause = print_where_clause(&t.generics, cx.cache(), cx.tcx(), 0, true), - bounds = bounds(&t.bounds, false, cx.cache(), cx.tcx()), + t.generics.print(cx), + where_clause = print_where_clause(&t.generics, cx, 0, true), + bounds = bounds(&t.bounds, false, cx), ); document(w, cx, it, None); @@ -782,9 +781,9 @@ fn item_typedef(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::T w, "type {}{}{where_clause} = {type_};</pre>", it.name.as_ref().unwrap(), - t.generics.print(cx.cache(), cx.tcx()), - where_clause = print_where_clause(&t.generics, cx.cache(), cx.tcx(), 0, true), - type_ = t.type_.print(cx.cache(), cx.tcx()), + t.generics.print(cx), + where_clause = print_where_clause(&t.generics, cx, 0, true), + type_ = t.type_.print(cx), ); document(w, cx, it, None); @@ -831,7 +830,7 @@ fn item_union(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Uni id = id, name = name, shortty = ItemType::StructField, - ty = ty.print(cx.cache(), cx.tcx()), + ty = ty.print(cx), ); if let Some(stability_class) = field.stability_class(cx.tcx()) { write!(w, "<span class=\"stab {stab}\"></span>", stab = stability_class); @@ -849,10 +848,10 @@ fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum write!( w, "{}enum {}{}{}", - it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), + it.visibility.print_with_space(cx, it.def_id), it.name.as_ref().unwrap(), - e.generics.print(cx.cache(), cx.tcx()), - print_where_clause(&e.generics, cx.cache(), cx.tcx(), 0, true), + e.generics.print(cx), + print_where_clause(&e.generics, cx, 0, true), ); if e.variants.is_empty() && !e.variants_stripped { w.write_str(" {}"); @@ -874,7 +873,7 @@ fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum if i > 0 { w.write_str(", ") } - write!(w, "{}", ty.print(cx.cache(), cx.tcx())); + write!(w, "{}", ty.print(cx)); } w.write_str(")"); } @@ -924,7 +923,7 @@ fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum if i > 0 { w.write_str(", "); } - write!(w, "{}", ty.print(cx.cache(), cx.tcx())); + write!(w, "{}", ty.print(cx)); } w.write_str(")"); } @@ -961,7 +960,7 @@ fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum </span>", id = id, f = field.name.as_ref().unwrap(), - t = ty.print(cx.cache(), cx.tcx()) + t = ty.print(cx) ); document(w, cx, field, Some(variant)); } @@ -1030,9 +1029,9 @@ fn item_constant(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, c: &clean:: write!( w, "{vis}const {name}: {typ}", - vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), + vis = it.visibility.print_with_space(cx, it.def_id), name = it.name.as_ref().unwrap(), - typ = c.type_.print(cx.cache(), cx.tcx()), + typ = c.type_.print(cx), ); let value = c.value(cx.tcx()); @@ -1102,7 +1101,7 @@ fn item_struct(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::St item_type = ItemType::StructField, id = id, name = field.name.as_ref().unwrap(), - ty = ty.print(cx.cache(), cx.tcx()) + ty = ty.print(cx) ); document(w, cx, field, Some(it)); } @@ -1117,10 +1116,10 @@ fn item_static(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::St write!( w, "{vis}static {mutability}{name}: {typ}</pre>", - vis = it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), + vis = it.visibility.print_with_space(cx, it.def_id), mutability = s.mutability.print_with_space(), name = it.name.as_ref().unwrap(), - typ = s.type_.print(cx.cache(), cx.tcx()) + typ = s.type_.print(cx) ); document(w, cx, it, None) } @@ -1131,7 +1130,7 @@ fn item_foreign_type(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) { write!( w, " {}type {};\n}}</pre>", - it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), + it.visibility.print_with_space(cx, it.def_id), it.name.as_ref().unwrap(), ); @@ -1195,12 +1194,7 @@ pub(super) fn item_path(ty: ItemType, name: &str) -> String { } } -fn bounds( - t_bounds: &[clean::GenericBound], - trait_alias: bool, - cache: &Cache, - tcx: TyCtxt<'_>, -) -> String { +fn bounds(t_bounds: &[clean::GenericBound], trait_alias: bool, cx: &Context<'_>) -> String { let mut bounds = String::new(); if !t_bounds.is_empty() { if !trait_alias { @@ -1210,7 +1204,7 @@ fn bounds( if i > 0 { bounds.push_str(" + "); } - bounds.push_str(&p.print(cache, tcx).to_string()); + bounds.push_str(&p.print(cx).to_string()); } } bounds @@ -1240,17 +1234,12 @@ fn render_stability_since( ) } -fn compare_impl<'a, 'b>( - lhs: &'a &&Impl, - rhs: &'b &&Impl, - cache: &Cache, - tcx: TyCtxt<'_>, -) -> Ordering { - let lhs = format!("{}", lhs.inner_impl().print(cache, false, tcx)); - let rhs = format!("{}", rhs.inner_impl().print(cache, false, tcx)); +fn compare_impl<'a, 'b>(lhs: &'a &&Impl, rhs: &'b &&Impl, cx: &Context<'_>) -> Ordering { + let lhss = format!("{}", lhs.inner_impl().print(false, cx)); + let rhss = format!("{}", rhs.inner_impl().print(false, cx)); // lhs and rhs are formatted as HTML, which may be unnecessary - compare_names(&lhs, &rhs) + compare_names(&lhss, &rhss) } fn render_implementor( @@ -1300,13 +1289,13 @@ fn render_union( write!( w, "{}{}{}", - it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), + it.visibility.print_with_space(cx, it.def_id), if structhead { "union " } else { "" }, it.name.as_ref().unwrap() ); if let Some(g) = g { - write!(w, "{}", g.print(cx.cache(), cx.tcx())); - write!(w, "{}", print_where_clause(&g, cx.cache(), cx.tcx(), 0, true)); + write!(w, "{}", g.print(cx)); + write!(w, "{}", print_where_clause(&g, cx, 0, true)); } write!(w, " {{\n{}", tab); @@ -1322,9 +1311,9 @@ fn render_union( write!( w, " {}{}: {},\n{}", - field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()), + field.visibility.print_with_space(cx, field.def_id), field.name.as_ref().unwrap(), - ty.print(cx.cache(), cx.tcx()), + ty.print(cx), tab ); } @@ -1352,17 +1341,17 @@ fn render_struct( write!( w, "{}{}{}", - it.visibility.print_with_space(cx.tcx(), it.def_id, cx.cache()), + it.visibility.print_with_space(cx, it.def_id), if structhead { "struct " } else { "" }, it.name.as_ref().unwrap() ); if let Some(g) = g { - write!(w, "{}", g.print(cx.cache(), cx.tcx())) + write!(w, "{}", g.print(cx)) } match ty { CtorKind::Fictive => { if let Some(g) = g { - write!(w, "{}", print_where_clause(g, cx.cache(), cx.tcx(), 0, true),) + write!(w, "{}", print_where_clause(g, cx, 0, true),) } w.write_str(" {"); let count_fields = @@ -1378,9 +1367,9 @@ fn render_struct( w, "\n{} {}{}: {},", tab, - field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()), + field.visibility.print_with_space(cx, field.def_id), field.name.as_ref().unwrap(), - ty.print(cx.cache(), cx.tcx()), + ty.print(cx), ); } } @@ -1412,8 +1401,8 @@ fn render_struct( write!( w, "{}{}", - field.visibility.print_with_space(cx.tcx(), field.def_id, cx.cache()), - ty.print(cx.cache(), cx.tcx()), + field.visibility.print_with_space(cx, field.def_id), + ty.print(cx), ) } _ => unreachable!(), @@ -1421,14 +1410,14 @@ fn render_struct( } w.write_str(")"); if let Some(g) = g { - write!(w, "{}", print_where_clause(g, cx.cache(), cx.tcx(), 0, false),) + write!(w, "{}", print_where_clause(g, cx, 0, false),) } w.write_str(";"); } CtorKind::Const => { // Needed for PhantomData. if let Some(g) = g { - write!(w, "{}", print_where_clause(g, cx.cache(), cx.tcx(), 0, false),) + write!(w, "{}", print_where_clause(g, cx, 0, false),) } w.write_str(";"); } diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 8fb6d68f3c6bc..90e56d00a1132 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -16,7 +16,6 @@ use crate::clean::Crate; use crate::config::{EmitType, RenderOptions}; use crate::docfs::PathError; use crate::error::Error; -use crate::formats::FormatRenderer; use crate::html::{layout, static_files}; crate static FILES_UNVERSIONED: Lazy<FxHashMap<&str, &[u8]>> = Lazy::new(|| { @@ -500,7 +499,7 @@ pub(super) fn write_shared( None } else { Some(Implementor { - text: imp.inner_impl().print(cx.cache(), false, cx.tcx()).to_string(), + text: imp.inner_impl().print(false, cx).to_string(), synthetic: imp.inner_impl().synthetic, types: collect_paths_for_type(imp.inner_impl().for_.clone(), cx.cache()), }) diff --git a/src/librustdoc/html/tests.rs b/src/librustdoc/html/tests.rs new file mode 100644 index 0000000000000..5d537dabd0c07 --- /dev/null +++ b/src/librustdoc/html/tests.rs @@ -0,0 +1,44 @@ +use crate::html::format::href_relative_parts; + +fn assert_relative_path(expected: &[&str], relative_to_fqp: &[&str], fqp: &[&str]) { + let relative_to_fqp: Vec<String> = relative_to_fqp.iter().copied().map(String::from).collect(); + let fqp: Vec<String> = fqp.iter().copied().map(String::from).collect(); + assert_eq!(expected, href_relative_parts(&fqp, &relative_to_fqp)); +} + +#[test] +fn href_relative_parts_basic() { + let relative_to_fqp = &["std", "vec"]; + let fqp = &["std", "iter"]; + assert_relative_path(&["..", "iter"], relative_to_fqp, fqp); +} +#[test] +fn href_relative_parts_parent_module() { + let relative_to_fqp = &["std", "vec"]; + let fqp = &["std"]; + assert_relative_path(&[".."], relative_to_fqp, fqp); +} +#[test] +fn href_relative_parts_different_crate() { + let relative_to_fqp = &["std", "vec"]; + let fqp = &["core", "iter"]; + assert_relative_path(&["..", "..", "core", "iter"], relative_to_fqp, fqp); +} +#[test] +fn href_relative_parts_same_module() { + let relative_to_fqp = &["std", "vec"]; + let fqp = &["std", "vec"]; + assert_relative_path(&[], relative_to_fqp, fqp); +} +#[test] +fn href_relative_parts_child_module() { + let relative_to_fqp = &["std"]; + let fqp = &["std", "vec"]; + assert_relative_path(&["vec"], relative_to_fqp, fqp); +} +#[test] +fn href_relative_parts_root() { + let relative_to_fqp = &[]; + let fqp = &["std"]; + assert_relative_path(&["std"], relative_to_fqp, fqp); +} diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index b9c4bbdceb276..2a51d78f64a39 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -289,7 +289,13 @@ fn opts() -> Vec<RustcOptGroup> { stable("cfg", |o| o.optmulti("", "cfg", "pass a --cfg to rustc", "")), stable("extern", |o| o.optmulti("", "extern", "pass an --extern to rustc", "NAME[=PATH]")), unstable("extern-html-root-url", |o| { - o.optmulti("", "extern-html-root-url", "base URL to use for dependencies", "NAME=URL") + o.optmulti( + "", + "extern-html-root-url", + "base URL to use for dependencies; for example, \ + \"std=/doc\" links std::vec::Vec to /doc/std/vec/struct.Vec.html", + "NAME=URL", + ) }), stable("plugin-path", |o| o.optmulti("", "plugin-path", "removed", "DIR")), stable("C", |o| { diff --git a/src/test/rustdoc/assoc-types.rs b/src/test/rustdoc/assoc-types.rs index 82fa7cf9e6057..8fda171002ba0 100644 --- a/src/test/rustdoc/assoc-types.rs +++ b/src/test/rustdoc/assoc-types.rs @@ -6,14 +6,14 @@ pub trait Index<I: ?Sized> { type Output: ?Sized; // @has - '//*[@id="tymethod.index"]//code' \ // "fn index<'a>(&'a self, index: I) -> &'a Self::Output" - // @has - '//*[@id="tymethod.index"]//code//a[@href="../assoc_types/trait.Index.html#associatedtype.Output"]' \ + // @has - '//*[@id="tymethod.index"]//code//a[@href="trait.Index.html#associatedtype.Output"]' \ // "Output" fn index<'a>(&'a self, index: I) -> &'a Self::Output; } // @has assoc_types/fn.use_output.html // @has - '//*[@class="rust fn"]' '-> &T::Output' -// @has - '//*[@class="rust fn"]//a[@href="../assoc_types/trait.Index.html#associatedtype.Output"]' 'Output' +// @has - '//*[@class="rust fn"]//a[@href="trait.Index.html#associatedtype.Output"]' 'Output' pub fn use_output<T: Index<usize>>(obj: &T, index: usize) -> &T::Output { obj.index(index) } @@ -24,12 +24,12 @@ pub trait Feed { // @has assoc_types/fn.use_input.html // @has - '//*[@class="rust fn"]' 'T::Input' -// @has - '//*[@class="rust fn"]//a[@href="../assoc_types/trait.Feed.html#associatedtype.Input"]' 'Input' +// @has - '//*[@class="rust fn"]//a[@href="trait.Feed.html#associatedtype.Input"]' 'Input' pub fn use_input<T: Feed>(_feed: &T, _element: T::Input) { } // @has assoc_types/fn.cmp_input.html // @has - '//*[@class="rust fn"]' 'where T::Input: PartialEq<U::Input>' -// @has - '//*[@class="rust fn"]//a[@href="../assoc_types/trait.Feed.html#associatedtype.Input"]' 'Input' +// @has - '//*[@class="rust fn"]//a[@href="trait.Feed.html#associatedtype.Input"]' 'Input' pub fn cmp_input<T: Feed, U: Feed>(a: &T::Input, b: &U::Input) -> bool where T::Input: PartialEq<U::Input> { diff --git a/src/test/rustdoc/auxiliary/primitive-doc.rs b/src/test/rustdoc/auxiliary/primitive-doc.rs new file mode 100644 index 0000000000000..a5b69740dd447 --- /dev/null +++ b/src/test/rustdoc/auxiliary/primitive-doc.rs @@ -0,0 +1,6 @@ +// compile-flags: --crate-type lib --edition 2018 + +#[doc(primitive = "usize")] +/// This is the built-in type `usize`. +mod usize { +} diff --git a/src/test/rustdoc/check-styled-link.rs b/src/test/rustdoc/check-styled-link.rs index b479820dfb2ec..ed4a5ea21374b 100644 --- a/src/test/rustdoc/check-styled-link.rs +++ b/src/test/rustdoc/check-styled-link.rs @@ -2,7 +2,7 @@ pub struct Foo; -// @has foo/struct.Bar.html '//a[@href="../foo/struct.Foo.html"]' 'Foo' +// @has foo/struct.Bar.html '//a[@href="struct.Foo.html"]' 'Foo' /// Code-styled reference to [`Foo`]. pub struct Bar; diff --git a/src/test/rustdoc/cross-crate-primitive-doc.rs b/src/test/rustdoc/cross-crate-primitive-doc.rs new file mode 100644 index 0000000000000..05376e4680ec4 --- /dev/null +++ b/src/test/rustdoc/cross-crate-primitive-doc.rs @@ -0,0 +1,9 @@ +// aux-build:primitive-doc.rs +// compile-flags: --extern-html-root-url=primitive_doc=../ -Z unstable-options + +#![no_std] + +extern crate primitive_doc; + +// @has 'cross_crate_primitive_doc/fn.foo.html' '//a[@href="../primitive_doc/primitive.usize.html"]' 'usize' +pub fn foo() -> usize { 0 } diff --git a/src/test/rustdoc/default-trait-method-link.rs b/src/test/rustdoc/default-trait-method-link.rs index e4f0bdab16292..7bcd2a3c149e9 100644 --- a/src/test/rustdoc/default-trait-method-link.rs +++ b/src/test/rustdoc/default-trait-method-link.rs @@ -1,7 +1,7 @@ #![crate_name = "foo"] -// @has foo/trait.Foo.html '//a[@href="../foo/trait.Foo.html#tymethod.req"]' 'req' -// @has foo/trait.Foo.html '//a[@href="../foo/trait.Foo.html#method.prov"]' 'prov' +// @has foo/trait.Foo.html '//a[@href="trait.Foo.html#tymethod.req"]' 'req' +// @has foo/trait.Foo.html '//a[@href="trait.Foo.html#method.prov"]' 'prov' /// Always make sure to implement [`req`], but you don't have to implement [`prov`]. /// diff --git a/src/test/rustdoc/intra-doc-crate/self.rs b/src/test/rustdoc/intra-doc-crate/self.rs index 4db63b12b6bb7..8c36a7fa002d8 100644 --- a/src/test/rustdoc/intra-doc-crate/self.rs +++ b/src/test/rustdoc/intra-doc-crate/self.rs @@ -3,7 +3,7 @@ extern crate cross_crate_self; -// @has self/struct.S.html '//a[@href="../self/struct.S.html#method.f"]' "Self::f" -// @has self/struct.S.html '//a[@href="../self/struct.S.html"]' "Self" +// @has self/struct.S.html '//a[@href="struct.S.html#method.f"]' "Self::f" +// @has self/struct.S.html '//a[@href="struct.S.html"]' "Self" // @has self/struct.S.html '//a[@href="../cross_crate_self/index.html"]' "crate" pub use cross_crate_self::S; diff --git a/src/test/rustdoc/intra-doc/anchors.rs b/src/test/rustdoc/intra-doc/anchors.rs index e4f0c737bdd33..8ec1a7b4f9056 100644 --- a/src/test/rustdoc/intra-doc/anchors.rs +++ b/src/test/rustdoc/intra-doc/anchors.rs @@ -4,7 +4,7 @@ pub struct Something; // @has anchors/struct.SomeOtherType.html -// @has - '//a/@href' '../anchors/struct.Something.html#Anchor!' +// @has - '//a/@href' 'struct.Something.html#Anchor!' /// I want... /// diff --git a/src/test/rustdoc/intra-doc/associated-defaults.rs b/src/test/rustdoc/intra-doc/associated-defaults.rs index 28dc7073a3ecc..68647127fe880 100644 --- a/src/test/rustdoc/intra-doc/associated-defaults.rs +++ b/src/test/rustdoc/intra-doc/associated-defaults.rs @@ -9,14 +9,14 @@ pub trait TraitWithDefault { } /// Link to [UsesDefaults::T] and [UsesDefaults::f] -// @has 'associated_defaults/struct.UsesDefaults.html' '//a[@href="../associated_defaults/struct.UsesDefaults.html#associatedtype.T"]' 'UsesDefaults::T' -// @has 'associated_defaults/struct.UsesDefaults.html' '//a[@href="../associated_defaults/struct.UsesDefaults.html#method.f"]' 'UsesDefaults::f' +// @has 'associated_defaults/struct.UsesDefaults.html' '//a[@href="struct.UsesDefaults.html#associatedtype.T"]' 'UsesDefaults::T' +// @has 'associated_defaults/struct.UsesDefaults.html' '//a[@href="struct.UsesDefaults.html#method.f"]' 'UsesDefaults::f' pub struct UsesDefaults; impl TraitWithDefault for UsesDefaults {} /// Link to [OverridesDefaults::T] and [OverridesDefaults::f] -// @has 'associated_defaults/struct.OverridesDefaults.html' '//a[@href="../associated_defaults/struct.OverridesDefaults.html#associatedtype.T"]' 'OverridesDefaults::T' -// @has 'associated_defaults/struct.OverridesDefaults.html' '//a[@href="../associated_defaults/struct.OverridesDefaults.html#method.f"]' 'OverridesDefaults::f' +// @has 'associated_defaults/struct.OverridesDefaults.html' '//a[@href="struct.OverridesDefaults.html#associatedtype.T"]' 'OverridesDefaults::T' +// @has 'associated_defaults/struct.OverridesDefaults.html' '//a[@href="struct.OverridesDefaults.html#method.f"]' 'OverridesDefaults::f' pub struct OverridesDefaults; impl TraitWithDefault for OverridesDefaults { type T = bool; diff --git a/src/test/rustdoc/intra-doc/associated-items.rs b/src/test/rustdoc/intra-doc/associated-items.rs index 43a43a79738b3..2757418bc64e5 100644 --- a/src/test/rustdoc/intra-doc/associated-items.rs +++ b/src/test/rustdoc/intra-doc/associated-items.rs @@ -9,10 +9,10 @@ pub fn foo() {} /// Link to [MyStruct], [link from struct][MyStruct::method], [MyStruct::clone], [MyStruct::Input] -// @has 'associated_items/struct.MyStruct.html' '//a[@href="../associated_items/struct.MyStruct.html"]' 'MyStruct' -// @has 'associated_items/struct.MyStruct.html' '//a[@href="../associated_items/struct.MyStruct.html#method.method"]' 'link from struct' -// @has 'associated_items/struct.MyStruct.html' '//a[@href="../associated_items/struct.MyStruct.html#method.clone"]' 'MyStruct::clone' -// @has 'associated_items/struct.MyStruct.html' '//a[@href="../associated_items/struct.MyStruct.html#associatedtype.Input"]' 'MyStruct::Input' +// @has 'associated_items/struct.MyStruct.html' '//a[@href="struct.MyStruct.html"]' 'MyStruct' +// @has 'associated_items/struct.MyStruct.html' '//a[@href="struct.MyStruct.html#method.method"]' 'link from struct' +// @has 'associated_items/struct.MyStruct.html' '//a[@href="struct.MyStruct.html#method.clone"]' 'MyStruct::clone' +// @has 'associated_items/struct.MyStruct.html' '//a[@href="struct.MyStruct.html#associatedtype.Input"]' 'MyStruct::Input' pub struct MyStruct { foo: () } impl Clone for MyStruct { @@ -30,7 +30,7 @@ impl T for MyStruct { type Input = usize; /// [link from method][MyStruct::method] on method - // @has 'associated_items/struct.MyStruct.html' '//a[@href="../associated_items/struct.MyStruct.html#method.method"]' 'link from method' + // @has 'associated_items/struct.MyStruct.html' '//a[@href="struct.MyStruct.html#method.method"]' 'link from method' fn method(i: usize) { } } diff --git a/src/test/rustdoc/intra-doc/basic.rs b/src/test/rustdoc/intra-doc/basic.rs index 7760546e1fa1c..39f5c298bc4a1 100644 --- a/src/test/rustdoc/intra-doc/basic.rs +++ b/src/test/rustdoc/intra-doc/basic.rs @@ -1,21 +1,21 @@ // @has basic/index.html -// @has - '//a/@href' '../basic/struct.ThisType.html' -// @has - '//a/@href' '../basic/struct.ThisType.html#method.this_method' -// @has - '//a/@href' '../basic/enum.ThisEnum.html' -// @has - '//a/@href' '../basic/enum.ThisEnum.html#variant.ThisVariant' -// @has - '//a/@href' '../basic/trait.ThisTrait.html' -// @has - '//a/@href' '../basic/trait.ThisTrait.html#tymethod.this_associated_method' -// @has - '//a/@href' '../basic/trait.ThisTrait.html#associatedtype.ThisAssociatedType' -// @has - '//a/@href' '../basic/trait.ThisTrait.html#associatedconstant.THIS_ASSOCIATED_CONST' -// @has - '//a/@href' '../basic/trait.ThisTrait.html' -// @has - '//a/@href' '../basic/type.ThisAlias.html' -// @has - '//a/@href' '../basic/union.ThisUnion.html' -// @has - '//a/@href' '../basic/fn.this_function.html' -// @has - '//a/@href' '../basic/constant.THIS_CONST.html' -// @has - '//a/@href' '../basic/static.THIS_STATIC.html' -// @has - '//a/@href' '../basic/macro.this_macro.html' -// @has - '//a/@href' '../basic/trait.SoAmbiguous.html' -// @has - '//a/@href' '../basic/fn.SoAmbiguous.html' +// @has - '//a/@href' 'struct.ThisType.html' +// @has - '//a/@href' 'struct.ThisType.html#method.this_method' +// @has - '//a/@href' 'enum.ThisEnum.html' +// @has - '//a/@href' 'enum.ThisEnum.html#variant.ThisVariant' +// @has - '//a/@href' 'trait.ThisTrait.html' +// @has - '//a/@href' 'trait.ThisTrait.html#tymethod.this_associated_method' +// @has - '//a/@href' 'trait.ThisTrait.html#associatedtype.ThisAssociatedType' +// @has - '//a/@href' 'trait.ThisTrait.html#associatedconstant.THIS_ASSOCIATED_CONST' +// @has - '//a/@href' 'trait.ThisTrait.html' +// @has - '//a/@href' 'type.ThisAlias.html' +// @has - '//a/@href' 'union.ThisUnion.html' +// @has - '//a/@href' 'fn.this_function.html' +// @has - '//a/@href' 'constant.THIS_CONST.html' +// @has - '//a/@href' 'static.THIS_STATIC.html' +// @has - '//a/@href' 'macro.this_macro.html' +// @has - '//a/@href' 'trait.SoAmbiguous.html' +// @has - '//a/@href' 'fn.SoAmbiguous.html' //! In this crate we would like to link to: //! //! * [`ThisType`](ThisType) @@ -46,7 +46,7 @@ macro_rules! this_macro { () => {}; } -// @has basic/struct.ThisType.html '//a/@href' '../basic/macro.this_macro.html' +// @has basic/struct.ThisType.html '//a/@href' 'macro.this_macro.html' /// another link to [`this_macro!()`] pub struct ThisType; @@ -72,10 +72,10 @@ pub trait SoAmbiguous {} pub fn SoAmbiguous() {} -// @has basic/struct.SomeOtherType.html '//a/@href' '../basic/struct.ThisType.html' -// @has - '//a/@href' '../basic/struct.ThisType.html#method.this_method' -// @has - '//a/@href' '../basic/enum.ThisEnum.html' -// @has - '//a/@href' '../basic/enum.ThisEnum.html#variant.ThisVariant' +// @has basic/struct.SomeOtherType.html '//a/@href' 'struct.ThisType.html' +// @has - '//a/@href' 'struct.ThisType.html#method.this_method' +// @has - '//a/@href' 'enum.ThisEnum.html' +// @has - '//a/@href' 'enum.ThisEnum.html#variant.ThisVariant' /// Shortcut links for: /// * [`ThisType`] /// * [`ThisType::this_method`] diff --git a/src/test/rustdoc/intra-doc/cross-crate/additional_doc.rs b/src/test/rustdoc/intra-doc/cross-crate/additional_doc.rs index 837390b3c7161..85c5866ca7ecf 100644 --- a/src/test/rustdoc/intra-doc/cross-crate/additional_doc.rs +++ b/src/test/rustdoc/intra-doc/cross-crate/additional_doc.rs @@ -4,7 +4,7 @@ extern crate my_rand; -// @has 'additional_doc/trait.Rng.html' '//a[@href="../additional_doc/trait.Rng.html"]' 'Rng' +// @has 'additional_doc/trait.Rng.html' '//a[@href="trait.Rng.html"]' 'Rng' // @has 'additional_doc/trait.Rng.html' '//a[@href="../my_rand/trait.RngCore.html"]' 'RngCore' /// This is an [`Rng`]. pub use my_rand::Rng; diff --git a/src/test/rustdoc/intra-doc/cross-crate/hidden.rs b/src/test/rustdoc/intra-doc/cross-crate/hidden.rs index 9c9d4c649455e..31337f20f18dc 100644 --- a/src/test/rustdoc/intra-doc/cross-crate/hidden.rs +++ b/src/test/rustdoc/intra-doc/cross-crate/hidden.rs @@ -6,5 +6,5 @@ extern crate hidden_dep; -// @has 'hidden/struct.Ready.html' '//a/@href' '../hidden/fn.ready.html' +// @has 'hidden/struct.Ready.html' '//a/@href' 'fn.ready.html' pub use hidden_dep::future::{ready, Ready}; diff --git a/src/test/rustdoc/intra-doc/cross-crate/submodule-outer.rs b/src/test/rustdoc/intra-doc/cross-crate/submodule-outer.rs index 45f561328f279..db7952b5aced0 100644 --- a/src/test/rustdoc/intra-doc/cross-crate/submodule-outer.rs +++ b/src/test/rustdoc/intra-doc/cross-crate/submodule-outer.rs @@ -11,6 +11,6 @@ pub mod bar { // NOTE: we re-exported both `Foo` and `Bar` here, // NOTE: so they are inlined and therefore we link to the current module. -// @has 'submodule_outer/trait.Foo.html' '//a[@href="../submodule_outer/bar/trait.Bar.html"]' 'Bar' -// @has 'submodule_outer/trait.Foo.html' '//a[@href="../submodule_outer/trait.Baz.html"]' 'Baz' +// @has 'submodule_outer/trait.Foo.html' '//a[@href="bar/trait.Bar.html"]' 'Bar' +// @has 'submodule_outer/trait.Foo.html' '//a[@href="trait.Baz.html"]' 'Baz' pub use ::bar_::{Foo, Baz}; diff --git a/src/test/rustdoc/intra-doc/disambiguators-removed.rs b/src/test/rustdoc/intra-doc/disambiguators-removed.rs index 12c3cee29c3ac..d782c5cf5dc84 100644 --- a/src/test/rustdoc/intra-doc/disambiguators-removed.rs +++ b/src/test/rustdoc/intra-doc/disambiguators-removed.rs @@ -2,26 +2,26 @@ // first try backticks /// Trait: [`trait@Name`], fn: [`fn@Name`], [`Name`][`macro@Name`] // @has disambiguators_removed/struct.AtDisambiguator.html -// @has - '//a[@href="../disambiguators_removed/trait.Name.html"][code]' "Name" -// @has - '//a[@href="../disambiguators_removed/fn.Name.html"][code]' "Name" -// @has - '//a[@href="../disambiguators_removed/macro.Name.html"][code]' "Name" +// @has - '//a[@href="trait.Name.html"][code]' "Name" +// @has - '//a[@href="fn.Name.html"][code]' "Name" +// @has - '//a[@href="macro.Name.html"][code]' "Name" pub struct AtDisambiguator; /// fn: [`Name()`], macro: [`Name!`] // @has disambiguators_removed/struct.SymbolDisambiguator.html -// @has - '//a[@href="../disambiguators_removed/fn.Name.html"][code]' "Name()" -// @has - '//a[@href="../disambiguators_removed/macro.Name.html"][code]' "Name!" +// @has - '//a[@href="fn.Name.html"][code]' "Name()" +// @has - '//a[@href="macro.Name.html"][code]' "Name!" pub struct SymbolDisambiguator; // Now make sure that backticks aren't added if they weren't already there /// [fn@Name] // @has disambiguators_removed/trait.Name.html -// @has - '//a[@href="../disambiguators_removed/fn.Name.html"]' "Name" -// @!has - '//a[@href="../disambiguators_removed/fn.Name.html"][code]' "Name" +// @has - '//a[@href="fn.Name.html"]' "Name" +// @!has - '//a[@href="fn.Name.html"][code]' "Name" // FIXME: this will turn !() into ! alone /// [Name!()] -// @has - '//a[@href="../disambiguators_removed/macro.Name.html"]' "Name!" +// @has - '//a[@href="macro.Name.html"]' "Name!" pub trait Name {} #[allow(non_snake_case)] @@ -29,22 +29,22 @@ pub trait Name {} // Try collapsed reference links /// [macro@Name][] // @has disambiguators_removed/fn.Name.html -// @has - '//a[@href="../disambiguators_removed/macro.Name.html"]' "Name" +// @has - '//a[@href="macro.Name.html"]' "Name" // Try links that have the same text as a generated URL -/// Weird URL aligned [../disambiguators_removed/macro.Name.html][trait@Name] -// @has - '//a[@href="../disambiguators_removed/trait.Name.html"]' "../disambiguators_removed/macro.Name.html" +/// Weird URL aligned [macro.Name.html][trait@Name] +// @has - '//a[@href="trait.Name.html"]' "macro.Name.html" pub fn Name() {} #[macro_export] // Rustdoc doesn't currently handle links that have weird interspersing of inline code blocks. /// [fn@Na`m`e] // @has disambiguators_removed/macro.Name.html -// @has - '//a[@href="../disambiguators_removed/fn.Name.html"]' "fn@Name" +// @has - '//a[@href="fn.Name.html"]' "fn@Name" // It also doesn't handle any case where the code block isn't the whole link text: /// [trait@`Name`] -// @has - '//a[@href="../disambiguators_removed/trait.Name.html"]' "trait@Name" +// @has - '//a[@href="trait.Name.html"]' "trait@Name" macro_rules! Name { () => () } diff --git a/src/test/rustdoc/intra-doc/enum-struct-field.rs b/src/test/rustdoc/intra-doc/enum-struct-field.rs index 70bf343a9a5e5..2270a1fafa1cb 100644 --- a/src/test/rustdoc/intra-doc/enum-struct-field.rs +++ b/src/test/rustdoc/intra-doc/enum-struct-field.rs @@ -11,4 +11,4 @@ pub enum Foo { /// I want [Foo::X::y]. pub fn foo() {} -// @has foo/fn.foo.html '//a/@href' '../foo/enum.Foo.html#variant.X.field.y' +// @has foo/fn.foo.html '//a/@href' 'enum.Foo.html#variant.X.field.y' diff --git a/src/test/rustdoc/intra-doc/extern-type.rs b/src/test/rustdoc/intra-doc/extern-type.rs index e1934698d1fad..f37ae62dde1aa 100644 --- a/src/test/rustdoc/intra-doc/extern-type.rs +++ b/src/test/rustdoc/intra-doc/extern-type.rs @@ -12,6 +12,6 @@ impl ExternType { // @has 'extern_type/foreigntype.ExternType.html' // @has 'extern_type/fn.links_to_extern_type.html' \ -// 'href="../extern_type/foreigntype.ExternType.html#method.f"' +// 'href="foreigntype.ExternType.html#method.f"' /// See also [ExternType::f] pub fn links_to_extern_type() {} diff --git a/src/test/rustdoc/intra-doc/issue-82209.rs b/src/test/rustdoc/intra-doc/issue-82209.rs index 76618cdce4cd2..68a5672a8d209 100644 --- a/src/test/rustdoc/intra-doc/issue-82209.rs +++ b/src/test/rustdoc/intra-doc/issue-82209.rs @@ -8,4 +8,4 @@ pub enum Foo { }, } -// @has foo/enum.Foo.html '//a/@href' '../foo/enum.Foo.html#variant.Bar.field.abc' +// @has foo/enum.Foo.html '//a/@href' 'enum.Foo.html#variant.Bar.field.abc' diff --git a/src/test/rustdoc/intra-doc/mod-ambiguity.rs b/src/test/rustdoc/intra-doc/mod-ambiguity.rs index feb013b22be65..24b9dc30a9e49 100644 --- a/src/test/rustdoc/intra-doc/mod-ambiguity.rs +++ b/src/test/rustdoc/intra-doc/mod-ambiguity.rs @@ -6,11 +6,11 @@ pub fn foo() { } pub mod foo {} -// @has mod_ambiguity/struct.A.html '//a/@href' '../mod_ambiguity/foo/index.html' +// @has mod_ambiguity/struct.A.html '//a/@href' 'foo/index.html' /// Module is [`module@foo`] pub struct A; -// @has mod_ambiguity/struct.B.html '//a/@href' '../mod_ambiguity/fn.foo.html' +// @has mod_ambiguity/struct.B.html '//a/@href' 'fn.foo.html' /// Function is [`fn@foo`] pub struct B; diff --git a/src/test/rustdoc/intra-doc/prim-precedence.rs b/src/test/rustdoc/intra-doc/prim-precedence.rs index ab6e3da17f4ed..478b40b0b516f 100644 --- a/src/test/rustdoc/intra-doc/prim-precedence.rs +++ b/src/test/rustdoc/intra-doc/prim-precedence.rs @@ -11,6 +11,6 @@ pub mod char { pub struct MyString; /// See also [crate::char] and [mod@char] -// @has prim_precedence/struct.MyString2.html '//*[@href="../prim_precedence/char/index.html"]' 'crate::char' -// @has - '//*[@href="../prim_precedence/char/index.html"]' 'mod@char' +// @has prim_precedence/struct.MyString2.html '//*[@href="char/index.html"]' 'crate::char' +// @has - '//*[@href="char/index.html"]' 'mod@char' pub struct MyString2; diff --git a/src/test/rustdoc/intra-doc/private.rs b/src/test/rustdoc/intra-doc/private.rs index 337102d6ab3fa..2756a7998e8ea 100644 --- a/src/test/rustdoc/intra-doc/private.rs +++ b/src/test/rustdoc/intra-doc/private.rs @@ -4,9 +4,9 @@ // make sure to update `rustdoc-ui/intra-doc/private.rs` if you update this file /// docs [DontDocMe] [DontDocMe::f] [DontDocMe::x] -// @has private/struct.DocMe.html '//*a[@href="../private/struct.DontDocMe.html"]' 'DontDocMe' -// @has private/struct.DocMe.html '//*a[@href="../private/struct.DontDocMe.html#method.f"]' 'DontDocMe::f' -// @has private/struct.DocMe.html '//*a[@href="../private/struct.DontDocMe.html#structfield.x"]' 'DontDocMe::x' +// @has private/struct.DocMe.html '//*a[@href="struct.DontDocMe.html"]' 'DontDocMe' +// @has private/struct.DocMe.html '//*a[@href="struct.DontDocMe.html#method.f"]' 'DontDocMe::f' +// @has private/struct.DocMe.html '//*a[@href="struct.DontDocMe.html#structfield.x"]' 'DontDocMe::x' pub struct DocMe; struct DontDocMe { x: usize, diff --git a/src/test/rustdoc/intra-doc/proc-macro.rs b/src/test/rustdoc/intra-doc/proc-macro.rs index ab4626ccfc3ab..fce10a130be65 100644 --- a/src/test/rustdoc/intra-doc/proc-macro.rs +++ b/src/test/rustdoc/intra-doc/proc-macro.rs @@ -9,17 +9,17 @@ pub use proc_macro_macro::{DeriveA, attr_a}; use proc_macro_macro::{DeriveB, attr_b}; // @has proc_macro/struct.Foo.html -// @has - '//a/@href' '../proc_macro/derive.DeriveA.html' -// @has - '//a/@href' '../proc_macro/attr.attr_a.html' -// @has - '//a/@href' '../proc_macro/trait.DeriveTrait.html' +// @has - '//a/@href' 'derive.DeriveA.html' +// @has - '//a/@href' 'attr.attr_a.html' +// @has - '//a/@href' 'trait.DeriveTrait.html' // @has - '//a/@href' '../proc_macro_macro/derive.DeriveB.html' // @has - '//a/@href' '../proc_macro_macro/attr.attr_b.html' /// Link to [DeriveA], [attr_a], [DeriveB], [attr_b], [DeriveTrait] pub struct Foo; // @has proc_macro/struct.Bar.html -// @has - '//a/@href' '../proc_macro/derive.DeriveA.html' -// @has - '//a/@href' '../proc_macro/attr.attr_a.html' +// @has - '//a/@href' 'derive.DeriveA.html' +// @has - '//a/@href' 'attr.attr_a.html' /// Link to [deriveA](derive@DeriveA) [attr](macro@attr_a) pub struct Bar; diff --git a/src/test/rustdoc/intra-doc/pub-use.rs b/src/test/rustdoc/intra-doc/pub-use.rs index dd52249abc6d0..579fa68cee8be 100644 --- a/src/test/rustdoc/intra-doc/pub-use.rs +++ b/src/test/rustdoc/intra-doc/pub-use.rs @@ -13,7 +13,7 @@ extern crate inner; // @has outer/index.html // @ has - '//a[@href="https://doc.rust-lang.org/nightly/std/env/fn.var.html"]' "std::env" -// @ has - '//a[@href="../outer/fn.f.html"]' "g" +// @ has - '//a[@href="fn.f.html"]' "g" pub use f as g; // FIXME: same as above diff --git a/src/test/rustdoc/intra-doc/raw-ident-self.rs b/src/test/rustdoc/intra-doc/raw-ident-self.rs index d289797f14652..177c3016fb151 100644 --- a/src/test/rustdoc/intra-doc/raw-ident-self.rs +++ b/src/test/rustdoc/intra-doc/raw-ident-self.rs @@ -5,7 +5,7 @@ pub mod r#impl { impl S { /// See [Self::b]. // @has raw_ident_self/impl/struct.S.html - // @has - '//a[@href="../../raw_ident_self/impl/struct.S.html#method.b"]' 'Self::b' + // @has - '//a[@href="struct.S.html#method.b"]' 'Self::b' pub fn a() {} pub fn b() {} diff --git a/src/test/rustdoc/intra-doc/reexport-additional-docs.rs b/src/test/rustdoc/intra-doc/reexport-additional-docs.rs index 96f3580f3055e..64683bacd6513 100644 --- a/src/test/rustdoc/intra-doc/reexport-additional-docs.rs +++ b/src/test/rustdoc/intra-doc/reexport-additional-docs.rs @@ -3,13 +3,13 @@ #![crate_name = "foo"] extern crate inner; -// @has foo/struct.Inner.html '//a[@href="../foo/fn.with_code.html"]' 'crate::with_code' +// @has foo/struct.Inner.html '//a[@href="fn.with_code.html"]' 'crate::with_code' /// [crate::with_code] -// @has - '//a[@href="../foo/fn.with_code.html"]' 'different text' +// @has - '//a[@href="fn.with_code.html"]' 'different text' /// [different text][with_code] -// @has - '//a[@href="../foo/fn.me_too.html"]' 'me_too' +// @has - '//a[@href="fn.me_too.html"]' 'me_too' #[doc = "[me_too]"] -// @has - '//a[@href="../foo/fn.me_three.html"]' 'reference link' +// @has - '//a[@href="fn.me_three.html"]' 'reference link' /// This [reference link] #[doc = "has an attr in the way"] /// diff --git a/src/test/rustdoc/intra-doc/self.rs b/src/test/rustdoc/intra-doc/self.rs index b2b75127b3122..0ba7df8a78ad7 100644 --- a/src/test/rustdoc/intra-doc/self.rs +++ b/src/test/rustdoc/intra-doc/self.rs @@ -1,8 +1,8 @@ #![crate_name = "foo"] -// @has foo/index.html '//a/@href' '../foo/struct.Foo.html#method.new' -// @has foo/struct.Foo.html '//a/@href' '../foo/struct.Foo.html#method.new' +// @has foo/index.html '//a/@href' 'struct.Foo.html#method.new' +// @has foo/struct.Foo.html '//a/@href' 'struct.Foo.html#method.new' /// Use [`new`] to create a new instance. /// @@ -15,8 +15,8 @@ impl Foo { } } -// @has foo/index.html '//a/@href' '../foo/struct.Bar.html#method.new2' -// @has foo/struct.Bar.html '//a/@href' '../foo/struct.Bar.html#method.new2' +// @has foo/index.html '//a/@href' 'struct.Bar.html#method.new2' +// @has foo/struct.Bar.html '//a/@href' 'struct.Bar.html#method.new2' /// Use [`new2`] to create a new instance. /// @@ -30,7 +30,7 @@ impl Bar { } pub struct MyStruct { - // @has foo/struct.MyStruct.html '//a/@href' '../foo/struct.MyStruct.html#structfield.struct_field' + // @has foo/struct.MyStruct.html '//a/@href' 'struct.MyStruct.html#structfield.struct_field' /// [`struct_field`] /// @@ -39,7 +39,7 @@ pub struct MyStruct { } pub enum MyEnum { - // @has foo/enum.MyEnum.html '//a/@href' '../foo/enum.MyEnum.html#variant.EnumVariant' + // @has foo/enum.MyEnum.html '//a/@href' 'enum.MyEnum.html#variant.EnumVariant' /// [`EnumVariant`] /// @@ -48,7 +48,7 @@ pub enum MyEnum { } pub union MyUnion { - // @has foo/union.MyUnion.html '//a/@href' '../foo/union.MyUnion.html#structfield.union_field' + // @has foo/union.MyUnion.html '//a/@href' 'union.MyUnion.html#structfield.union_field' /// [`union_field`] /// @@ -57,21 +57,21 @@ pub union MyUnion { } pub trait MyTrait { - // @has foo/trait.MyTrait.html '//a/@href' '../foo/trait.MyTrait.html#associatedtype.AssoType' + // @has foo/trait.MyTrait.html '//a/@href' 'trait.MyTrait.html#associatedtype.AssoType' /// [`AssoType`] /// /// [`AssoType`]: Self::AssoType type AssoType; - // @has foo/trait.MyTrait.html '//a/@href' '../foo/trait.MyTrait.html#associatedconstant.ASSO_CONST' + // @has foo/trait.MyTrait.html '//a/@href' 'trait.MyTrait.html#associatedconstant.ASSO_CONST' /// [`ASSO_CONST`] /// /// [`ASSO_CONST`]: Self::ASSO_CONST const ASSO_CONST: i32 = 1; - // @has foo/trait.MyTrait.html '//a/@href' '../foo/trait.MyTrait.html#method.asso_fn' + // @has foo/trait.MyTrait.html '//a/@href' 'trait.MyTrait.html#method.asso_fn' /// [`asso_fn`] /// @@ -80,7 +80,7 @@ pub trait MyTrait { } impl MyStruct { - // @has foo/struct.MyStruct.html '//a/@href' '../foo/struct.MyStruct.html#method.for_impl' + // @has foo/struct.MyStruct.html '//a/@href' 'struct.MyStruct.html#method.for_impl' /// [`for_impl`] /// @@ -91,21 +91,21 @@ impl MyStruct { } impl MyTrait for MyStruct { - // @has foo/struct.MyStruct.html '//a/@href' '../foo/struct.MyStruct.html#associatedtype.AssoType' + // @has foo/struct.MyStruct.html '//a/@href' 'struct.MyStruct.html#associatedtype.AssoType' /// [`AssoType`] /// /// [`AssoType`]: Self::AssoType type AssoType = u32; - // @has foo/struct.MyStruct.html '//a/@href' '../foo/struct.MyStruct.html#associatedconstant.ASSO_CONST' + // @has foo/struct.MyStruct.html '//a/@href' 'struct.MyStruct.html#associatedconstant.ASSO_CONST' /// [`ASSO_CONST`] /// /// [`ASSO_CONST`]: Self::ASSO_CONST const ASSO_CONST: i32 = 10; - // @has foo/struct.MyStruct.html '//a/@href' '../foo/struct.MyStruct.html#method.asso_fn' + // @has foo/struct.MyStruct.html '//a/@href' 'struct.MyStruct.html#method.asso_fn' /// [`asso_fn`] /// diff --git a/src/test/rustdoc/intra-doc/trait-impl.rs b/src/test/rustdoc/intra-doc/trait-impl.rs index ef1987a829ad2..cf60dc1dbd50e 100644 --- a/src/test/rustdoc/intra-doc/trait-impl.rs +++ b/src/test/rustdoc/intra-doc/trait-impl.rs @@ -5,21 +5,21 @@ pub struct MyStruct; impl MyTrait for MyStruct { -// @has foo/struct.MyStruct.html '//a/@href' '../foo/struct.MyStruct.html#associatedtype.AssoType' +// @has foo/struct.MyStruct.html '//a/@href' 'struct.MyStruct.html#associatedtype.AssoType' /// [`AssoType`] /// /// [`AssoType`]: MyStruct::AssoType type AssoType = u32; -// @has foo/struct.MyStruct.html '//a/@href' '../foo/struct.MyStruct.html#associatedconstant.ASSO_CONST' +// @has foo/struct.MyStruct.html '//a/@href' 'struct.MyStruct.html#associatedconstant.ASSO_CONST' /// [`ASSO_CONST`] /// /// [`ASSO_CONST`]: MyStruct::ASSO_CONST const ASSO_CONST: i32 = 10; -// @has foo/struct.MyStruct.html '//a/@href' '../foo/struct.MyStruct.html#method.trait_fn' +// @has foo/struct.MyStruct.html '//a/@href' 'struct.MyStruct.html#method.trait_fn' /// [`trait_fn`] /// diff --git a/src/test/rustdoc/intra-doc/trait-item.rs b/src/test/rustdoc/intra-doc/trait-item.rs index affd2aaec2d22..7602aced56416 100644 --- a/src/test/rustdoc/intra-doc/trait-item.rs +++ b/src/test/rustdoc/intra-doc/trait-item.rs @@ -2,7 +2,7 @@ /// Link to [S::assoc_fn()] /// Link to [Default::default()] -// @has trait_item/struct.S.html '//*[@href="../trait_item/struct.S.html#method.assoc_fn"]' 'S::assoc_fn()' +// @has trait_item/struct.S.html '//*[@href="struct.S.html#method.assoc_fn"]' 'S::assoc_fn()' // @has - '//*[@href="https://doc.rust-lang.org/nightly/core/default/trait.Default.html#tymethod.default"]' 'Default::default()' pub struct S; diff --git a/src/test/rustdoc/intra-link-self-cache.rs b/src/test/rustdoc/intra-link-self-cache.rs index add1530a5a675..63bf7fa5768a7 100644 --- a/src/test/rustdoc/intra-link-self-cache.rs +++ b/src/test/rustdoc/intra-link-self-cache.rs @@ -1,12 +1,12 @@ #![crate_name = "foo"] -// @has foo/enum.E1.html '//a/@href' '../foo/enum.E1.html#variant.A' +// @has foo/enum.E1.html '//a/@href' 'enum.E1.html#variant.A' /// [Self::A::b] pub enum E1 { A { b: usize } } -// @has foo/enum.E2.html '//a/@href' '../foo/enum.E2.html#variant.A' +// @has foo/enum.E2.html '//a/@href' 'enum.E2.html#variant.A' /// [Self::A::b] pub enum E2 { diff --git a/src/test/rustdoc/issue-28478.rs b/src/test/rustdoc/issue-28478.rs index 4cc4056025463..497276e6826a1 100644 --- a/src/test/rustdoc/issue-28478.rs +++ b/src/test/rustdoc/issue-28478.rs @@ -23,9 +23,9 @@ impl Foo { } impl Bar for Foo { - // @has - '//*[@href="../issue_28478/trait.Bar.html#associatedtype.Bar"]' 'Bar' - // @has - '//*[@href="../issue_28478/trait.Bar.html#associatedconstant.Baz"]' 'Baz' - // @has - '//*[@href="../issue_28478/trait.Bar.html#tymethod.bar"]' 'bar' + // @has - '//*[@href="trait.Bar.html#associatedtype.Bar"]' 'Bar' + // @has - '//*[@href="trait.Bar.html#associatedconstant.Baz"]' 'Baz' + // @has - '//*[@href="trait.Bar.html#tymethod.bar"]' 'bar' fn bar() {} - // @has - '//*[@href="../issue_28478/trait.Bar.html#method.baz"]' 'baz' + // @has - '//*[@href="trait.Bar.html#method.baz"]' 'baz' } diff --git a/src/test/rustdoc/issue-55364.rs b/src/test/rustdoc/issue-55364.rs index 4aa553f779339..f156d225bd79b 100644 --- a/src/test/rustdoc/issue-55364.rs +++ b/src/test/rustdoc/issue-55364.rs @@ -2,19 +2,19 @@ // @has issue_55364/subone/index.html // These foo/bar links in the module's documentation should refer inside `subone` -// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/subone/fn.foo.html"]' 'foo' -// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/subone/fn.bar.html"]' 'bar' +// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="fn.foo.html"]' 'foo' +// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="fn.bar.html"]' 'bar' pub mod subone { //! See either [foo] or [bar]. // This should refer to subone's `bar` // @has issue_55364/subone/fn.foo.html - // @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/subone/fn.bar.html"]' 'bar' + // @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="fn.bar.html"]' 'bar' /// See [bar] pub fn foo() {} // This should refer to subone's `foo` // @has issue_55364/subone/fn.bar.html - // @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/subone/fn.foo.html"]' 'foo' + // @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="fn.foo.html"]' 'foo' /// See [foo] pub fn bar() {} } @@ -23,11 +23,11 @@ pub mod subone { // @has issue_55364/subtwo/index.html // These foo/bar links in the module's documentation should not reference inside `subtwo` -// @!has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/subtwo/fn.foo.html"]' 'foo' -// @!has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/subtwo/fn.bar.html"]' 'bar' +// @!has - '//section[@id="main"]/div[@class="docblock"]//a[@href="fn.foo.html"]' 'foo' +// @!has - '//section[@id="main"]/div[@class="docblock"]//a[@href="fn.bar.html"]' 'bar' // Instead it should be referencing the top level functions -// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/fn.foo.html"]' 'foo' -// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/fn.bar.html"]' 'bar' +// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../fn.foo.html"]' 'foo' +// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../fn.bar.html"]' 'bar' // Though there should be such links later // @has - '//section[@id="main"]/table//tr[@class="module-item"]/td/a[@class="fn"][@href="fn.foo.html"]' 'foo' // @has - '//section[@id="main"]/table//tr[@class="module-item"]/td/a[@class="fn"][@href="fn.bar.html"]' 'bar' @@ -37,13 +37,13 @@ pub mod subtwo { // Despite the module's docs referring to the top level foo/bar, // this should refer to subtwo's `bar` // @has issue_55364/subtwo/fn.foo.html - // @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/subtwo/fn.bar.html"]' 'bar' + // @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="fn.bar.html"]' 'bar' /// See [bar] pub fn foo() {} // Despite the module's docs referring to the top level foo/bar, // this should refer to subtwo's `foo` // @has issue_55364/subtwo/fn.bar.html - // @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/subtwo/fn.foo.html"]' 'foo' + // @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="fn.foo.html"]' 'foo' /// See [foo] pub fn bar() {} } @@ -59,8 +59,8 @@ pub fn bar() {} // @has issue_55364/subthree/index.html // This module should also refer to the top level foo/bar -// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/fn.foo.html"]' 'foo' -// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../../issue_55364/fn.bar.html"]' 'bar' +// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../fn.foo.html"]' 'foo' +// @has - '//section[@id="main"]/div[@class="docblock"]//a[@href="../fn.bar.html"]' 'bar' pub mod subthree { //! See either [foo][super::foo] or [bar][super::bar] } @@ -68,8 +68,8 @@ pub mod subthree { // Next we go *deeper* - In order to ensure it's not just "this or parent" // we test `crate::` and a `super::super::...` chain // @has issue_55364/subfour/subfive/subsix/subseven/subeight/index.html -// @has - '//section[@id="main"]/table//tr[@class="module-item"]/td[@class="docblock-short"]//a[@href="../../../../../../issue_55364/subone/fn.foo.html"]' 'other foo' -// @has - '//section[@id="main"]/table//tr[@class="module-item"]/td[@class="docblock-short"]//a[@href="../../../../../../issue_55364/subtwo/fn.bar.html"]' 'other bar' +// @has - '//section[@id="main"]/table//tr[@class="module-item"]/td[@class="docblock-short"]//a[@href="../../../../../subone/fn.foo.html"]' 'other foo' +// @has - '//section[@id="main"]/table//tr[@class="module-item"]/td[@class="docblock-short"]//a[@href="../../../../../subtwo/fn.bar.html"]' 'other bar' pub mod subfour { pub mod subfive { pub mod subsix { diff --git a/src/test/rustdoc/issue-72340.rs b/src/test/rustdoc/issue-72340.rs index 6ed3bfbe3e54b..64044cfe94720 100644 --- a/src/test/rustdoc/issue-72340.rs +++ b/src/test/rustdoc/issue-72340.rs @@ -10,7 +10,7 @@ impl Body { } impl Default for Body { - // @has foo/struct.Body.html '//a/@href' '../foo/struct.Body.html#method.empty' + // @has foo/struct.Body.html '//a/@href' 'struct.Body.html#method.empty' /// Returns [`Body::empty()`](Body::empty). fn default() -> Body { diff --git a/src/test/rustdoc/link-assoc-const.rs b/src/test/rustdoc/link-assoc-const.rs index f9eb2b722d6e6..75a2531a308f7 100644 --- a/src/test/rustdoc/link-assoc-const.rs +++ b/src/test/rustdoc/link-assoc-const.rs @@ -1,7 +1,7 @@ #![crate_name = "foo"] -// @has foo/index.html '//a[@href="../foo/foo/constant.FIRSTCONST.html"]' 'foo::FIRSTCONST' -// @has foo/index.html '//a[@href="../foo/struct.Bar.html#associatedconstant.CONST"]' 'Bar::CONST' +// @has foo/index.html '//a[@href="foo/constant.FIRSTCONST.html"]' 'foo::FIRSTCONST' +// @has foo/index.html '//a[@href="struct.Bar.html#associatedconstant.CONST"]' 'Bar::CONST' //! We have here [`foo::FIRSTCONST`] and [`Bar::CONST`]. diff --git a/src/test/rustdoc/proc-macro.rs b/src/test/rustdoc/proc-macro.rs index 82196e413e94b..f6d1f2cf91b5f 100644 --- a/src/test/rustdoc/proc-macro.rs +++ b/src/test/rustdoc/proc-macro.rs @@ -61,12 +61,12 @@ pub fn some_derive(_item: TokenStream) -> TokenStream { // @has some_macros/foo/index.html mod foo { // @has - '//code' 'pub use some_proc_macro;' - // @has - '//a/@href' '../../some_macros/macro.some_proc_macro.html' + // @has - '//a/@href' '../macro.some_proc_macro.html' pub use some_proc_macro; // @has - '//code' 'pub use some_proc_attr;' - // @has - '//a/@href' '../../some_macros/attr.some_proc_attr.html' + // @has - '//a/@href' '../attr.some_proc_attr.html' pub use some_proc_attr; // @has - '//code' 'pub use some_derive;' - // @has - '//a/@href' '../../some_macros/derive.SomeDerive.html' + // @has - '//a/@href' '../derive.SomeDerive.html' pub use some_derive; } diff --git a/src/test/rustdoc/raw-ident-eliminate-r-hashtag.rs b/src/test/rustdoc/raw-ident-eliminate-r-hashtag.rs index 3ecf434c39e45..ad19036126760 100644 --- a/src/test/rustdoc/raw-ident-eliminate-r-hashtag.rs +++ b/src/test/rustdoc/raw-ident-eliminate-r-hashtag.rs @@ -8,13 +8,13 @@ pub mod internal { /// /// [name]: mod /// [other name]: crate::internal::mod - // @has 'raw_ident_eliminate_r_hashtag/internal/struct.B.html' '//*a[@href="../../raw_ident_eliminate_r_hashtag/internal/struct.mod.html"]' 'name' - // @has 'raw_ident_eliminate_r_hashtag/internal/struct.B.html' '//*a[@href="../../raw_ident_eliminate_r_hashtag/internal/struct.mod.html"]' 'other name' + // @has 'raw_ident_eliminate_r_hashtag/internal/struct.B.html' '//*a[@href="struct.mod.html"]' 'name' + // @has 'raw_ident_eliminate_r_hashtag/internal/struct.B.html' '//*a[@href="struct.mod.html"]' 'other name' pub struct B; } /// See [name]. /// /// [name]: internal::mod -// @has 'raw_ident_eliminate_r_hashtag/struct.A.html' '//*a[@href="../raw_ident_eliminate_r_hashtag/internal/struct.mod.html"]' 'name' +// @has 'raw_ident_eliminate_r_hashtag/struct.A.html' '//*a[@href="internal/struct.mod.html"]' 'name' pub struct A; diff --git a/src/test/rustdoc/struct-field.rs b/src/test/rustdoc/struct-field.rs index 974b863bb1604..998683bdde7f2 100644 --- a/src/test/rustdoc/struct-field.rs +++ b/src/test/rustdoc/struct-field.rs @@ -1,9 +1,9 @@ #![crate_name = "foo"] -// @has foo/index.html '//*[@class="docblock"]/p/a[@href="../foo/struct.Foo.html#structfield.bar"]' 'Foo::bar' -// @has foo/index.html '//*[@class="docblock"]/p/a[@href="../foo/union.Bar.html#structfield.foo"]' 'Bar::foo' -// @has foo/index.html '//*[@class="docblock"]/p/a[@href="../foo/enum.Uniooon.html#variant.X"]' 'Uniooon::X' +// @has foo/index.html '//*[@class="docblock"]/p/a[@href="struct.Foo.html#structfield.bar"]' 'Foo::bar' +// @has foo/index.html '//*[@class="docblock"]/p/a[@href="union.Bar.html#structfield.foo"]' 'Bar::foo' +// @has foo/index.html '//*[@class="docblock"]/p/a[@href="enum.Uniooon.html#variant.X"]' 'Uniooon::X' //! Test with [Foo::bar], [Bar::foo], [Uniooon::X] diff --git a/src/test/rustdoc/trait-impl-items-links-and-anchors.rs b/src/test/rustdoc/trait-impl-items-links-and-anchors.rs index 6c09be1144a83..c6a9313e821c9 100644 --- a/src/test/rustdoc/trait-impl-items-links-and-anchors.rs +++ b/src/test/rustdoc/trait-impl-items-links-and-anchors.rs @@ -40,25 +40,25 @@ impl MyTrait for Vec<u8> { impl MyTrait for MyStruct { // @has trait_impl_items_links_and_anchors/trait.MyTrait.html '//h4[@id="associatedtype.Assoc-3"]//a[@class="type"]/@href' #associatedtype.Assoc // @has trait_impl_items_links_and_anchors/trait.MyTrait.html '//h4[@id="associatedtype.Assoc-3"]//a[@class="anchor"]/@href' #associatedtype.Assoc-3 - // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="associatedtype.Assoc"]//a[@class="type"]/@href' ../trait_impl_items_links_and_anchors/trait.MyTrait.html#associatedtype.Assoc + // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="associatedtype.Assoc"]//a[@class="type"]/@href' trait.MyTrait.html#associatedtype.Assoc // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="associatedtype.Assoc"]//a[@class="anchor"]/@href' #associatedtype.Assoc type Assoc = bool; // @has trait_impl_items_links_and_anchors/trait.MyTrait.html '//h4[@id="associatedconstant.VALUE-3"]//a[@class="constant"]/@href' #associatedconstant.VALUE // @has trait_impl_items_links_and_anchors/trait.MyTrait.html '//h4[@id="associatedconstant.VALUE-3"]//a[@class="anchor"]/@href' #associatedconstant.VALUE-3 - // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="associatedconstant.VALUE"]//a[@class="constant"]/@href' ../trait_impl_items_links_and_anchors/trait.MyTrait.html#associatedconstant.VALUE + // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="associatedconstant.VALUE"]//a[@class="constant"]/@href' trait.MyTrait.html#associatedconstant.VALUE // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="associatedconstant.VALUE"]//a[@class="anchor"]/@href' #associatedconstant.VALUE const VALUE: u32 = 20; // @has trait_impl_items_links_and_anchors/trait.MyTrait.html '//h4[@id="method.trait_function-2"]//a[@class="fnname"]/@href' #tymethod.trait_function // @has trait_impl_items_links_and_anchors/trait.MyTrait.html '//h4[@id="method.trait_function-2"]//a[@class="anchor"]/@href' #method.trait_function-2 - // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="method.trait_function"]//a[@class="fnname"]/@href' ../trait_impl_items_links_and_anchors/trait.MyTrait.html#tymethod.trait_function + // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="method.trait_function"]//a[@class="fnname"]/@href' trait.MyTrait.html#tymethod.trait_function // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="method.trait_function"]//a[@class="anchor"]/@href' #method.trait_function fn trait_function(&self) {} // @has trait_impl_items_links_and_anchors/trait.MyTrait.html '//h4[@id="method.defaulted_override-3"]//a[@class="fnname"]/@href' #method.defaulted_override // @has trait_impl_items_links_and_anchors/trait.MyTrait.html '//h4[@id="method.defaulted_override-3"]//a[@class="anchor"]/@href' #method.defaulted_override-3 - // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="method.defaulted_override"]//a[@class="fnname"]/@href' ../trait_impl_items_links_and_anchors/trait.MyTrait.html#method.defaulted_override + // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="method.defaulted_override"]//a[@class="fnname"]/@href' trait.MyTrait.html#method.defaulted_override // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="method.defaulted_override"]//a[@class="anchor"]/@href' #method.defaulted_override fn defaulted_override(&self) {} - // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="method.defaulted"]//a[@class="fnname"]/@href' ../trait_impl_items_links_and_anchors/trait.MyTrait.html#method.defaulted + // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="method.defaulted"]//a[@class="fnname"]/@href' trait.MyTrait.html#method.defaulted // @has trait_impl_items_links_and_anchors/struct.MyStruct.html '//h4[@id="method.defaulted"]//a[@class="anchor"]/@href' #method.defaulted } diff --git a/src/test/rustdoc/trait-self-link.rs b/src/test/rustdoc/trait-self-link.rs index bac28b44012cc..e311dadff0e63 100644 --- a/src/test/rustdoc/trait-self-link.rs +++ b/src/test/rustdoc/trait-self-link.rs @@ -1,4 +1,4 @@ -// @has trait_self_link/trait.Foo.html //a/@href ../trait_self_link/trait.Foo.html +// @has trait_self_link/trait.Foo.html //a/@href trait.Foo.html pub trait Foo {} pub struct Bar; From 21bf8af8739235eda6e19b779bf19cad528f3b92 Mon Sep 17 00:00:00 2001 From: Michael Howell <michael@notriddle.com> Date: Fri, 16 Apr 2021 10:06:48 -0700 Subject: [PATCH 10/13] Remove assert that no longer matters The function `to_src_with_space` doesn't even accept the cache as a parameter, so it doesn't matter any more what's in it. --- src/librustdoc/clean/mod.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index f5cd298dac4ff..7d33cf210139c 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -2284,13 +2284,6 @@ impl Clean<Item> for (&hir::MacroDef<'_>, Option<Symbol>) { let vis = item.vis.clean(cx); let def_id = item.def_id.to_def_id(); - // Since this occurs in `clean()`, there are no cache entries for vis.print_with_space, - // so no links can be made. - // - // It's important that we maintain this invariant, because this is supposed to generate - // source code, not HTML. - assert!(cx.cache.paths.is_empty()); - if matchers.len() <= 1 { format!( "{}macro {}{} {{\n ...\n}}", From bb0204861b8c870eaf972986e85fb682b3c337a6 Mon Sep 17 00:00:00 2001 From: Michael Howell <michael@notriddle.com> Date: Fri, 16 Apr 2021 11:21:17 -0700 Subject: [PATCH 11/13] rustdoc: change 'cx to 'tcx, to match the struct itself --- src/librustdoc/html/format.rs | 120 +++++++++++++++++----------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 191f29f40e72e..c65ad738a9d69 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -124,10 +124,10 @@ fn comma_sep<T: fmt::Display>(items: impl Iterator<Item = T>) -> impl fmt::Displ }) } -crate fn print_generic_bounds<'a, 'cx: 'a>( - cx: &'a Context<'cx>, +crate fn print_generic_bounds<'a, 'tcx: 'a>( + cx: &'a Context<'tcx>, bounds: &'a [clean::GenericBound], -) -> impl fmt::Display + 'a + Captures<'cx> { +) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| { let mut bounds_dup = FxHashSet::default(); @@ -144,10 +144,10 @@ crate fn print_generic_bounds<'a, 'cx: 'a>( } impl clean::GenericParamDef { - crate fn print<'a, 'cx: 'a>( + crate fn print<'a, 'tcx: 'a>( &'a self, - cx: &'a Context<'cx>, - ) -> impl fmt::Display + 'a + Captures<'cx> { + cx: &'a Context<'tcx>, + ) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| match self.kind { clean::GenericParamDefKind::Lifetime => write!(f, "{}", self.name), clean::GenericParamDefKind::Type { ref bounds, ref default, .. } => { @@ -183,10 +183,10 @@ impl clean::GenericParamDef { } impl clean::Generics { - crate fn print<'a, 'cx: 'a>( + crate fn print<'a, 'tcx: 'a>( &'a self, - cx: &'a Context<'cx>, - ) -> impl fmt::Display + 'a + Captures<'cx> { + cx: &'a Context<'tcx>, + ) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| { let real_params = self.params.iter().filter(|p| !p.is_synthetic_type_param()).collect::<Vec<_>>(); @@ -205,12 +205,12 @@ impl clean::Generics { /// * The Generics from which to emit a where-clause. /// * The number of spaces to indent each line with. /// * Whether the where-clause needs to add a comma and newline after the last bound. -crate fn print_where_clause<'a, 'cx: 'a>( +crate fn print_where_clause<'a, 'tcx: 'a>( gens: &'a clean::Generics, - cx: &'a Context<'cx>, + cx: &'a Context<'tcx>, indent: usize, end_newline: bool, -) -> impl fmt::Display + 'a + Captures<'cx> { +) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| { if gens.where_predicates.is_empty() { return Ok(()); @@ -314,10 +314,10 @@ impl clean::Constant { } impl clean::PolyTrait { - fn print<'a, 'cx: 'a>( + fn print<'a, 'tcx: 'a>( &'a self, - cx: &'a Context<'cx>, - ) -> impl fmt::Display + 'a + Captures<'cx> { + cx: &'a Context<'tcx>, + ) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| { if !self.generic_params.is_empty() { if f.alternate() { @@ -344,10 +344,10 @@ impl clean::PolyTrait { } impl clean::GenericBound { - crate fn print<'a, 'cx: 'a>( + crate fn print<'a, 'tcx: 'a>( &'a self, - cx: &'a Context<'cx>, - ) -> impl fmt::Display + 'a + Captures<'cx> { + cx: &'a Context<'tcx>, + ) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| match self { clean::GenericBound::Outlives(lt) => write!(f, "{}", lt.print()), clean::GenericBound::TraitBound(ty, modifier) => { @@ -367,10 +367,10 @@ impl clean::GenericBound { } impl clean::GenericArgs { - fn print<'a, 'cx: 'a>( + fn print<'a, 'tcx: 'a>( &'a self, - cx: &'a Context<'cx>, - ) -> impl fmt::Display + 'a + Captures<'cx> { + cx: &'a Context<'tcx>, + ) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| { match self { clean::GenericArgs::AngleBracketed { args, bindings } => { @@ -602,10 +602,10 @@ fn primitive_link( } /// Helper to render type parameters -fn tybounds<'a, 'cx: 'a>( +fn tybounds<'a, 'tcx: 'a>( param_names: &'a Option<Vec<clean::GenericBound>>, - cx: &'a Context<'cx>, -) -> impl fmt::Display + 'a + Captures<'cx> { + cx: &'a Context<'tcx>, +) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| match *param_names { Some(ref params) => { for param in params { @@ -882,20 +882,20 @@ fn fmt_type<'cx>( } impl clean::Type { - crate fn print<'b, 'a: 'b, 'cx: 'a>( + crate fn print<'b, 'a: 'b, 'tcx: 'a>( &'a self, - cx: &'a Context<'cx>, - ) -> impl fmt::Display + 'b + Captures<'cx> { + cx: &'a Context<'tcx>, + ) -> impl fmt::Display + 'b + Captures<'tcx> { display_fn(move |f| fmt_type(self, f, false, cx)) } } impl clean::Impl { - crate fn print<'a, 'cx: 'a>( + crate fn print<'a, 'tcx: 'a>( &'a self, use_absolute: bool, - cx: &'a Context<'cx>, - ) -> impl fmt::Display + 'a + Captures<'cx> { + cx: &'a Context<'tcx>, + ) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| { if f.alternate() { write!(f, "impl{:#} ", self.generics.print(cx))?; @@ -924,10 +924,10 @@ impl clean::Impl { } impl clean::Arguments { - crate fn print<'a, 'cx: 'a>( + crate fn print<'a, 'tcx: 'a>( &'a self, - cx: &'a Context<'cx>, - ) -> impl fmt::Display + 'a + Captures<'cx> { + cx: &'a Context<'tcx>, + ) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| { for (i, input) in self.values.iter().enumerate() { if !input.name.is_empty() { @@ -948,10 +948,10 @@ impl clean::Arguments { } impl clean::FnRetTy { - crate fn print<'a, 'cx: 'a>( + crate fn print<'a, 'tcx: 'a>( &'a self, - cx: &'a Context<'cx>, - ) -> impl fmt::Display + 'a + Captures<'cx> { + cx: &'a Context<'tcx>, + ) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| match self { clean::Return(clean::Tuple(tys)) if tys.is_empty() => Ok(()), clean::Return(ty) if f.alternate() => { @@ -964,10 +964,10 @@ impl clean::FnRetTy { } impl clean::BareFunctionDecl { - fn print_hrtb_with_space<'a, 'cx: 'a>( + fn print_hrtb_with_space<'a, 'tcx: 'a>( &'a self, - cx: &'a Context<'cx>, - ) -> impl fmt::Display + 'a + Captures<'cx> { + cx: &'a Context<'tcx>, + ) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| { if !self.generic_params.is_empty() { write!(f, "for<{}> ", comma_sep(self.generic_params.iter().map(|g| g.print(cx)))) @@ -979,10 +979,10 @@ impl clean::BareFunctionDecl { } impl clean::FnDecl { - crate fn print<'b, 'a: 'b, 'cx: 'a>( + crate fn print<'b, 'a: 'b, 'tcx: 'a>( &'a self, - cx: &'a Context<'cx>, - ) -> impl fmt::Display + 'b + Captures<'cx> { + cx: &'a Context<'tcx>, + ) -> impl fmt::Display + 'b + Captures<'tcx> { display_fn(move |f| { let ellipsis = if self.c_variadic { ", ..." } else { "" }; if f.alternate() { @@ -1011,13 +1011,13 @@ impl clean::FnDecl { /// * `indent`: The number of spaces to indent each successive line with, if line-wrapping is /// necessary. /// * `asyncness`: Whether the function is async or not. - crate fn full_print<'a, 'cx: 'a>( + crate fn full_print<'a, 'tcx: 'a>( &'a self, - cx: &'a Context<'cx>, + cx: &'a Context<'tcx>, header_len: usize, indent: usize, asyncness: hir::IsAsync, - ) -> impl fmt::Display + 'a + Captures<'cx> { + ) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| self.inner_full_print(cx, header_len, indent, asyncness, f)) } @@ -1132,11 +1132,11 @@ impl clean::FnDecl { } impl clean::Visibility { - crate fn print_with_space<'a, 'cx: 'a>( + crate fn print_with_space<'a, 'tcx: 'a>( self, - cx: &'a Context<'cx>, + cx: &'a Context<'tcx>, item_did: DefId, - ) -> impl fmt::Display + 'a + Captures<'cx> { + ) -> impl fmt::Display + 'a + Captures<'tcx> { let to_print = match self { clean::Public => "pub ".to_owned(), clean::Inherited => String::new(), @@ -1256,10 +1256,10 @@ impl PrintWithSpace for hir::Mutability { } impl clean::Import { - crate fn print<'a, 'cx: 'a>( + crate fn print<'a, 'tcx: 'a>( &'a self, - cx: &'a Context<'cx>, - ) -> impl fmt::Display + 'a + Captures<'cx> { + cx: &'a Context<'tcx>, + ) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| match self.kind { clean::ImportKind::Simple(name) => { if name == self.source.path.last() { @@ -1280,10 +1280,10 @@ impl clean::Import { } impl clean::ImportSource { - crate fn print<'a, 'cx: 'a>( + crate fn print<'a, 'tcx: 'a>( &'a self, - cx: &'a Context<'cx>, - ) -> impl fmt::Display + 'a + Captures<'cx> { + cx: &'a Context<'tcx>, + ) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| match self.did { Some(did) => resolved_path(f, did, &self.path, true, false, cx), _ => { @@ -1303,10 +1303,10 @@ impl clean::ImportSource { } impl clean::TypeBinding { - crate fn print<'a, 'cx: 'a>( + crate fn print<'a, 'tcx: 'a>( &'a self, - cx: &'a Context<'cx>, - ) -> impl fmt::Display + 'a + Captures<'cx> { + cx: &'a Context<'tcx>, + ) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| { f.write_str(&*self.name.as_str())?; match self.kind { @@ -1347,10 +1347,10 @@ crate fn print_default_space<'a>(v: bool) -> &'a str { } impl clean::GenericArg { - crate fn print<'a, 'cx: 'a>( + crate fn print<'a, 'tcx: 'a>( &'a self, - cx: &'a Context<'cx>, - ) -> impl fmt::Display + 'a + Captures<'cx> { + cx: &'a Context<'tcx>, + ) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| match self { clean::GenericArg::Lifetime(lt) => fmt::Display::fmt(<.print(), f), clean::GenericArg::Type(ty) => fmt::Display::fmt(&ty.print(cx), f), From 755b4fb02b2a1fcc7d1e2163d1b4b370699788c9 Mon Sep 17 00:00:00 2001 From: Michael Howell <michael@notriddle.com> Date: Fri, 16 Apr 2021 12:29:35 -0700 Subject: [PATCH 12/13] rustdoc: move the cx argument to the end of the list This should help make things consistent. --- src/librustdoc/html/format.rs | 26 +++++++++---------- src/librustdoc/html/render/mod.rs | 8 +++--- src/librustdoc/html/render/print_item.rs | 32 ++++++++++++------------ 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index c65ad738a9d69..ca364b9f10365 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -125,8 +125,8 @@ fn comma_sep<T: fmt::Display>(items: impl Iterator<Item = T>) -> impl fmt::Displ } crate fn print_generic_bounds<'a, 'tcx: 'a>( - cx: &'a Context<'tcx>, bounds: &'a [clean::GenericBound], + cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx> { display_fn(move |f| { let mut bounds_dup = FxHashSet::default(); @@ -155,9 +155,9 @@ impl clean::GenericParamDef { if !bounds.is_empty() { if f.alternate() { - write!(f, ": {:#}", print_generic_bounds(cx, bounds))?; + write!(f, ": {:#}", print_generic_bounds(bounds, cx))?; } else { - write!(f, ": {}", print_generic_bounds(cx, bounds))?; + write!(f, ": {}", print_generic_bounds(bounds, cx))?; } } @@ -239,13 +239,13 @@ crate fn print_where_clause<'a, 'tcx: 'a>( clause.push_str(&format!( "{:#}: {:#}", ty.print(cx), - print_generic_bounds(cx, bounds) + print_generic_bounds(bounds, cx) )); } else { clause.push_str(&format!( "{}: {}", ty.print(cx), - print_generic_bounds(cx, bounds) + print_generic_bounds(bounds, cx) )); } } @@ -819,9 +819,9 @@ fn fmt_type<'cx>( } clean::ImplTrait(ref bounds) => { if f.alternate() { - write!(f, "impl {:#}", print_generic_bounds(cx, bounds)) + write!(f, "impl {:#}", print_generic_bounds(bounds, cx)) } else { - write!(f, "impl {}", print_generic_bounds(cx, bounds)) + write!(f, "impl {}", print_generic_bounds(bounds, cx)) } } clean::QPath { ref name, ref self_type, ref trait_ } => { @@ -1013,21 +1013,21 @@ impl clean::FnDecl { /// * `asyncness`: Whether the function is async or not. crate fn full_print<'a, 'tcx: 'a>( &'a self, - cx: &'a Context<'tcx>, header_len: usize, indent: usize, asyncness: hir::IsAsync, + cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx> { - display_fn(move |f| self.inner_full_print(cx, header_len, indent, asyncness, f)) + display_fn(move |f| self.inner_full_print(header_len, indent, asyncness, f, cx)) } fn inner_full_print( &self, - cx: &Context<'_>, header_len: usize, indent: usize, asyncness: hir::IsAsync, f: &mut fmt::Formatter<'_>, + cx: &Context<'_>, ) -> fmt::Result { let amp = if f.alternate() { "&" } else { "&" }; let mut args = String::new(); @@ -1134,8 +1134,8 @@ impl clean::FnDecl { impl clean::Visibility { crate fn print_with_space<'a, 'tcx: 'a>( self, - cx: &'a Context<'tcx>, item_did: DefId, + cx: &'a Context<'tcx>, ) -> impl fmt::Display + 'a + Captures<'tcx> { let to_print = match self { clean::Public => "pub ".to_owned(), @@ -1320,9 +1320,9 @@ impl clean::TypeBinding { clean::TypeBindingKind::Constraint { ref bounds } => { if !bounds.is_empty() { if f.alternate() { - write!(f, ": {:#}", print_generic_bounds(cx, bounds))?; + write!(f, ": {:#}", print_generic_bounds(bounds, cx))?; } else { - write!(f, ": {}", print_generic_bounds(cx, bounds))?; + write!(f, ": {}", print_generic_bounds(bounds, cx))?; } } } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 9bd4f57051c87..d10b612a73799 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -815,7 +815,7 @@ fn assoc_const( w, "{}{}const <a href=\"{}\" class=\"constant\"><b>{}</b></a>: {}", extra, - it.visibility.print_with_space(cx, it.def_id), + it.visibility.print_with_space(it.def_id, cx), naive_assoc_href(it, link, cx), it.name.as_ref().unwrap(), ty.print(cx) @@ -839,7 +839,7 @@ fn assoc_type( it.name.as_ref().unwrap() ); if !bounds.is_empty() { - write!(w, ": {}", print_generic_bounds(cx, bounds)) + write!(w, ": {}", print_generic_bounds(bounds, cx)) } if let Some(default) = default { write!(w, " = {}", default.print(cx)) @@ -910,7 +910,7 @@ fn render_assoc_item( .unwrap_or_else(|| format!("#{}.{}", ty, name)) } }; - let vis = meth.visibility.print_with_space(cx, meth.def_id).to_string(); + let vis = meth.visibility.print_with_space(meth.def_id, cx).to_string(); let constness = header.constness.print_with_space(); let asyncness = header.asyncness.print_with_space(); let unsafety = header.unsafety.print_with_space(); @@ -952,7 +952,7 @@ fn render_assoc_item( href = href, name = name, generics = g.print(cx), - decl = d.full_print(cx, header_len, indent, header.asyncness), + decl = d.full_print(header_len, indent, header.asyncness, cx), notable_traits = notable_traits_decl(&d, cx), where_clause = print_where_clause(g, cx, indent, end_newline), ) diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 3b939a255a4e7..42b795030171b 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -267,14 +267,14 @@ fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[cl Some(ref src) => write!( w, "<tr><td><code>{}extern crate {} as {};", - myitem.visibility.print_with_space(cx, myitem.def_id), + myitem.visibility.print_with_space(myitem.def_id, cx), anchor(myitem.def_id, &*src.as_str(), cx), myitem.name.as_ref().unwrap(), ), None => write!( w, "<tr><td><code>{}extern crate {};", - myitem.visibility.print_with_space(cx, myitem.def_id), + myitem.visibility.print_with_space(myitem.def_id, cx), anchor(myitem.def_id, &*myitem.name.as_ref().unwrap().as_str(), cx), ), } @@ -285,7 +285,7 @@ fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[cl write!( w, "<tr><td><code>{}{}</code></td></tr>", - myitem.visibility.print_with_space(cx, myitem.def_id), + myitem.visibility.print_with_space(myitem.def_id, cx), import.print(cx), ); } @@ -386,7 +386,7 @@ fn extra_info_tags(item: &clean::Item, parent: &clean::Item, tcx: TyCtxt<'_>) -> fn item_function(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, f: &clean::Function) { let header_len = format!( "{}{}{}{}{:#}fn {}{:#}", - it.visibility.print_with_space(cx, it.def_id), + it.visibility.print_with_space(it.def_id, cx), f.header.constness.print_with_space(), f.header.asyncness.print_with_space(), f.header.unsafety.print_with_space(), @@ -401,7 +401,7 @@ fn item_function(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, f: &clean:: w, "{vis}{constness}{asyncness}{unsafety}{abi}fn \ {name}{generics}{decl}{notable_traits}{where_clause}</pre>", - vis = it.visibility.print_with_space(cx, it.def_id), + vis = it.visibility.print_with_space(it.def_id, cx), constness = f.header.constness.print_with_space(), asyncness = f.header.asyncness.print_with_space(), unsafety = f.header.unsafety.print_with_space(), @@ -409,7 +409,7 @@ fn item_function(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, f: &clean:: name = it.name.as_ref().unwrap(), generics = f.generics.print(cx), where_clause = print_where_clause(&f.generics, cx, 0, true), - decl = f.decl.full_print(cx, header_len, 0, f.header.asyncness), + decl = f.decl.full_print(header_len, 0, f.header.asyncness, cx), notable_traits = notable_traits_decl(&f.decl, cx), ); document(w, cx, it, None) @@ -429,7 +429,7 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra write!( w, "{}{}{}trait {}{}{}", - it.visibility.print_with_space(cx, it.def_id), + it.visibility.print_with_space(it.def_id, cx), t.unsafety.print_with_space(), if t.is_auto { "auto " } else { "" }, it.name.as_ref().unwrap(), @@ -848,7 +848,7 @@ fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum write!( w, "{}enum {}{}{}", - it.visibility.print_with_space(cx, it.def_id), + it.visibility.print_with_space(it.def_id, cx), it.name.as_ref().unwrap(), e.generics.print(cx), print_where_clause(&e.generics, cx, 0, true), @@ -1029,7 +1029,7 @@ fn item_constant(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, c: &clean:: write!( w, "{vis}const {name}: {typ}", - vis = it.visibility.print_with_space(cx, it.def_id), + vis = it.visibility.print_with_space(it.def_id, cx), name = it.name.as_ref().unwrap(), typ = c.type_.print(cx), ); @@ -1116,7 +1116,7 @@ fn item_static(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::St write!( w, "{vis}static {mutability}{name}: {typ}</pre>", - vis = it.visibility.print_with_space(cx, it.def_id), + vis = it.visibility.print_with_space(it.def_id, cx), mutability = s.mutability.print_with_space(), name = it.name.as_ref().unwrap(), typ = s.type_.print(cx) @@ -1130,7 +1130,7 @@ fn item_foreign_type(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item) { write!( w, " {}type {};\n}}</pre>", - it.visibility.print_with_space(cx, it.def_id), + it.visibility.print_with_space(it.def_id, cx), it.name.as_ref().unwrap(), ); @@ -1289,7 +1289,7 @@ fn render_union( write!( w, "{}{}{}", - it.visibility.print_with_space(cx, it.def_id), + it.visibility.print_with_space(it.def_id, cx), if structhead { "union " } else { "" }, it.name.as_ref().unwrap() ); @@ -1311,7 +1311,7 @@ fn render_union( write!( w, " {}{}: {},\n{}", - field.visibility.print_with_space(cx, field.def_id), + field.visibility.print_with_space(field.def_id, cx), field.name.as_ref().unwrap(), ty.print(cx), tab @@ -1341,7 +1341,7 @@ fn render_struct( write!( w, "{}{}{}", - it.visibility.print_with_space(cx, it.def_id), + it.visibility.print_with_space(it.def_id, cx), if structhead { "struct " } else { "" }, it.name.as_ref().unwrap() ); @@ -1367,7 +1367,7 @@ fn render_struct( w, "\n{} {}{}: {},", tab, - field.visibility.print_with_space(cx, field.def_id), + field.visibility.print_with_space(field.def_id, cx), field.name.as_ref().unwrap(), ty.print(cx), ); @@ -1401,7 +1401,7 @@ fn render_struct( write!( w, "{}{}", - field.visibility.print_with_space(cx, field.def_id), + field.visibility.print_with_space(field.def_id, cx), ty.print(cx), ) } From 59546efaa3bbbad5fdcd2a95346c87f5bb2b9511 Mon Sep 17 00:00:00 2001 From: Joshua Nelson <jyn514@gmail.com> Date: Wed, 14 Apr 2021 16:59:41 -0400 Subject: [PATCH 13/13] rustdoc: Give a more accurate span for anchor failures --- .../passes/collect_intra_doc_links.rs | 24 ++++++++++++------- src/test/rustdoc-ui/intra-doc/anchors.stderr | 20 ++++++++++++---- .../rustdoc-ui/intra-doc/double-anchor.stderr | 4 +++- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 4ce7c70d4b57e..837cd034b655c 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -1957,20 +1957,28 @@ fn resolution_failure( /// Report an anchor failure. fn anchor_failure(cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>, failure: AnchorFailure) { - let msg = match failure { + let (msg, anchor_idx) = match failure { AnchorFailure::MultipleAnchors => { - format!("`{}` contains multiple anchors", diag_info.ori_link) + (format!("`{}` contains multiple anchors", diag_info.ori_link), 1) } - AnchorFailure::RustdocAnchorConflict(res) => format!( - "`{}` contains an anchor, but links to {kind}s are already anchored", - diag_info.ori_link, - kind = res.descr(), + AnchorFailure::RustdocAnchorConflict(res) => ( + format!( + "`{}` contains an anchor, but links to {kind}s are already anchored", + diag_info.ori_link, + kind = res.descr(), + ), + 0, ), }; report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, &msg, &diag_info, |diag, sp| { - if let Some(sp) = sp { - diag.span_label(sp, "contains invalid anchor"); + if let Some(mut sp) = sp { + if let Some((fragment_offset, _)) = + diag_info.ori_link.char_indices().filter(|(_, x)| *x == '#').nth(anchor_idx) + { + sp = sp.with_lo(sp.lo() + rustc_span::BytePos(fragment_offset as _)); + } + diag.span_label(sp, "invalid anchor"); } if let AnchorFailure::RustdocAnchorConflict(Res::Primitive(_)) = failure { diag.note("this restriction may be lifted in a future release"); diff --git a/src/test/rustdoc-ui/intra-doc/anchors.stderr b/src/test/rustdoc-ui/intra-doc/anchors.stderr index 42a8832185ae6..d63e1ee60b3c5 100644 --- a/src/test/rustdoc-ui/intra-doc/anchors.stderr +++ b/src/test/rustdoc-ui/intra-doc/anchors.stderr @@ -2,7 +2,9 @@ error: `prim@usize#x` contains an anchor, but links to builtin types are already --> $DIR/anchors.rs:47:6 | LL | /// [prim@usize#x] - | ^^^^^^^^^^^^ contains invalid anchor + | ^^^^^^^^^^-- + | | + | invalid anchor | note: the lint level is defined here --> $DIR/anchors.rs:1:9 @@ -16,25 +18,33 @@ error: `Foo::f#hola` contains an anchor, but links to fields are already anchore --> $DIR/anchors.rs:25:15 | LL | /// Or maybe [Foo::f#hola]. - | ^^^^^^^^^^^ contains invalid anchor + | ^^^^^^----- + | | + | invalid anchor error: `hello#people#!` contains multiple anchors --> $DIR/anchors.rs:31:28 | LL | /// Another anchor error: [hello#people#!]. - | ^^^^^^^^^^^^^^ contains invalid anchor + | ^^^^^^^^^^^^-- + | | + | invalid anchor error: `Enum::A#whatever` contains an anchor, but links to variants are already anchored --> $DIR/anchors.rs:37:28 | LL | /// Damn enum's variants: [Enum::A#whatever]. - | ^^^^^^^^^^^^^^^^ contains invalid anchor + | ^^^^^^^--------- + | | + | invalid anchor error: `u32#hello` contains an anchor, but links to builtin types are already anchored --> $DIR/anchors.rs:43:6 | LL | /// [u32#hello] - | ^^^^^^^^^ contains invalid anchor + | ^^^------ + | | + | invalid anchor | = note: this restriction may be lifted in a future release = note: see https://github.com/rust-lang/rust/issues/83083 for more information diff --git a/src/test/rustdoc-ui/intra-doc/double-anchor.stderr b/src/test/rustdoc-ui/intra-doc/double-anchor.stderr index c0241b98b78c1..6addb010e078f 100644 --- a/src/test/rustdoc-ui/intra-doc/double-anchor.stderr +++ b/src/test/rustdoc-ui/intra-doc/double-anchor.stderr @@ -2,7 +2,9 @@ warning: `with#anchor#error` contains multiple anchors --> $DIR/double-anchor.rs:5:18 | LL | /// docs [label][with#anchor#error] - | ^^^^^^^^^^^^^^^^^ contains invalid anchor + | ^^^^^^^^^^^------ + | | + | invalid anchor | = note: `#[warn(rustdoc::broken_intra_doc_links)]` on by default