Rollup merge of #90630 - GuillaumeGomez:improve-rustdoc-search, r=notriddle

Create real parser for search queries

You can test it [here](https://rustdoc.crud.net/imperio/improve-rustdoc-search/std/index.html).

This PR adds a real parser for the query engine in rustdoc. The parser is quite simple but it allows to makes query handling much easier. I added a new testsuite to ensure it works as expected and ran fuzzing checks on it for a few hours without problems.

So about the parser: as you can see in the screenshot, it handles recursive generics parsing. It also allows to set which item should use exact matching by adding double-quotes around it (look for `exact_search` in the screenshot).

Now about the query engine itself: I simplified it a lot thanks to the parsed query. It behaves mostly the same when there is only one argument, but is much more powerful when there are more than one.

When making this change, we also removed the support for multi-query.

PS: A big part of the PR is tests and test-related code. :)

r? `@camelid`
This commit is contained in:
Dylan DPC 2022-04-21 01:14:13 +02:00 committed by GitHub
commit 976c6b2d19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 2300 additions and 458 deletions

View file

@ -8,10 +8,34 @@ function initSearch(searchIndex){}
/**
* @typedef {{
* raw: string,
* query: string,
* type: string,
* id: string,
* name: string,
* fullPath: Array<string>,
* pathWithoutLast: Array<string>,
* pathLast: string,
* generics: Array<QueryElement>,
* }}
*/
var QueryElement;
/**
* @typedef {{
* pos: number,
* totalElems: number,
* typeFilter: (null|string),
* userQuery: string,
* }}
*/
var ParserState;
/**
* @typedef {{
* original: string,
* userQuery: string,
* typeFilter: number,
* elems: Array<QueryElement>,
* args: Array<QueryElement>,
* returned: Array<QueryElement>,
* foundElems: number,
* }}
*/
var ParsedQuery;
@ -30,3 +54,30 @@ var ParsedQuery;
* }}
*/
var Row;
/**
* @typedef {{
* in_args: Array<Object>,
* returned: Array<Object>,
* others: Array<Object>,
* query: ParsedQuery,
* }}
*/
var ResultsTable;
/**
* @typedef {{
* desc: string,
* displayPath: string,
* fullPath: string,
* href: string,
* id: number,
* lev: number,
* name: string,
* normalizedName: string,
* parent: (Object|undefined),
* path: string,
* ty: number,
* }}
*/
var Results;

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
// exact-check
const QUERY = 'hashmap';
const QUERY = '"hashmap"';
const FILTER_CRATE = 'core';
const EXPECTED = {

View file

@ -0,0 +1,365 @@
const QUERY = [
'<P>',
'-> <P>',
'a<"P">',
'"P" "P"',
'P "P"',
'"p" p',
'"const": p',
"a<:a>",
"a<::a>",
"((a))",
"(p -> p",
"::a::b",
"a::::b",
"a::b::",
":a",
"a b:",
"a (b:",
"_:",
"a-bb",
"a>bb",
"ab'",
"a->",
'"p" <a>',
'"p" a<a>',
"a,<",
"aaaaa<>b",
"fn:aaaaa<>b",
"->a<>b",
"a<->",
"a:: a",
"a ::a",
"a<a>:",
"a<>:",
"a,:",
" a<> :",
"mod : :",
];
const PARSED = [
{
elems: [],
foundElems: 0,
original: "<P>",
returned: [],
typeFilter: -1,
userQuery: "<p>",
error: "Found generics without a path",
},
{
elems: [],
foundElems: 0,
original: "-> <P>",
returned: [],
typeFilter: -1,
userQuery: "-> <p>",
error: "Found generics without a path",
},
{
elems: [],
foundElems: 0,
original: "a<\"P\">",
returned: [],
typeFilter: -1,
userQuery: "a<\"p\">",
error: "`\"` cannot be used in generics",
},
{
elems: [],
foundElems: 0,
original: "\"P\" \"P\"",
returned: [],
typeFilter: -1,
userQuery: "\"p\" \"p\"",
error: "Cannot have more than one literal search element",
},
{
elems: [],
foundElems: 0,
original: "P \"P\"",
returned: [],
typeFilter: -1,
userQuery: "p \"p\"",
error: "Cannot use literal search when there is more than one element",
},
{
elems: [],
foundElems: 0,
original: "\"p\" p",
returned: [],
typeFilter: -1,
userQuery: "\"p\" p",
error: "You cannot have more than one element if you use quotes",
},
{
elems: [],
foundElems: 0,
original: "\"const\": p",
returned: [],
typeFilter: -1,
userQuery: "\"const\": p",
error: "You cannot use quotes on type filter",
},
{
elems: [],
foundElems: 0,
original: "a<:a>",
returned: [],
typeFilter: -1,
userQuery: "a<:a>",
error: "Unexpected `:` after `<`",
},
{
elems: [],
foundElems: 0,
original: "a<::a>",
returned: [],
typeFilter: -1,
userQuery: "a<::a>",
error: "Unexpected `::`: paths cannot start with `::`",
},
{
elems: [],
foundElems: 0,
original: "((a))",
returned: [],
typeFilter: -1,
userQuery: "((a))",
error: "Unexpected `(`",
},
{
elems: [],
foundElems: 0,
original: "(p -> p",
returned: [],
typeFilter: -1,
userQuery: "(p -> p",
error: "Unexpected `(`",
},
{
elems: [],
foundElems: 0,
original: "::a::b",
returned: [],
typeFilter: -1,
userQuery: "::a::b",
error: "Paths cannot start with `::`",
},
{
elems: [],
foundElems: 0,
original: "a::::b",
returned: [],
typeFilter: -1,
userQuery: "a::::b",
error: "Unexpected `::::`",
},
{
elems: [],
foundElems: 0,
original: "a::b::",
returned: [],
typeFilter: -1,
userQuery: "a::b::",
error: "Paths cannot end with `::`",
},
{
elems: [],
foundElems: 0,
original: ":a",
returned: [],
typeFilter: -1,
userQuery: ":a",
error: "Expected type filter before `:`",
},
{
elems: [],
foundElems: 0,
original: "a b:",
returned: [],
typeFilter: -1,
userQuery: "a b:",
error: "Unexpected `:`",
},
{
elems: [],
foundElems: 0,
original: "a (b:",
returned: [],
typeFilter: -1,
userQuery: "a (b:",
error: "Unexpected `(`",
},
{
elems: [],
foundElems: 0,
original: "_:",
returned: [],
typeFilter: -1,
userQuery: "_:",
error: "Unknown type filter `_`",
},
{
elems: [],
foundElems: 0,
original: "a-bb",
returned: [],
typeFilter: -1,
userQuery: "a-bb",
error: "Unexpected `-` (did you mean `->`?)",
},
{
elems: [],
foundElems: 0,
original: "a>bb",
returned: [],
typeFilter: -1,
userQuery: "a>bb",
error: "Unexpected `>` (did you mean `->`?)",
},
{
elems: [],
foundElems: 0,
original: "ab'",
returned: [],
typeFilter: -1,
userQuery: "ab'",
error: "Unexpected `'`",
},
{
elems: [],
foundElems: 0,
original: "a->",
returned: [],
typeFilter: -1,
userQuery: "a->",
error: "Expected at least one item after `->`",
},
{
elems: [],
foundElems: 0,
original: '"p" <a>',
returned: [],
typeFilter: -1,
userQuery: '"p" <a>',
error: "Found generics without a path",
},
{
elems: [],
foundElems: 0,
original: '"p" a<a>',
returned: [],
typeFilter: -1,
userQuery: '"p" a<a>',
error: "You cannot have more than one element if you use quotes",
},
{
elems: [],
foundElems: 0,
original: 'a,<',
returned: [],
typeFilter: -1,
userQuery: 'a,<',
error: 'Found generics without a path',
},
{
elems: [],
foundElems: 0,
original: 'aaaaa<>b',
returned: [],
typeFilter: -1,
userQuery: 'aaaaa<>b',
error: 'Expected `,`, ` `, `:` or `->`, found `b`',
},
{
elems: [],
foundElems: 0,
original: 'fn:aaaaa<>b',
returned: [],
typeFilter: -1,
userQuery: 'fn:aaaaa<>b',
error: 'Expected `,`, ` ` or `->`, found `b`',
},
{
elems: [],
foundElems: 0,
original: '->a<>b',
returned: [],
typeFilter: -1,
userQuery: '->a<>b',
error: 'Expected `,` or ` `, found `b`',
},
{
elems: [],
foundElems: 0,
original: 'a<->',
returned: [],
typeFilter: -1,
userQuery: 'a<->',
error: 'Unexpected `-` after `<`',
},
{
elems: [],
foundElems: 0,
original: 'a:: a',
returned: [],
typeFilter: -1,
userQuery: 'a:: a',
error: 'Paths cannot end with `::`',
},
{
elems: [],
foundElems: 0,
original: 'a ::a',
returned: [],
typeFilter: -1,
userQuery: 'a ::a',
error: 'Paths cannot start with `::`',
},
{
elems: [],
foundElems: 0,
original: "a<a>:",
returned: [],
typeFilter: -1,
userQuery: "a<a>:",
error: 'Unexpected `:`',
},
{
elems: [],
foundElems: 0,
original: "a<>:",
returned: [],
typeFilter: -1,
userQuery: "a<>:",
error: 'Unexpected `<` in type filter',
},
{
elems: [],
foundElems: 0,
original: "a,:",
returned: [],
typeFilter: -1,
userQuery: "a,:",
error: 'Unexpected `,` in type filter',
},
{
elems: [],
foundElems: 0,
original: "a<> :",
returned: [],
typeFilter: -1,
userQuery: "a<> :",
error: 'Unexpected `<` in type filter',
},
{
elems: [],
foundElems: 0,
original: "mod : :",
returned: [],
typeFilter: -1,
userQuery: "mod : :",
error: 'Unexpected `:`',
},
];

View file

@ -0,0 +1,43 @@
const QUERY = ['fn:foo', 'enum : foo', 'macro<f>:foo'];
const PARSED = [
{
elems: [{
name: "foo",
fullPath: ["foo"],
pathWithoutLast: [],
pathLast: "foo",
generics: [],
}],
foundElems: 1,
original: "fn:foo",
returned: [],
typeFilter: 5,
userQuery: "fn:foo",
error: null,
},
{
elems: [{
name: "foo",
fullPath: ["foo"],
pathWithoutLast: [],
pathLast: "foo",
generics: [],
}],
foundElems: 1,
original: "enum : foo",
returned: [],
typeFilter: 4,
userQuery: "enum : foo",
error: null,
},
{
elems: [],
foundElems: 0,
original: "macro<f>:foo",
returned: [],
typeFilter: -1,
userQuery: "macro<f>:foo",
error: "Unexpected `:`",
},
];

View file

@ -0,0 +1,62 @@
const QUERY = ['A<B<C<D>, E>', 'p<> u8', '"p"<a>'];
const PARSED = [
{
elems: [],
foundElems: 0,
original: 'A<B<C<D>, E>',
returned: [],
typeFilter: -1,
userQuery: 'a<b<c<d>, e>',
error: 'Unexpected `<` after `<`',
},
{
elems: [
{
name: "p",
fullPath: ["p"],
pathWithoutLast: [],
pathLast: "p",
generics: [],
},
{
name: "u8",
fullPath: ["u8"],
pathWithoutLast: [],
pathLast: "u8",
generics: [],
},
],
foundElems: 2,
original: "p<> u8",
returned: [],
typeFilter: -1,
userQuery: "p<> u8",
error: null,
},
{
elems: [
{
name: "p",
fullPath: ["p"],
pathWithoutLast: [],
pathLast: "p",
generics: [
{
name: "a",
fullPath: ["a"],
pathWithoutLast: [],
pathLast: "a",
generics: [],
},
],
},
],
foundElems: 1,
original: '"p"<a>',
returned: [],
typeFilter: -1,
userQuery: '"p"<a>',
error: null,
},
];

View file

@ -0,0 +1,27 @@
const QUERY = ['R<P>'];
const PARSED = [
{
elems: [{
name: "r",
fullPath: ["r"],
pathWithoutLast: [],
pathLast: "r",
generics: [
{
name: "p",
fullPath: ["p"],
pathWithoutLast: [],
pathLast: "p",
generics: [],
},
],
}],
foundElems: 1,
original: "R<P>",
returned: [],
typeFilter: -1,
userQuery: "r<p>",
error: null,
}
];

View file

@ -0,0 +1,90 @@
const QUERY = ['A::B', 'A::B,C', 'A::B<f>,C', 'mod::a'];
const PARSED = [
{
elems: [{
name: "a::b",
fullPath: ["a", "b"],
pathWithoutLast: ["a"],
pathLast: "b",
generics: [],
}],
foundElems: 1,
original: "A::B",
returned: [],
typeFilter: -1,
userQuery: "a::b",
error: null,
},
{
elems: [
{
name: "a::b",
fullPath: ["a", "b"],
pathWithoutLast: ["a"],
pathLast: "b",
generics: [],
},
{
name: "c",
fullPath: ["c"],
pathWithoutLast: [],
pathLast: "c",
generics: [],
},
],
foundElems: 2,
original: 'A::B,C',
returned: [],
typeFilter: -1,
userQuery: 'a::b,c',
error: null,
},
{
elems: [
{
name: "a::b",
fullPath: ["a", "b"],
pathWithoutLast: ["a"],
pathLast: "b",
generics: [
{
name: "f",
fullPath: ["f"],
pathWithoutLast: [],
pathLast: "f",
generics: [],
},
],
},
{
name: "c",
fullPath: ["c"],
pathWithoutLast: [],
pathLast: "c",
generics: [],
},
],
foundElems: 2,
original: 'A::B<f>,C',
returned: [],
typeFilter: -1,
userQuery: 'a::b<f>,c',
error: null,
},
{
elems: [{
name: "mod::a",
fullPath: ["mod", "a"],
pathWithoutLast: ["mod"],
pathLast: "a",
generics: [],
}],
foundElems: 1,
original: "mod::a",
returned: [],
typeFilter: -1,
userQuery: "mod::a",
error: null,
},
];

View file

@ -0,0 +1,87 @@
const QUERY = [
'-> "p"',
'"p",',
'"p" -> a',
'"a" -> "p"',
'->"-"',
'"a',
'""',
];
const PARSED = [
{
elems: [],
foundElems: 1,
original: '-> "p"',
returned: [{
name: "p",
fullPath: ["p"],
pathWithoutLast: [],
pathLast: "p",
generics: [],
}],
typeFilter: -1,
userQuery: '-> "p"',
error: null,
},
{
elems: [{
name: "p",
fullPath: ["p"],
pathWithoutLast: [],
pathLast: "p",
generics: [],
}],
foundElems: 1,
original: '"p",',
returned: [],
typeFilter: -1,
userQuery: '"p",',
error: null,
},
{
elems: [],
foundElems: 0,
original: '"p" -> a',
returned: [],
typeFilter: -1,
userQuery: '"p" -> a',
error: "You cannot have more than one element if you use quotes",
},
{
elems: [],
foundElems: 0,
original: '"a" -> "p"',
returned: [],
typeFilter: -1,
userQuery: '"a" -> "p"',
error: "Cannot have more than one literal search element",
},
{
elems: [],
foundElems: 0,
original: '->"-"',
returned: [],
typeFilter: -1,
userQuery: '->"-"',
error: 'Unexpected `-` in a string element',
},
{
elems: [],
foundElems: 0,
original: '"a',
returned: [],
typeFilter: -1,
userQuery: '"a',
error: 'Unclosed `"`',
},
{
elems: [],
foundElems: 0,
original: '""',
returned: [],
typeFilter: -1,
userQuery: '""',
error: 'Cannot have empty string element',
},
];

View file

@ -0,0 +1,78 @@
const QUERY = ['-> F<P>', '-> P', '->,a', 'aaaaa->a'];
const PARSED = [
{
elems: [],
foundElems: 1,
original: "-> F<P>",
returned: [{
name: "f",
fullPath: ["f"],
pathWithoutLast: [],
pathLast: "f",
generics: [
{
name: "p",
fullPath: ["p"],
pathWithoutLast: [],
pathLast: "p",
generics: [],
},
],
}],
typeFilter: -1,
userQuery: "-> f<p>",
error: null,
},
{
elems: [],
foundElems: 1,
original: "-> P",
returned: [{
name: "p",
fullPath: ["p"],
pathWithoutLast: [],
pathLast: "p",
generics: [],
}],
typeFilter: -1,
userQuery: "-> p",
error: null,
},
{
elems: [],
foundElems: 1,
original: "->,a",
returned: [{
name: "a",
fullPath: ["a"],
pathWithoutLast: [],
pathLast: "a",
generics: [],
}],
typeFilter: -1,
userQuery: "->,a",
error: null,
},
{
elems: [{
name: "aaaaa",
fullPath: ["aaaaa"],
pathWithoutLast: [],
pathLast: "aaaaa",
generics: [],
}],
foundElems: 2,
original: "aaaaa->a",
returned: [{
name: "a",
fullPath: ["a"],
pathWithoutLast: [],
pathLast: "a",
generics: [],
}],
typeFilter: -1,
userQuery: "aaaaa->a",
error: null,
},
];

View file

@ -0,0 +1,206 @@
// ignore-tidy-tab
const QUERY = [
'aaaaaa b',
'a b',
'a,b',
'a\tb',
'a<b c>',
'a<b,c>',
'a<b\tc>',
];
const PARSED = [
{
elems: [
{
name: 'aaaaaa',
fullPath: ['aaaaaa'],
pathWithoutLast: [],
pathLast: 'aaaaaa',
generics: [],
},
{
name: 'b',
fullPath: ['b'],
pathWithoutLast: [],
pathLast: 'b',
generics: [],
},
],
foundElems: 2,
original: "aaaaaa b",
returned: [],
typeFilter: -1,
userQuery: "aaaaaa b",
error: null,
},
{
elems: [
{
name: 'a',
fullPath: ['a'],
pathWithoutLast: [],
pathLast: 'a',
generics: [],
},
{
name: 'b',
fullPath: ['b'],
pathWithoutLast: [],
pathLast: 'b',
generics: [],
},
],
foundElems: 2,
original: "a b",
returned: [],
typeFilter: -1,
userQuery: "a b",
error: null,
},
{
elems: [
{
name: 'a',
fullPath: ['a'],
pathWithoutLast: [],
pathLast: 'a',
generics: [],
},
{
name: 'b',
fullPath: ['b'],
pathWithoutLast: [],
pathLast: 'b',
generics: [],
},
],
foundElems: 2,
original: "a,b",
returned: [],
typeFilter: -1,
userQuery: "a,b",
error: null,
},
{
elems: [
{
name: 'a',
fullPath: ['a'],
pathWithoutLast: [],
pathLast: 'a',
generics: [],
},
{
name: 'b',
fullPath: ['b'],
pathWithoutLast: [],
pathLast: 'b',
generics: [],
},
],
foundElems: 2,
original: "a\tb",
returned: [],
typeFilter: -1,
userQuery: "a\tb",
error: null,
},
{
elems: [
{
name: 'a',
fullPath: ['a'],
pathWithoutLast: [],
pathLast: 'a',
generics: [
{
name: 'b',
fullPath: ['b'],
pathWithoutLast: [],
pathLast: 'b',
generics: [],
},
{
name: 'c',
fullPath: ['c'],
pathWithoutLast: [],
pathLast: 'c',
generics: [],
},
],
},
],
foundElems: 1,
original: "a<b c>",
returned: [],
typeFilter: -1,
userQuery: "a<b c>",
error: null,
},
{
elems: [
{
name: 'a',
fullPath: ['a'],
pathWithoutLast: [],
pathLast: 'a',
generics: [
{
name: 'b',
fullPath: ['b'],
pathWithoutLast: [],
pathLast: 'b',
generics: [],
},
{
name: 'c',
fullPath: ['c'],
pathWithoutLast: [],
pathLast: 'c',
generics: [],
},
],
},
],
foundElems: 1,
original: "a<b,c>",
returned: [],
typeFilter: -1,
userQuery: "a<b,c>",
error: null,
},
{
elems: [
{
name: 'a',
fullPath: ['a'],
pathWithoutLast: [],
pathLast: 'a',
generics: [
{
name: 'b',
fullPath: ['b'],
pathWithoutLast: [],
pathLast: 'b',
generics: [],
},
{
name: 'c',
fullPath: ['c'],
pathWithoutLast: [],
pathLast: 'c',
generics: [],
},
],
},
],
foundElems: 1,
original: "a<b\tc>",
returned: [],
typeFilter: -1,
userQuery: "a<b\tc>",
error: null,
},
];

View file

@ -0,0 +1,123 @@
// This test is mostly to check that the parser still kinda outputs something
// (and doesn't enter an infinite loop!) even though the query is completely
// invalid.
const QUERY = [
'a b',
'a b',
'a,b(c)',
'aaa,a',
',,,,',
'mod :',
'mod\t:',
];
const PARSED = [
{
elems: [
{
name: "a",
fullPath: ["a"],
pathWithoutLast: [],
pathLast: "a",
generics: [],
},
{
name: "b",
fullPath: ["b"],
pathWithoutLast: [],
pathLast: "b",
generics: [],
},
],
foundElems: 2,
original: "a b",
returned: [],
typeFilter: -1,
userQuery: "a b",
error: null,
},
{
elems: [
{
name: "a",
fullPath: ["a"],
pathWithoutLast: [],
pathLast: "a",
generics: [],
},
{
name: "b",
fullPath: ["b"],
pathWithoutLast: [],
pathLast: "b",
generics: [],
},
],
foundElems: 2,
original: "a b",
returned: [],
typeFilter: -1,
userQuery: "a b",
error: null,
},
{
elems: [],
foundElems: 0,
original: "a,b(c)",
returned: [],
typeFilter: -1,
userQuery: "a,b(c)",
error: "Unexpected `(`",
},
{
elems: [
{
name: "aaa",
fullPath: ["aaa"],
pathWithoutLast: [],
pathLast: "aaa",
generics: [],
},
{
name: "a",
fullPath: ["a"],
pathWithoutLast: [],
pathLast: "a",
generics: [],
},
],
foundElems: 2,
original: "aaa,a",
returned: [],
typeFilter: -1,
userQuery: "aaa,a",
error: null,
},
{
elems: [],
foundElems: 0,
original: ",,,,",
returned: [],
typeFilter: -1,
userQuery: ",,,,",
error: null,
},
{
elems: [],
foundElems: 0,
original: 'mod :',
returned: [],
typeFilter: 0,
userQuery: 'mod :',
error: null,
},
{
elems: [],
foundElems: 0,
original: 'mod\t:',
returned: [],
typeFilter: 0,
userQuery: 'mod\t:',
error: null,
},
];

View file

@ -1,4 +1,7 @@
// ignore-order
const QUERY = '"error"';
const FILTER_CRATE = 'std';
const EXPECTED = {
'others': [
@ -6,7 +9,12 @@ const EXPECTED = {
{ 'path': 'std::fmt', 'name': 'Error' },
{ 'path': 'std::io', 'name': 'Error' },
],
'in_args': [],
'in_args': [
{ 'path': 'std::fmt::Error', 'name': 'eq' },
{ 'path': 'std::fmt::Error', 'name': 'cmp' },
{ 'path': 'std::fmt::Error', 'name': 'partial_cmp' },
],
'returned': [
{ 'path': 'std::fmt::LowerExp', 'name': 'fmt' },
],

View file

@ -1,8 +1,8 @@
const QUERY = 'struct:Vec';
const QUERY = 'struct:VecD';
const EXPECTED = {
'others': [
{ 'path': 'std::vec', 'name': 'Vec' },
{ 'path': 'std::collections', 'name': 'VecDeque' },
{ 'path': 'std::vec', 'name': 'Vec' },
],
};

View file

@ -1,6 +1,7 @@
// exact-check
const QUERY = 'macro:print';
const FILTER_CRATE = 'std';
const EXPECTED = {
'others': [
@ -9,6 +10,8 @@ const EXPECTED = {
{ 'path': 'std', 'name': 'println' },
{ 'path': 'std', 'name': 'eprintln' },
{ 'path': 'std::pin', 'name': 'pin' },
{ 'path': 'core::pin', 'name': 'pin' },
{ 'path': 'std::future', 'name': 'join' },
{ 'path': 'std', 'name': 'line' },
{ 'path': 'std', 'name': 'write' },
],
};

View file

@ -4,6 +4,6 @@ const EXPECTED = {
'others': [
{ 'path': 'std::vec::Vec', 'name': 'new' },
{ 'path': 'std::vec::Vec', 'name': 'ne' },
{ 'path': 'std::rc::Rc', 'name': 'ne' },
{ 'path': 'alloc::vec::Vec', 'name': 'ne' },
],
};

View file

@ -1,6 +1,6 @@
// exact-check
const QUERY = 'true';
const QUERY = '"true"';
const FILTER_CRATE = 'doc_alias_filter';

View file

@ -27,6 +27,7 @@ const QUERY = [
const EXPECTED = [
{
// StructItem
'others': [
{
'path': 'doc_alias',
@ -38,6 +39,7 @@ const EXPECTED = [
],
},
{
// StructFieldItem
'others': [
{
'path': 'doc_alias::Struct',
@ -49,6 +51,7 @@ const EXPECTED = [
],
},
{
// StructMethodItem
'others': [
{
'path': 'doc_alias::Struct',
@ -76,6 +79,7 @@ const EXPECTED = [
],
},
{
// ImplTraitFunction
'others': [
{
'path': 'doc_alias::Struct',
@ -87,6 +91,7 @@ const EXPECTED = [
],
},
{
// EnumItem
'others': [
{
'path': 'doc_alias',
@ -98,6 +103,7 @@ const EXPECTED = [
],
},
{
// VariantItem
'others': [
{
'path': 'doc_alias::Enum',
@ -109,6 +115,7 @@ const EXPECTED = [
],
},
{
// EnumMethodItem
'others': [
{
'path': 'doc_alias::Enum',
@ -120,6 +127,7 @@ const EXPECTED = [
],
},
{
// TypedefItem
'others': [
{
'path': 'doc_alias',
@ -131,6 +139,7 @@ const EXPECTED = [
],
},
{
// TraitItem
'others': [
{
'path': 'doc_alias',
@ -142,6 +151,7 @@ const EXPECTED = [
],
},
{
// TraitTypeItem
'others': [
{
'path': 'doc_alias::Trait',
@ -153,6 +163,7 @@ const EXPECTED = [
],
},
{
// AssociatedConstItem
'others': [
{
'path': 'doc_alias::Trait',
@ -164,6 +175,7 @@ const EXPECTED = [
],
},
{
// TraitFunctionItem
'others': [
{
'path': 'doc_alias::Trait',
@ -175,6 +187,7 @@ const EXPECTED = [
],
},
{
// FunctionItem
'others': [
{
'path': 'doc_alias',
@ -186,6 +199,7 @@ const EXPECTED = [
],
},
{
// ModuleItem
'others': [
{
'path': 'doc_alias',
@ -197,6 +211,7 @@ const EXPECTED = [
],
},
{
// ConstItem
'others': [
{
'path': 'doc_alias',
@ -212,6 +227,7 @@ const EXPECTED = [
],
},
{
// StaticItem
'others': [
{
'path': 'doc_alias',
@ -223,6 +239,7 @@ const EXPECTED = [
],
},
{
// UnionItem
'others': [
{
'path': 'doc_alias',
@ -240,6 +257,7 @@ const EXPECTED = [
],
},
{
// UnionFieldItem
'others': [
{
'path': 'doc_alias::Union',
@ -251,6 +269,7 @@ const EXPECTED = [
],
},
{
// UnionMethodItem
'others': [
{
'path': 'doc_alias::Union',
@ -262,6 +281,7 @@ const EXPECTED = [
],
},
{
// MacroItem
'others': [
{
'path': 'doc_alias',

View file

@ -1,16 +1,18 @@
// exact-check
const QUERY = [
'"R<P>"',
'R<P>',
'"P"',
'P',
'"ExtraCreditStructMulti<ExtraCreditInnerMulti, ExtraCreditInnerMulti>"',
'ExtraCreditStructMulti<ExtraCreditInnerMulti, ExtraCreditInnerMulti>',
'TraitCat',
'TraitDog',
'Result<String>',
];
const EXPECTED = [
{
// R<P>
'returned': [
{ 'path': 'generics', 'name': 'alef' },
],
@ -19,6 +21,7 @@ const EXPECTED = [
],
},
{
// "P"
'others': [
{ 'path': 'generics', 'name': 'P' },
],
@ -30,29 +33,41 @@ const EXPECTED = [
],
},
{
// P
'returned': [
{ 'path': 'generics', 'name': 'alef' },
{ 'path': 'generics', 'name': 'bet' },
],
'in_args': [
{ 'path': 'generics', 'name': 'alpha' },
{ 'path': 'generics', 'name': 'beta' },
],
},
{
// "ExtraCreditStructMulti"<ExtraCreditInnerMulti, ExtraCreditInnerMulti>
'in_args': [
{ 'path': 'generics', 'name': 'extracreditlabhomework' },
],
'returned': [],
},
{
// TraitCat
'in_args': [
{ 'path': 'generics', 'name': 'gamma' },
],
},
{
// TraitDog
'in_args': [
{ 'path': 'generics', 'name': 'gamma' },
],
},
{
// Result<String>
'others': [],
'returned': [
{ 'path': 'generics', 'name': 'super_soup' },
],
'in_args': [
{ 'path': 'generics', 'name': 'super_soup' },
],
},
];

View file

@ -24,3 +24,5 @@ pub trait TraitCat {}
pub trait TraitDog {}
pub fn gamma<T: TraitCat + TraitDog>(t: T) {}
pub fn super_soup(s: Result<String, i32>) -> Result<String, i32> { s }

View file

@ -58,7 +58,8 @@ function extractFunction(content, functionName) {
} while (pos < content.length && content[pos] !== '/' && content[pos - 1] !== '*');
// Eat quoted strings
} else if (content[pos] === '"' || content[pos] === "'" || content[pos] === "`") {
} else if ((content[pos] === '"' || content[pos] === "'" || content[pos] === "`") &&
(pos === 0 || content[pos - 1] !== '/')) {
stop = content[pos];
do {
if (content[pos] === '\\') {
@ -269,8 +270,13 @@ function loadSearchJsAndIndex(searchJs, searchIndex, storageJs, crate) {
// execQuery last parameter is built in buildIndex.
// buildIndex requires the hashmap from search-index.
var functionsToLoad = ["buildHrefAndPath", "pathSplitter", "levenshtein", "validateResult",
"handleAliases", "getQuery", "buildIndex", "execQuery", "execSearch",
"removeEmptyStringsFromArray"];
"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 = {};
@ -286,12 +292,99 @@ function loadSearchJsAndIndex(searchJs, searchIndex, storageJs, crate) {
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;
if (fullPath.length === 0) {
fieldsToCheck = [
"foundElems",
"original",
"returned",
"typeFilter",
"userQuery",
"error",
];
} else if (fullPath.endsWith("elems") || fullPath.endsWith("generics")) {
fieldsToCheck = [
"name",
"fullPath",
"pathWithoutLast",
"pathLast",
"generics",
];
} else {
fieldsToCheck = [];
}
for (var i = 0; i < fieldsToCheck.length; ++i) {
const field = fieldsToCheck[i];
if (!expected.hasOwnProperty(field)) {
let text = `${queryName}==> Mandatory key \`${field}\` is not present`;
if (fullPath.length > 0) {
text += ` in field \`${fullPath}\``;
if (position != null) {
text += ` (position ${position})`;
}
}
error_text.push(text);
}
}
}
function valueCheck(fullPath, expected, result, error_text, queryName) {
if (Array.isArray(expected)) {
for (var i = 0; i < expected.length; ++i) {
checkNeededFields(fullPath, expected[i], error_text, queryName, i);
if (i >= result.length) {
error_text.push(`${queryName}==> EXPECTED has extra value in array from field ` +
`\`${fullPath}\` (position ${i}): \`${JSON.stringify(expected[i])}\``);
} else {
valueCheck(fullPath + '[' + i + ']', expected[i], result[i], error_text, queryName);
}
}
for (; i < result.length; ++i) {
error_text.push(`${queryName}==> RESULT has extra value in array from field ` +
`\`${fullPath}\` (position ${i}): \`${JSON.stringify(result[i])}\` ` +
'compared to EXPECTED');
}
} else if (expected !== null && typeof expected !== "undefined" &&
expected.constructor == Object)
{
for (const key in expected) {
if (!expected.hasOwnProperty(key)) {
continue;
}
if (!result.hasOwnProperty(key)) {
error_text.push('==> Unknown key "' + key + '"');
break;
}
const obj_path = fullPath + (fullPath.length > 0 ? '.' : '') + key;
valueCheck(obj_path, expected[key], result[key], error_text, queryName);
}
} else {
expectedValue = JSON.stringify(expected);
resultValue = JSON.stringify(result);
if (expectedValue != resultValue) {
error_text.push(`${queryName}==> Different values for field \`${fullPath}\`:\n` +
`EXPECTED: \`${expectedValue}\`\nRESULT: \`${resultValue}\``);
}
}
}
function runParser(query, expected, loaded, loadedFile, queryName) {
var error_text = [];
checkNeededFields("", expected, error_text, queryName, null);
if (error_text.length === 0) {
valueCheck('', expected, loaded.parseQuery(query), error_text, queryName);
}
return error_text;
}
function runSearch(query, expected, index, loaded, loadedFile, queryName) {
const filter_crate = loadedFile.FILTER_CRATE;
const ignore_order = loadedFile.ignore_order;
const exact_check = loadedFile.exact_check;
var results = loaded.execSearch(loaded.getQuery(query), index, filter_crate);
var results = loaded.execQuery(loaded.parseQuery(query), index, filter_crate);
var error_text = [];
for (var key in expected) {
@ -353,40 +446,75 @@ function checkResult(error_text, loadedFile, displaySuccess) {
return 1;
}
function runChecks(testFile, loaded, index) {
var testFileContent = readFile(testFile) + 'exports.QUERY = QUERY;exports.EXPECTED = EXPECTED;';
if (testFileContent.indexOf("FILTER_CRATE") !== -1) {
testFileContent += "exports.FILTER_CRATE = FILTER_CRATE;";
} else {
testFileContent += "exports.FILTER_CRATE = null;";
}
var loadedFile = loadContent(testFileContent);
const expected = loadedFile.EXPECTED;
function runCheck(loadedFile, key, callback) {
const expected = loadedFile[key];
const query = loadedFile.QUERY;
if (Array.isArray(query)) {
if (!Array.isArray(expected)) {
console.log("FAILED");
console.log("==> If QUERY variable is an array, EXPECTED should be an array too");
console.log(`==> If QUERY variable is an array, ${key} should be an array too`);
return 1;
} else if (query.length !== expected.length) {
console.log("FAILED");
console.log("==> QUERY variable should have the same length as EXPECTED");
console.log(`==> QUERY variable should have the same length as ${key}`);
return 1;
}
for (var i = 0; i < query.length; ++i) {
var error_text = runSearch(query[i], expected[i], index, loaded, loadedFile,
"[ query `" + query[i] + "`]");
var error_text = callback(query[i], expected[i], "[ query `" + query[i] + "`]");
if (checkResult(error_text, loadedFile, false) !== 0) {
return 1;
}
}
console.log("OK");
return 0;
} else {
var error_text = callback(query, expected, "");
if (checkResult(error_text, loadedFile, true) !== 0) {
return 1;
}
}
var error_text = runSearch(query, expected, index, loaded, loadedFile, "");
return checkResult(error_text, loadedFile, true);
return 0;
}
function runChecks(testFile, loaded, index) {
var checkExpected = false;
var checkParsed = false;
var testFileContent = readFile(testFile) + 'exports.QUERY = QUERY;';
if (testFileContent.indexOf("FILTER_CRATE") !== -1) {
testFileContent += "exports.FILTER_CRATE = FILTER_CRATE;";
} else {
testFileContent += "exports.FILTER_CRATE = null;";
}
if (testFileContent.indexOf("\nconst EXPECTED") !== -1) {
testFileContent += 'exports.EXPECTED = EXPECTED;';
checkExpected = true;
}
if (testFileContent.indexOf("\nconst PARSED") !== -1) {
testFileContent += 'exports.PARSED = PARSED;';
checkParsed = true;
}
if (!checkParsed && !checkExpected) {
console.log("FAILED");
console.log("==> At least `PARSED` or `EXPECTED` is needed!");
return 1;
}
const loadedFile = loadContent(testFileContent);
var res = 0;
if (checkExpected) {
res += runCheck(loadedFile, "EXPECTED", (query, expected, text) => {
return runSearch(query, expected, index, loaded, loadedFile, text);
});
}
if (checkParsed) {
res += runCheck(loadedFile, "PARSED", (query, expected, text) => {
return runParser(query, expected, loaded, loadedFile, text);
});
}
return res;
}
function load_files(doc_folder, resource_suffix, crate) {