Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow logging for Prism to be toggled #75

Merged
merged 3 commits into from
May 12, 2023
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ See `PUBLISH.md` for instructions on how to publish a new version.
-->


- (minor) Allow logging for Prism to be toggled
- (patch) Manually track loaded Prism components

# v1.8.0 - aaf8532
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,7 @@ Pass options for this plugin as the `prismjs` property of the `do-markdownit` pl
Set this property to `false` to disable this plugin.

- `delimiter` (`string`, optional, defaults to `','`): String to split fence information on.
- `logging` (`boolean`, optional, defaults to `false`): Whether to log errors to the console.
</details>

### limit_tokens
Expand Down
12 changes: 7 additions & 5 deletions modifiers/prismjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const { languages, languageAliases, getDependencies } = require('../util/prism_u
/**
* @typedef {Object} PrismJsOptions
* @property {string} [delimiter=','] String to split fence information on.
* @property {boolean} [logging=false] Whether to log errors to the console.
*/

/**
Expand All @@ -43,16 +44,17 @@ const loaded = new Set();
* Helper to load in a Prism component if not yet loaded.
*
* @param {string} component Prism component name to be loaded.
* @param {boolean} logging Whether to log errors to the console.
* @private
*/
const loadComponent = component => {
const loadComponent = (component, logging) => {
if (loaded.has(component)) return;
try {
// eslint-disable-next-line import/no-dynamic-require
require(`../vendor/prismjs/components/prism-${component}`)(Prism);
loaded.add(component);
} catch (err) {
console.error('Failed to load Prism component', component, err);
if (logging) console.error('Failed to load Prism component', component, err);
}
};

Expand Down Expand Up @@ -171,8 +173,8 @@ module.exports = (md, options) => {
const { before, inside, after } = extractCodeBlock(rendered, language);

// Load requirements for language
getDependencies(language.clean).forEach(loadComponent);
loadComponent(language.clean);
getDependencies(language.clean).forEach(dep => loadComponent(dep, !!optsObj.logging));
loadComponent(language.clean, !!optsObj.logging);

// If we failed to load the language (it's a dynamic require), return original
if (!(language.clean in Prism.languages)) return rendered;
Expand All @@ -184,7 +186,7 @@ module.exports = (md, options) => {
return `${before}${highlighted}${after}`;
} catch (err) {
// Fallback to no Prism if render fails
console.error('Bad Prism render occurred', err);
if (optsObj.logging) console.error('Bad Prism render occurred', err);
return rendered;
}
};
Expand Down
85 changes: 79 additions & 6 deletions modifiers/prismjs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ limitations under the License.

'use strict';

const md = require('markdown-it')().use(require('./prismjs'));
const md = require('markdown-it')().use(require('./prismjs'), { logging: true });

it('handles a code fence with a language (adding the class to the pre + highlighting)', () => {
expect(md.render('```nginx\nserver {\n try_files test =404;\n}\n```')).toBe(`<pre class="language-nginx"><code class="language-nginx"><span class="token directive"><span class="token keyword">server</span></span> <span class="token punctuation">{</span>
Expand All @@ -33,11 +33,26 @@ it('handles a code fence with a language alias', () => {
});

it('does not repeatedly load a modifier component', () => {
const error = jest.spyOn(global.console, 'error');
md.render('```ts\nconsole.log("test");\n```\n\n```ts\nconsole.log("test");\n```');
expect(error).not.toHaveBeenCalledWith(expect.stringContaining('Failed to load Prism component'), 'js-templates', expect.anything());
error.mockReset();
error.mockRestore();
// Reset modules in case the modifier was already loaded
jest.resetModules();

// Mock the modifier to track how many times it is called
const callCount = jest.fn();
jest.doMock('../vendor/prismjs/components/prism-js-templates', () => {
const actual = jest.requireActual('../vendor/prismjs/components/prism-js-templates');
return (...args) => {
callCount();
return actual(...args);
};
});

// Process some Markdown that uses a language with the modifier twice
const mdTemp = require('markdown-it')().use(require('./prismjs'));
mdTemp.render('```ts\nconsole.log("test");\n```\n\n```ts\nconsole.log("test");\n```');
expect(callCount).toHaveBeenCalledTimes(1);

// Clean up the mock
jest.dontMock('../vendor/prismjs/components/prism-js-templates');
});

it('does not pollute global scope', () => {
Expand All @@ -51,6 +66,64 @@ it('does not pollute global scope', () => {
expect(global.Prism).toBeUndefined();
});

describe('Error logging', () => {
it('does log errors when logging is enabled', () => {
// Reset modules in case the language was already loaded
jest.resetModules();

// Mock the language to track how many times it is called, and to throw an error
const callCount = jest.fn();
jest.doMock('../vendor/prismjs/components/prism-typescript', () => () => {
callCount();
throw new Error('test');
});

// Spy on console.error to check that it is called
const error = jest.spyOn(global.console, 'error');

// Process some Markdown that uses the language
const mdTemp = require('markdown-it')().use(require('./prismjs'), { logging: true });
mdTemp.render('```ts\nconsole.log("test");\n```');

// Check the mock was called and the error was logged
expect(callCount).toHaveBeenCalledTimes(1);
expect(error).toHaveBeenCalledWith('Failed to load Prism component', 'typescript', expect.any(Error));

// Clean up the mocks
error.mockReset();
error.mockRestore();
jest.dontMock('../vendor/prismjs/components/prism-typescript');
});

it('does not log errors when logging is disabled', () => {
// Reset modules in case the language was already loaded
jest.resetModules();

// Mock the component to track how many times it is called, and to throw an error
const callCount = jest.fn();
jest.doMock('../vendor/prismjs/components/prism-typescript', () => () => {
callCount();
throw new Error('test');
});

// Spy on console.error to check that it is called
const error = jest.spyOn(global.console, 'error');

// Process some Markdown that uses the language
const mdTemp = require('markdown-it')().use(require('./prismjs'));
mdTemp.render('```ts\nconsole.log("test");\n```');

// Check the mock was called and the error was logged
expect(callCount).toHaveBeenCalledTimes(1);
expect(error).not.toHaveBeenCalled();

// Clean up the mocks
error.mockReset();
error.mockRestore();
jest.dontMock('../vendor/prismjs/components/prism-typescript');
});
});

describe('HTML preservation', () => {
// This does pollute the tests, no longer isolated, but is needed to inject HTML into the code blocks
const mdHtml = require('markdown-it')()
Expand Down