Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// @vitest-environment happy-dom

import { afterEach, describe, expect, it, vi } from 'vitest';
import prism, { loadLanguage } from '../index';
import { loadedLanguages } from '../loadLanguage';

function resetCppState() {
for (const lang of ['c', 'cpp']) {
delete (prism.languages as Record<string, unknown>)[lang];
loadedLanguages.delete(lang);
}
}

afterEach(() => {
vi.restoreAllMocks();
});

describe('loadLanguage dependency order', () => {
it('loads a dependency before the dependent grammar that extends it', async () => {
resetCppState();

const addOrder: string[] = [];
const realAdd = loadedLanguages.add.bind(loadedLanguages);
vi.spyOn(loadedLanguages, 'add').mockImplementation((lang: string) => {
addOrder.push(lang);
return realAdd(lang);
});

await expect(loadLanguage('cpp')).resolves.toBeDefined();

expect(addOrder).toContain('c');
expect(addOrder).toContain('cpp');
expect(addOrder.indexOf('c')).toBeLessThan(addOrder.indexOf('cpp'));
expect(prism.languages.cpp).toBeTruthy();
expect(() => prism.tokenize('int main(){}', prism.languages.cpp)).not.toThrow();
});
});
76 changes: 44 additions & 32 deletions third_party/muya/src/utils/prism/loadLanguage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import components from 'prismjs/components.js';
import getLoader from 'prismjs/dependencies';
import { getDefer } from '../index';

interface ILangLoadStatus {
lang: string;
Expand All @@ -19,10 +18,28 @@ export const loadedLanguages = new Set([
]);

const { languages } = components;
const languageModules = import.meta.glob([
'../../../../../prismjs/components/prism-*.js',
'!../../../../../prismjs/components/prism-*.min.js',
]) as Record<string, () => Promise<unknown>>;
// Two glob shapes on purpose: the importer path differs between Muya's own
// vitest/build context (three levels up to third_party/muya/node_modules) and
// the Android app build, where this file compiles from the installed
// @muyajs/core copy and prismjs resolves five levels up beside it in the
// pnpm store. Each shape matches nothing in the other context, so keep them
// as separate globs (one array poisons resolution for both) and merge.
const languageModules = {
...import.meta.glob([
'../../../../../prismjs/components/prism-*.js',
'!../../../../../prismjs/components/prism-*.min.js',
]),
...import.meta.glob([
'../../../node_modules/prismjs/components/prism-*.js',
'!../../../node_modules/prismjs/components/prism-*.min.js',
]),
} as Record<string, () => Promise<unknown>>;

function getLanguageModule(lang: string) {
const modulePath = `../../../node_modules/prismjs/components/prism-${lang}.js`;
return languageModules[modulePath]
?? Object.entries(languageModules).find(([path]) => path.endsWith(`/prism-${lang}.js`))?.[1];
}

// Look for the origin language by alias
export function transformAliasToOrigin(langs: string[]) {
Expand Down Expand Up @@ -81,43 +98,38 @@ function initLoadLanguage(Prism: IPrismLike) {
if (!Array.isArray(langs))
langs = [langs];

const promises: Promise<ILangLoadStatus>[] = [];
const statuses: ILangLoadStatus[] = [];
// The user might have loaded languages via some other way or used `prism.js` which already includes some
// We don't need to validate the ids because `getLoader` will ignore invalid ones
const loaded = [...loadedLanguages, ...Object.keys(Prism.languages)];
getLoader(components, langs, loaded).load(async (lang: string) => {
const defer = getDefer<ILangLoadStatus>();
promises.push(defer.promise);
const loadComponent = async (lang: string): Promise<void> => {
if (!(lang in components.languages)) {
defer.resolve({
lang,
status: 'noexist',
});
statuses.push({ lang, status: 'noexist' });
return;
}
else if (loadedLanguages.has(lang)) {
defer.resolve({
lang,
status: 'cached',
});
if (loadedLanguages.has(lang)) {
statuses.push({ lang, status: 'cached' });
return;
}
else {
delete Prism.languages[lang];
const modulePath = `../../../../../prismjs/components/prism-${lang}.js`;
const loadLanguageModule = languageModules[modulePath];
if (!loadLanguageModule) {
throw new Error(`Prism language module not found: ${lang}`);
}

await loadLanguageModule();
defer.resolve({
lang,
status: 'loaded',
});
loadedLanguages.add(lang);
delete Prism.languages[lang];
const loadLanguageModule = getLanguageModule(lang);
if (!loadLanguageModule) {
throw new Error(`Prism language module not found: ${lang}`);
}

await loadLanguageModule();
loadedLanguages.add(lang);
statuses.push({ lang, status: 'loaded' });
};

await getLoader(components, langs, loaded).load(loadComponent, {
series: (before: Promise<void>, after: () => Promise<void>) =>
before.then(after),
parallel: (values: Promise<void>[]) => Promise.all(values),
});

return Promise.all(promises);
return statuses;
};
}

Expand Down
Loading