Skip to content

Commit 994c4a7

Browse files
Ensure we don't index when language doesn't match
Previously we pulled the language off what we indexed which could cause an issue when the search index was updated for a new language but the content had not been reloaded. Instead an index now knows its language. Additionally, ensure we wait for the languages of all content to match before indexing.
1 parent d4313dd commit 994c4a7

16 files changed

+87
-87
lines changed

src/documentation/ApiArea.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { useDocumentation } from "./documentation-hooks";
99

1010
const ApiArea = () => {
1111
const { api } = useDocumentation();
12-
return api ? <ApiDocumentation docs={api} /> : <Spinner />;
12+
return api ? <ApiDocumentation docs={api.content} /> : <Spinner />;
1313
};
1414

1515
export default ApiArea;

src/documentation/api/apidocs-util.test.ts

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,44 @@
33
*
44
* SPDX-License-Identifier: MIT
55
*/
6-
import { ApiDocsResponse } from "../../language-server/apidocs";
6+
import { ApiDocsContent } from "../../language-server/apidocs";
77
import { moduleAndApiFromId, pullModulesToTop } from "./apidocs-util";
88

99
describe("pullModulesToTop", () => {
1010
it("pulls modules up", () => {
11-
const mutated: ApiDocsResponse = {
12-
microbit: {
13-
kind: "module",
14-
fullName: "microbit",
15-
id: "microbit",
16-
name: "microbit",
17-
children: [
18-
{
19-
kind: "module",
20-
fullName: "microbit.compass",
21-
id: "microbit.compass",
22-
name: "compass",
23-
},
24-
{
25-
kind: "module",
26-
fullName: "microbit.display",
27-
id: "microbit.display",
28-
name: "display",
29-
},
30-
{
31-
kind: "variable",
32-
fullName: "microbit.display.foo",
33-
id: "foo",
34-
name: "foo",
35-
},
36-
],
11+
const mutated: ApiDocsContent = {
12+
languageId: "en",
13+
content: {
14+
microbit: {
15+
kind: "module",
16+
fullName: "microbit",
17+
id: "microbit",
18+
name: "microbit",
19+
children: [
20+
{
21+
kind: "module",
22+
fullName: "microbit.compass",
23+
id: "microbit.compass",
24+
name: "compass",
25+
},
26+
{
27+
kind: "module",
28+
fullName: "microbit.display",
29+
id: "microbit.display",
30+
name: "display",
31+
},
32+
{
33+
kind: "variable",
34+
fullName: "microbit.display.foo",
35+
id: "foo",
36+
name: "foo",
37+
},
38+
],
39+
},
3740
},
3841
};
3942
pullModulesToTop(mutated);
40-
expect(mutated).toEqual({
43+
expect(mutated.content).toEqual({
4144
microbit: {
4245
kind: "module",
4346
fullName: "microbit",

src/documentation/api/apidocs-util.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33
*
44
* SPDX-License-Identifier: MIT
55
*/
6-
import { ApiDocsEntry, ApiDocsResponse } from "../../language-server/apidocs";
6+
import {
7+
ApiDocsContent,
8+
ApiDocsEntry,
9+
ApiDocsResponse,
10+
} from "../../language-server/apidocs";
711

8-
export const pullModulesToTop = (input: ApiDocsResponse) => {
12+
export const pullModulesToTop = (input: ApiDocsContent) => {
913
const recurse = (docs: ApiDocsEntry[], topLevel: boolean) => {
1014
let removedSoFar = 0;
1115
[...docs].forEach((d, index) => {
1216
if (d.kind === "module" && !topLevel) {
13-
input[d.fullName] = d;
17+
input.content[d.fullName] = d;
1418
docs.splice(index - removedSoFar, 1);
1519
removedSoFar++;
1620
}
@@ -19,7 +23,7 @@ export const pullModulesToTop = (input: ApiDocsResponse) => {
1923
}
2024
});
2125
};
22-
recurse(Object.values(input), true);
26+
recurse(Object.values(input.content), true);
2327
};
2428

2529
export const resolveModule = (

src/documentation/documentation-hooks.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
useRef,
1414
useState,
1515
} from "react";
16-
import { apiDocs, ApiDocsResponse } from "../language-server/apidocs";
16+
import { apiDocs, ApiDocsContent } from "../language-server/apidocs";
1717
import { useLanguageServerClient } from "../language-server/language-server-hooks";
1818
import { useLogging } from "../logging/logging-hooks";
1919
import { useSettings } from "../settings/settings";
@@ -26,7 +26,7 @@ import { fetchReferenceToolkit } from "./reference/content";
2626
import { Toolkit } from "./reference/model";
2727

2828
export type ContentState<T> =
29-
| { status: "ok"; content: T }
29+
| { status: "ok"; content: T; languageId: string }
3030
| { status: "error" }
3131
| { status: "loading" };
3232

@@ -44,7 +44,7 @@ const useContent = <T,>(
4444
try {
4545
const content = await fetchContent(languageId);
4646
if (!ignore) {
47-
setState({ status: "ok", content });
47+
setState({ status: "ok", content, languageId });
4848
}
4949
} catch (e) {
5050
logging.error(e);
@@ -63,9 +63,9 @@ const useContent = <T,>(
6363
return state;
6464
};
6565

66-
const useApiDocumentation = (): ApiDocsResponse | undefined => {
66+
const useApiDocumentation = (): ApiDocsContent | undefined => {
6767
const client = useLanguageServerClient();
68-
const [apidocs, setApiDocs] = useState<ApiDocsResponse | undefined>();
68+
const [apidocs, setApiDocs] = useState<ApiDocsContent | undefined>();
6969
useEffect(() => {
7070
let ignore = false;
7171
const load = async () => {
@@ -86,7 +86,7 @@ const useApiDocumentation = (): ApiDocsResponse | undefined => {
8686
};
8787

8888
export interface DocumentationContextValue {
89-
api: ApiDocsResponse | undefined;
89+
api: ApiDocsContent | undefined;
9090
ideas: ContentState<Idea[]>;
9191
reference: ContentState<Toolkit>;
9292
apiReferenceMap: ContentState<ApiReferenceMap>;

src/documentation/search/search-hooks.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,13 @@ const SearchProvider = ({ children }: { children: ReactNode }) => {
5151
search.current = new WorkerSearch(languageId);
5252
setQuery("");
5353
}
54-
// Wait for both, no reason to index with just one then redo with both.
55-
if (reference.status === "ok" && api) {
56-
search.current.index(reference.content, api);
54+
// Wait for everything to be loaded and in the right language
55+
if (
56+
reference.status === "ok" &&
57+
reference.languageId === languageId &&
58+
api?.languageId === languageId
59+
) {
60+
search.current.index(reference.content, api.content);
5761
}
5862
}, [languageId, reference, api]);
5963

src/documentation/search/search.worker.de.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
import { SearchWorker } from "./search.worker";
77
import languageSupport from "@microbit/lunr-languages/lunr.de";
88

9-
new SearchWorker(self as DedicatedWorkerGlobalScope, languageSupport);
9+
new SearchWorker(self as DedicatedWorkerGlobalScope, "de", languageSupport);

src/documentation/search/search.worker.en.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
*/
66
import { SearchWorker } from "./search.worker";
77

8-
new SearchWorker(self as DedicatedWorkerGlobalScope, undefined);
8+
new SearchWorker(self as DedicatedWorkerGlobalScope, undefined, undefined);

src/documentation/search/search.worker.es.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
import { SearchWorker } from "./search.worker";
77
import languageSupport from "@microbit/lunr-languages/lunr.es";
88

9-
new SearchWorker(self as DedicatedWorkerGlobalScope, languageSupport);
9+
// Note the language code is different to the app
10+
new SearchWorker(self as DedicatedWorkerGlobalScope, "es", languageSupport);

src/documentation/search/search.worker.fr.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
import { SearchWorker } from "./search.worker";
77
import languageSupport from "@microbit/lunr-languages/lunr.fr";
88

9-
new SearchWorker(self as DedicatedWorkerGlobalScope, languageSupport);
9+
new SearchWorker(self as DedicatedWorkerGlobalScope, "fr", languageSupport);

src/documentation/search/search.worker.ja.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
import { SearchWorker } from "./search.worker";
77
import languageSupport from "@microbit/lunr-languages/lunr.ja";
88

9-
new SearchWorker(self as DedicatedWorkerGlobalScope, languageSupport);
9+
new SearchWorker(self as DedicatedWorkerGlobalScope, "ja", languageSupport);

src/documentation/search/search.worker.ko.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
import { SearchWorker } from "./search.worker";
77
import languageSupport from "@microbit/lunr-languages/lunr.ko";
88

9-
new SearchWorker(self as DedicatedWorkerGlobalScope, languageSupport);
9+
new SearchWorker(self as DedicatedWorkerGlobalScope, "ko", languageSupport);

src/documentation/search/search.worker.nl.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@
55
*/
66
import { SearchWorker } from "./search.worker";
77
import languageSupport from "@microbit/lunr-languages/lunr.nl";
8-
9-
new SearchWorker(self as DedicatedWorkerGlobalScope, languageSupport);
8+
if (!languageSupport) {
9+
throw new Error("Whoops!");
10+
}
11+
new SearchWorker(self as DedicatedWorkerGlobalScope, "nl", languageSupport);

src/documentation/search/search.worker.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,12 @@ describe("buildReferenceIndex", () => {
122122
...referenceEn,
123123
language: "fr",
124124
};
125-
const enIndex = await buildIndex(referenceEn, api, undefined);
125+
const enIndex = await buildIndex(referenceEn, api, undefined, undefined);
126126
expect(enIndex.search("topic").reference.length).toEqual(1);
127127
// "that" is an English stopword
128128
expect(enIndex.search("that").reference.length).toEqual(0);
129129

130-
const frIndex = await buildIndex(referenceFr, api, frLanguageSupport);
130+
const frIndex = await buildIndex(referenceFr, api, "fr", frLanguageSupport);
131131
expect(frIndex.search("topic").reference.length).toEqual(1);
132132
// "that" is not a French stopword
133133
expect(frIndex.search("that").reference.length).toEqual(1);
@@ -141,7 +141,7 @@ describe("SearchWorker", () => {
141141
postMessage,
142142
} as unknown as DedicatedWorkerGlobalScope;
143143

144-
new SearchWorker(ctx, undefined);
144+
new SearchWorker(ctx, undefined, undefined);
145145

146146
ctx.onmessage!(
147147
new MessageEvent("message", {
@@ -184,7 +184,7 @@ describe("SearchWorker", () => {
184184
postMessage,
185185
} as unknown as DedicatedWorkerGlobalScope;
186186

187-
new SearchWorker(ctx, undefined);
187+
new SearchWorker(ctx, undefined, undefined);
188188

189189
const emptyIndex: IndexMessage = {
190190
kind: "index",

src/documentation/search/search.worker.ts

Lines changed: 9 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -268,34 +268,34 @@ export const buildSearchIndex = (
268268
export const buildIndex = async (
269269
reference: Toolkit,
270270
api: ApiDocsResponse,
271+
lunrLanguage: LunrLanguage | undefined,
271272
languageSupport: ((l: typeof lunr) => void) | undefined
272273
): Promise<LunrSearch> => {
273-
const language = convertLangToLunrParam(reference.language);
274274
const plugins: lunr.Builder.Plugin[] = [];
275-
if (languageSupport && language) {
275+
if (languageSupport && lunrLanguage) {
276276
languageSupport(lunr);
277-
plugins.push(lunr[language]);
277+
plugins.push(lunr[lunrLanguage]);
278278
}
279279

280280
// There is always some degree of English content.
281281
const multiLanguages = ["en"];
282-
if (language) {
283-
multiLanguages.push(language);
282+
if (lunrLanguage) {
283+
multiLanguages.push(lunrLanguage);
284284
}
285285
const languagePlugin = lunr.multiLanguage(...multiLanguages);
286286

287287
return new LunrSearch(
288288
buildSearchIndex(
289289
referenceSearchableContent(reference),
290290
"reference",
291-
language,
291+
lunrLanguage,
292292
languagePlugin,
293293
...plugins
294294
),
295295
buildSearchIndex(
296296
apiSearchableContent(api),
297297
"api",
298-
language,
298+
lunrLanguage,
299299
languagePlugin,
300300
...plugins
301301
)
@@ -304,27 +304,6 @@ export const buildIndex = async (
304304

305305
type LunrLanguage = "de" | "es" | "fr" | "ja" | "nl" | "ko";
306306

307-
function convertLangToLunrParam(language: string): LunrLanguage | undefined {
308-
// See also workerForLanguage
309-
switch (language.toLowerCase()) {
310-
case "de":
311-
return "de";
312-
case "fr":
313-
return "fr";
314-
case "es-es":
315-
return "es";
316-
case "ja":
317-
return "ja";
318-
case "ko":
319-
return "ko";
320-
case "nl":
321-
return "nl";
322-
default:
323-
// No search support for the language, default to lunr's built-in English support.
324-
return undefined;
325-
}
326-
}
327-
328307
export class SearchWorker {
329308
private search: LunrSearch | undefined;
330309
// We block queries on indexing.
@@ -333,6 +312,7 @@ export class SearchWorker {
333312

334313
constructor(
335314
private ctx: DedicatedWorkerGlobalScope,
315+
private languageId: LunrLanguage | undefined,
336316
private languageSupport: ((l: typeof lunr) => void) | undefined
337317
) {
338318
// We return Promises here just to allow for easy testing.
@@ -358,6 +338,7 @@ export class SearchWorker {
358338
this.search = await buildIndex(
359339
message.reference,
360340
message.api,
341+
this.languageId,
361342
this.languageSupport
362343
);
363344
this.recordInitialization!();

src/language-server/apidocs.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ export interface ApiDocsEntry {
4444
params?: ApiDocsFunctionParameter[];
4545
}
4646

47+
export interface ApiDocsContent {
48+
languageId: string;
49+
content: ApiDocsResponse;
50+
}
51+
4752
export interface ApiDocsResponse extends Record<string, ApiDocsEntry> {}
4853

4954
export const apiDocsRequestType = new ProtocolRequestType<
@@ -56,10 +61,10 @@ export const apiDocsRequestType = new ProtocolRequestType<
5661

5762
export const apiDocs = async (
5863
client: LanguageServerClient
59-
): Promise<ApiDocsResponse> => {
64+
): Promise<ApiDocsContent> => {
6065
// This is a non-standard LSP call that we've added support for to Pyright.
6166
try {
62-
const result = await client.connection.sendRequest(apiDocsRequestType, {
67+
const content = await client.connection.sendRequest(apiDocsRequestType, {
6368
path: client.rootUri,
6469
documentationFormat: [MarkupKind.Markdown],
6570
modules: [
@@ -84,10 +89,10 @@ export const apiDocs = async (
8489
"time",
8590
],
8691
});
87-
return result;
92+
return { content, languageId: client.locale };
8893
} catch (e) {
8994
if (isErrorDueToDispose(e)) {
90-
return {};
95+
return { content: {}, languageId: client.locale };
9196
}
9297
throw e;
9398
}

src/language-server/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export class LanguageServerClient extends EventEmitter {
5757

5858
constructor(
5959
public connection: MessageConnection,
60-
private locale: string,
60+
public locale: string,
6161
public rootUri: string
6262
) {
6363
super();

0 commit comments

Comments
 (0)