rustdoc: make search.js a module
Previously, search.js relied on the DOM and the `window` object. It can now be loaded in the absence of the DOM, for instance by Node. The same is true of search-index.js. This allows removing a lot of code from src/tools/rustdoc-js/tester.js that tried to parse search.js and extract specific functions that were needed for testing.
This commit is contained in:
parent
29e972dc60
commit
453979462a
4 changed files with 117 additions and 313 deletions
|
@ -1,182 +1,6 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function getNextStep(content, pos, stop) {
|
||||
while (pos < content.length && content[pos] !== stop &&
|
||||
(content[pos] === ' ' || content[pos] === '\t' || content[pos] === '\n')) {
|
||||
pos += 1;
|
||||
}
|
||||
if (pos >= content.length) {
|
||||
return null;
|
||||
}
|
||||
if (content[pos] !== stop) {
|
||||
return pos * -1;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
// Stupid function extractor based on indent. Doesn't support block
|
||||
// comments. If someone puts a ' or an " in a block comment this
|
||||
// will blow up. Template strings are not tested and might also be
|
||||
// broken.
|
||||
function extractFunction(content, functionName) {
|
||||
var level = 0;
|
||||
var splitter = "function " + functionName + "(";
|
||||
var stop;
|
||||
var pos, start;
|
||||
|
||||
while (true) {
|
||||
start = content.indexOf(splitter);
|
||||
if (start === -1) {
|
||||
break;
|
||||
}
|
||||
pos = start;
|
||||
while (pos < content.length && content[pos] !== ')') {
|
||||
pos += 1;
|
||||
}
|
||||
if (pos >= content.length) {
|
||||
break;
|
||||
}
|
||||
pos = getNextStep(content, pos + 1, '{');
|
||||
if (pos === null) {
|
||||
break;
|
||||
} else if (pos < 0) {
|
||||
content = content.slice(-pos);
|
||||
continue;
|
||||
}
|
||||
while (pos < content.length) {
|
||||
// Eat single-line comments
|
||||
if (content[pos] === '/' && pos > 0 && content[pos - 1] === '/') {
|
||||
do {
|
||||
pos += 1;
|
||||
} while (pos < content.length && content[pos] !== '\n');
|
||||
|
||||
// Eat multiline comment.
|
||||
} else if (content[pos] === '*' && pos > 0 && content[pos - 1] === '/') {
|
||||
do {
|
||||
pos += 1;
|
||||
} while (pos < content.length && content[pos] !== '/' && content[pos - 1] !== '*');
|
||||
|
||||
// Eat quoted strings
|
||||
} else if ((content[pos] === '"' || content[pos] === "'" || content[pos] === "`") &&
|
||||
(pos === 0 || content[pos - 1] !== '/')) {
|
||||
stop = content[pos];
|
||||
do {
|
||||
if (content[pos] === '\\') {
|
||||
pos += 1;
|
||||
}
|
||||
pos += 1;
|
||||
} while (pos < content.length && content[pos] !== stop);
|
||||
|
||||
// Otherwise, check for block level.
|
||||
} else if (content[pos] === '{') {
|
||||
level += 1;
|
||||
} else if (content[pos] === '}') {
|
||||
level -= 1;
|
||||
if (level === 0) {
|
||||
return content.slice(start, pos + 1);
|
||||
}
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
content = content.slice(start + 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Stupid function extractor for array.
|
||||
function extractArrayVariable(content, arrayName, kind) {
|
||||
if (typeof kind === "undefined") {
|
||||
kind = "let ";
|
||||
}
|
||||
var splitter = kind + arrayName;
|
||||
while (true) {
|
||||
var start = content.indexOf(splitter);
|
||||
if (start === -1) {
|
||||
break;
|
||||
}
|
||||
var pos = getNextStep(content, start, '=');
|
||||
if (pos === null) {
|
||||
break;
|
||||
} else if (pos < 0) {
|
||||
content = content.slice(-pos);
|
||||
continue;
|
||||
}
|
||||
pos = getNextStep(content, pos, '[');
|
||||
if (pos === null) {
|
||||
break;
|
||||
} else if (pos < 0) {
|
||||
content = content.slice(-pos);
|
||||
continue;
|
||||
}
|
||||
while (pos < content.length) {
|
||||
if (content[pos] === '"' || content[pos] === "'") {
|
||||
var stop = content[pos];
|
||||
do {
|
||||
if (content[pos] === '\\') {
|
||||
pos += 2;
|
||||
} else {
|
||||
pos += 1;
|
||||
}
|
||||
} while (pos < content.length &&
|
||||
(content[pos] !== stop || content[pos - 1] === '\\'));
|
||||
} else if (content[pos] === ']' &&
|
||||
pos + 1 < content.length &&
|
||||
content[pos + 1] === ';') {
|
||||
return content.slice(start, pos + 2);
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
content = content.slice(start + 1);
|
||||
}
|
||||
if (kind === "let ") {
|
||||
return extractArrayVariable(content, arrayName, "const ");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Stupid function extractor for variable.
|
||||
function extractVariable(content, varName, kind) {
|
||||
if (typeof kind === "undefined") {
|
||||
kind = "let ";
|
||||
}
|
||||
var splitter = kind + varName;
|
||||
while (true) {
|
||||
var start = content.indexOf(splitter);
|
||||
if (start === -1) {
|
||||
break;
|
||||
}
|
||||
var pos = getNextStep(content, start, '=');
|
||||
if (pos === null) {
|
||||
break;
|
||||
} else if (pos < 0) {
|
||||
content = content.slice(-pos);
|
||||
continue;
|
||||
}
|
||||
while (pos < content.length) {
|
||||
if (content[pos] === '"' || content[pos] === "'") {
|
||||
var stop = content[pos];
|
||||
do {
|
||||
if (content[pos] === '\\') {
|
||||
pos += 2;
|
||||
} else {
|
||||
pos += 1;
|
||||
}
|
||||
} while (pos < content.length &&
|
||||
(content[pos] !== stop || content[pos - 1] === '\\'));
|
||||
} else if (content[pos] === ';' || content[pos] === ',') {
|
||||
return content.slice(start, pos + 1);
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
content = content.slice(start + 1);
|
||||
}
|
||||
if (kind === "let ") {
|
||||
return extractVariable(content, varName, "const ");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function loadContent(content) {
|
||||
var Module = module.constructor;
|
||||
var m = new Module();
|
||||
|
@ -194,20 +18,6 @@ function readFile(filePath) {
|
|||
return fs.readFileSync(filePath, 'utf8');
|
||||
}
|
||||
|
||||
function loadThings(thingsToLoad, kindOfLoad, funcToCall, fileContent) {
|
||||
var content = '';
|
||||
for (var i = 0; i < thingsToLoad.length; ++i) {
|
||||
var tmp = funcToCall(fileContent, thingsToLoad[i]);
|
||||
if (tmp === null) {
|
||||
console.log('unable to find ' + kindOfLoad + ' "' + thingsToLoad[i] + '"');
|
||||
process.exit(1);
|
||||
}
|
||||
content += tmp;
|
||||
content += 'exports.' + thingsToLoad[i] + ' = ' + thingsToLoad[i] + ';';
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
function contentToDiffLine(key, value) {
|
||||
return `"${key}": "${value}",`;
|
||||
}
|
||||
|
@ -264,46 +74,6 @@ function lookForEntry(entry, data) {
|
|||
return null;
|
||||
}
|
||||
|
||||
function loadSearchJsAndIndex(searchJs, searchIndex, storageJs, crate) {
|
||||
if (searchIndex[searchIndex.length - 1].length === 0) {
|
||||
searchIndex.pop();
|
||||
}
|
||||
searchIndex.pop();
|
||||
var fullSearchIndex = searchIndex.join("\n") + '\nexports.rawSearchIndex = searchIndex;';
|
||||
searchIndex = loadContent(fullSearchIndex);
|
||||
var finalJS = "";
|
||||
|
||||
var arraysToLoad = ["itemTypes"];
|
||||
var variablesToLoad = ["MAX_LEV_DISTANCE", "MAX_RESULTS", "NO_TYPE_FILTER",
|
||||
"GENERICS_DATA", "NAME", "INPUTS_DATA", "OUTPUT_DATA",
|
||||
"TY_PRIMITIVE", "TY_KEYWORD",
|
||||
"levenshtein_row2"];
|
||||
// execQuery first parameter is built in getQuery (which takes in the search input).
|
||||
// execQuery last parameter is built in buildIndex.
|
||||
// buildIndex requires the hashmap from search-index.
|
||||
var functionsToLoad = ["buildHrefAndPath", "pathSplitter", "levenshtein", "validateResult",
|
||||
"buildIndex", "execQuery", "parseQuery", "createQueryResults",
|
||||
"isWhitespace", "isSpecialStartCharacter", "isStopCharacter",
|
||||
"parseInput", "getItemsBefore", "getNextElem", "createQueryElement",
|
||||
"isReturnArrow", "isPathStart", "getStringElem", "newParsedQuery",
|
||||
"itemTypeFromName", "isEndCharacter", "isErrorCharacter",
|
||||
"isIdentCharacter", "isSeparatorCharacter", "getIdentEndPosition",
|
||||
"checkExtraTypeFilterCharacters", "isWhitespaceCharacter"];
|
||||
|
||||
const functions = ["hasOwnPropertyRustdoc", "onEach"];
|
||||
ALIASES = {};
|
||||
finalJS += 'window = { "currentCrate": "' + crate + '", rootPath: "../" };\n';
|
||||
finalJS += loadThings(functions, 'function', extractFunction, storageJs);
|
||||
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);
|
||||
|
||||
return [loaded, index];
|
||||
}
|
||||
|
||||
// This function checks if `expected` has all the required fields needed for the checks.
|
||||
function checkNeededFields(fullPath, expected, error_text, queryName, position) {
|
||||
let fieldsToCheck;
|
||||
|
@ -359,8 +129,7 @@ function valueCheck(fullPath, expected, result, error_text, queryName) {
|
|||
'compared to EXPECTED');
|
||||
}
|
||||
} else if (expected !== null && typeof expected !== "undefined" &&
|
||||
expected.constructor == Object)
|
||||
{
|
||||
expected.constructor == Object) {
|
||||
for (const key in expected) {
|
||||
if (!expected.hasOwnProperty(key)) {
|
||||
continue;
|
||||
|
@ -382,21 +151,20 @@ function valueCheck(fullPath, expected, result, error_text, queryName) {
|
|||
}
|
||||
}
|
||||
|
||||
function runParser(query, expected, loaded, loadedFile, queryName) {
|
||||
function runParser(query, expected, parseQuery, queryName) {
|
||||
var error_text = [];
|
||||
checkNeededFields("", expected, error_text, queryName, null);
|
||||
if (error_text.length === 0) {
|
||||
valueCheck('', expected, loaded.parseQuery(query), error_text, queryName);
|
||||
valueCheck('', expected, parseQuery(query), error_text, queryName);
|
||||
}
|
||||
return error_text;
|
||||
}
|
||||
|
||||
function runSearch(query, expected, index, loaded, loadedFile, queryName) {
|
||||
const filter_crate = loadedFile.FILTER_CRATE;
|
||||
function runSearch(query, expected, doSearch, loadedFile, queryName) {
|
||||
const ignore_order = loadedFile.ignore_order;
|
||||
const exact_check = loadedFile.exact_check;
|
||||
|
||||
var results = loaded.execQuery(loaded.parseQuery(query), index, filter_crate);
|
||||
var results = doSearch(query, loadedFile.FILTER_CRATE);
|
||||
var error_text = [];
|
||||
|
||||
for (var key in expected) {
|
||||
|
@ -488,7 +256,7 @@ function runCheck(loadedFile, key, callback) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
function runChecks(testFile, loaded, index) {
|
||||
function runChecks(testFile, doSearch, parseQuery) {
|
||||
var checkExpected = false;
|
||||
var checkParsed = false;
|
||||
var testFileContent = readFile(testFile) + 'exports.QUERY = QUERY;';
|
||||
|
@ -518,24 +286,40 @@ function runChecks(testFile, loaded, index) {
|
|||
|
||||
if (checkExpected) {
|
||||
res += runCheck(loadedFile, "EXPECTED", (query, expected, text) => {
|
||||
return runSearch(query, expected, index, loaded, loadedFile, text);
|
||||
return runSearch(query, expected, doSearch, loadedFile, text);
|
||||
});
|
||||
}
|
||||
if (checkParsed) {
|
||||
res += runCheck(loadedFile, "PARSED", (query, expected, text) => {
|
||||
return runParser(query, expected, loaded, loadedFile, text);
|
||||
return runParser(query, expected, parseQuery, text);
|
||||
});
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function load_files(doc_folder, resource_suffix, crate) {
|
||||
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");
|
||||
/**
|
||||
* Load searchNNN.js and search-indexNNN.js.
|
||||
*
|
||||
* @param {string} doc_folder - Path to a folder generated by running rustdoc
|
||||
* @param {string} resource_suffix - Version number between filename and .js, e.g. "1.59.0"
|
||||
* @returns {Object} - Object containing two keys: `doSearch`, which runs a search
|
||||
* with the loaded index and returns a table of results; and `parseQuery`, which is the
|
||||
* `parseQuery` function exported from the search module.
|
||||
*/
|
||||
function loadSearchJS(doc_folder, resource_suffix) {
|
||||
const searchJs = path.join(doc_folder, "search" + resource_suffix + ".js");
|
||||
const searchIndexJs = path.join(doc_folder, "search-index" + resource_suffix + ".js");
|
||||
const searchIndex = require(searchIndexJs);
|
||||
const searchModule = require(searchJs);
|
||||
const searchWords = searchModule.initSearch(searchIndex.searchIndex);
|
||||
|
||||
return loadSearchJsAndIndex(searchJs, searchIndex, storageJs, crate);
|
||||
return {
|
||||
doSearch: function (queryStr, filterCrate, currentCrate) {
|
||||
return searchModule.execQuery(searchModule.parseQuery(queryStr), searchWords,
|
||||
filterCrate, currentCrate);
|
||||
},
|
||||
parseQuery: searchModule.parseQuery,
|
||||
}
|
||||
}
|
||||
|
||||
function showHelp() {
|
||||
|
@ -598,35 +382,34 @@ function parseOptions(args) {
|
|||
return null;
|
||||
}
|
||||
|
||||
function checkFile(test_file, opts, loaded, index) {
|
||||
const test_name = path.basename(test_file, ".js");
|
||||
|
||||
process.stdout.write('Checking "' + test_name + '" ... ');
|
||||
return runChecks(test_file, loaded, index);
|
||||
}
|
||||
|
||||
function main(argv) {
|
||||
var opts = parseOptions(argv.slice(2));
|
||||
if (opts === null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
var [loaded, index] = load_files(
|
||||
let parseAndSearch = loadSearchJS(
|
||||
opts["doc_folder"],
|
||||
opts["resource_suffix"],
|
||||
opts["crate_name"]);
|
||||
opts["resource_suffix"]);
|
||||
var errors = 0;
|
||||
|
||||
let doSearch = function (queryStr, filterCrate) {
|
||||
return parseAndSearch.doSearch(queryStr, filterCrate, opts["crate_name"]);
|
||||
};
|
||||
|
||||
if (opts["test_file"].length !== 0) {
|
||||
opts["test_file"].forEach(function(file) {
|
||||
errors += checkFile(file, opts, loaded, index);
|
||||
opts["test_file"].forEach(function (file) {
|
||||
process.stdout.write(`Testing ${file} ... `);
|
||||
errors += runChecks(file, doSearch, parseAndSearch.parseQuery);
|
||||
});
|
||||
} else if (opts["test_folder"].length !== 0) {
|
||||
fs.readdirSync(opts["test_folder"]).forEach(function(file) {
|
||||
fs.readdirSync(opts["test_folder"]).forEach(function (file) {
|
||||
if (!file.endsWith(".js")) {
|
||||
return;
|
||||
}
|
||||
errors += checkFile(path.join(opts["test_folder"], file), opts, loaded, index);
|
||||
process.stdout.write(`Testing ${file} ... `);
|
||||
errors += runChecks(path.join(opts["test_folder"], file), doSearch,
|
||||
parseAndSearch.parseQuery);
|
||||
});
|
||||
}
|
||||
return errors > 0 ? 1 : 0;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue