From 83b1db6a8c69bdbc75e4b01de63ea8f929c941c9 Mon Sep 17 00:00:00 2001 From: Chen Charnolevsky Date: Sat, 6 Jul 2024 11:35:09 +0300 Subject: [PATCH 1/4] Update Highlight.js and add multi lines marking functionality Add the left side of 'code flow' that including description and steps Disable feature flag Update 'vulnerability_file_contents' with new highlighted component remove unnecessary code move functions fix pipeline errors CR changes Fix unit test Fix error --- .../components/source_viewer/plugins/index.js | 22 --- .../plugins/language_highlight.js | 67 ++++++++ .../plugins/mark_multiple_lines.js | 41 +++++ .../source_viewer/plugins/wrap_lines.js | 4 +- .../source_viewer/workers/highlight_utils.js | 60 +++----- .../vulnerability_file_content_viewer.vue | 79 ++++++++++ .../vulnerability_file_contents.vue | 83 +++++----- .../vulnerability_file_content_viewer_spec.js | 65 ++++++++ .../vulnerability_file_contents_spec.js | 14 +- .../source_viewer/highlight_util_spec.js | 145 +++++++----------- .../source_viewer/plugins/index_spec.js | 18 --- .../plugins/language_highlight_spec.js | 62 ++++++++ .../plugins/mark_multi_lines_spec.js | 22 +++ 13 files changed, 464 insertions(+), 218 deletions(-) delete mode 100644 app/assets/javascripts/vue_shared/components/source_viewer/plugins/index.js create mode 100644 app/assets/javascripts/vue_shared/components/source_viewer/plugins/language_highlight.js create mode 100644 app/assets/javascripts/vue_shared/components/source_viewer/plugins/mark_multiple_lines.js create mode 100644 ee/app/assets/javascripts/vulnerabilities/components/vulnerability_file_content_viewer.vue create mode 100644 ee/spec/frontend/vulnerabilities/vulnerability_file_content_viewer_spec.js delete mode 100644 spec/frontend/vue_shared/components/source_viewer/plugins/index_spec.js create mode 100644 spec/frontend/vue_shared/components/source_viewer/plugins/language_highlight_spec.js create mode 100644 spec/frontend/vue_shared/components/source_viewer/plugins/mark_multi_lines_spec.js diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/index.js b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/index.js deleted file mode 100644 index 3f8a9258fc3304..00000000000000 --- a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import wrapChildNodes from './wrap_child_nodes'; -import linkDependencies from './link_dependencies'; -import wrapBidiChars from './wrap_bidi_chars'; -import wrapLines from './wrap_lines'; - -export const HLJS_ON_AFTER_HIGHLIGHT = 'after:highlight'; - -/** - * Registers our plugins for Highlight.js - * - * Plugin API: https://github.com/highlightjs/highlight.js/blob/main/docs/plugin-api.rst - * - * @param {Object} hljs - the Highlight.js instance. - */ -export const registerPlugins = (hljs, fileType, rawContent, shouldWrapLines) => { - hljs.addPlugin({ [HLJS_ON_AFTER_HIGHLIGHT]: wrapChildNodes }); - hljs.addPlugin({ [HLJS_ON_AFTER_HIGHLIGHT]: wrapBidiChars }); - hljs.addPlugin({ - [HLJS_ON_AFTER_HIGHLIGHT]: (result) => linkDependencies(result, fileType, rawContent), - }); - if (shouldWrapLines) hljs.addPlugin({ [HLJS_ON_AFTER_HIGHLIGHT]: wrapLines }); -}; diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/language_highlight.js b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/language_highlight.js new file mode 100644 index 00000000000000..2a51b3397f15c4 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/language_highlight.js @@ -0,0 +1,67 @@ +import hljsCore from 'highlight.js/lib/core'; +import languageLoader from '~/content_editor/services/highlight_js_language_loader'; +import { ROUGE_TO_HLJS_LANGUAGE_MAP } from '~/vue_shared/components/source_viewer/constants'; + +const HLJS_ON_AFTER_HIGHLIGHT = 'after:highlight'; + +const loadLanguage = async (language, hljs) => { + const languageDefinition = await languageLoader[language](); + hljs.registerLanguage(language, languageDefinition.default); +}; + +const loadSubLanguages = async (languageDefinition, hljs) => { + // Some files can contain sub-languages (i.e., Svelte); this ensures that sub-languages are also loaded + if (!languageDefinition?.contains) return; + + // generate list of languages to load + const languages = new Set( + languageDefinition.contains + .filter((component) => Boolean(component.subLanguage)) + .map((component) => component.subLanguage), + ); + + if (languageDefinition.subLanguage) { + languages.add(languageDefinition.subLanguage); + } + + await Promise.all([...languages].map((language) => loadLanguage(language, hljs))); +}; + +// Registers our plugins for Highlight.js +// Plugin API: https://github.com/highlightjs/highlight.js/blob/main/docs/plugin-api.rst +const registerPlugins = (hljs, plugins) => { + if (!plugins) { + return; + } + for (const plugin of plugins) { + hljs.addPlugin({ [HLJS_ON_AFTER_HIGHLIGHT]: plugin }); + } +}; + +const registerLanguage = async (hljs, language) => { + await loadLanguage(language, hljs); + await loadSubLanguages(hljs.getLanguage(language), hljs); +}; + +const initHighlightJs = async (language, plugins) => { + const hljs = hljsCore.newInstance(); + + registerPlugins(hljs, plugins); + await registerLanguage(hljs, language); + + return hljs; +}; + +const highlightContent = async (lang, rawContent, plugins) => { + const language = ROUGE_TO_HLJS_LANGUAGE_MAP[lang.toLowerCase()]; + let highlightedContent; + + if (language) { + const hljs = await initHighlightJs(language, plugins); + highlightedContent = hljs.highlight(rawContent, { language }).value; + } + + return highlightedContent; +}; + +export { highlightContent }; diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/mark_multiple_lines.js b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/mark_multiple_lines.js new file mode 100644 index 00000000000000..3c153039f16166 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/mark_multiple_lines.js @@ -0,0 +1,41 @@ +/** + * Highlight.js plugin for wrapping lines in the correct classes and attributes. + * Needed for things like hash highlighting to work. + * + * Plugin API: https://github.com/highlightjs/highlight.js/blob/main/docs/plugin-api.rst + * + * @param {String} content - represent highlighted line of code + * @param {number} lineNum - represent highlighted line number + * @param {Object} markLineInfo - highlighting info for the current line + */ +function wrapLineWithDiv(content, lineNum, markLineInfo) { + let wrappedLine = content; + + const stepNumberSpan = + lineNum === markLineInfo?.startLine + ? `${markLineInfo.index + 1}` + : ''; + const stepNumberSpanNone = + lineNum !== markLineInfo?.startLine && markLineInfo + ? `${markLineInfo.index + 1}` + : ''; + + if (markLineInfo) { + const contentStartIndex = content.indexOf(content.trimStart()); + wrappedLine = `${content.slice( + 0, + contentStartIndex, + )}${stepNumberSpanNone}${stepNumberSpan}${content.slice(contentStartIndex)}`; + } + return `
${wrappedLine}
`; +} + +export default (result, lineToMarkersInfo) => { + // eslint-disable-next-line no-param-reassign + result.value = result.value + .split(/\r?\n/) + .map((content, index) => wrapLineWithDiv(content, index + 1, lineToMarkersInfo?.[index + 1])) + .join('\n'); +}; diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/wrap_lines.js b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/wrap_lines.js index 384ada300013b2..b0471e2af98c7d 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/wrap_lines.js +++ b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/wrap_lines.js @@ -4,7 +4,9 @@ * * Plugin API: https://github.com/highlightjs/highlight.js/blob/main/docs/plugin-api.rst * - * @param {Object} Result - an object that represents the highlighted result from Highlight.js + * @param content - represents the highlighted content + * @param number - represents the highlighted line number + * @param language - represents the highlighted language */ function wrapLine(content, number, language) { diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/workers/highlight_utils.js b/app/assets/javascripts/vue_shared/components/source_viewer/workers/highlight_utils.js index 057a1c2d1133b3..0ee7db99e38ff1 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/workers/highlight_utils.js +++ b/app/assets/javascripts/vue_shared/components/source_viewer/workers/highlight_utils.js @@ -1,36 +1,10 @@ -import hljs from 'highlight.js/lib/core'; -import languageLoader from '~/content_editor/services/highlight_js_language_loader'; -import { registerPlugins } from '../plugins/index'; -import { LINES_PER_CHUNK, NEWLINE, ROUGE_TO_HLJS_LANGUAGE_MAP } from '../constants'; - -const loadLanguage = async (language) => { - const languageDefinition = await languageLoader[language](); - hljs.registerLanguage(language, languageDefinition.default); -}; - -const loadSubLanguages = async (languageDefinition) => { - // Some files can contain sub-languages (i.e., Svelte); this ensures that sub-languages are also loaded - if (!languageDefinition?.contains) return; +import wrapChildNodes from '~/vue_shared/components/source_viewer/plugins/wrap_child_nodes'; +import wrapBidiChars from '~/vue_shared/components/source_viewer/plugins/wrap_bidi_chars'; +import linkDependencies from '~/vue_shared/components/source_viewer/plugins/link_dependencies'; +import { highlightContent } from '~/vue_shared/components/source_viewer/plugins/language_highlight'; +import wrapMultipleLines from '~/vue_shared/components/source_viewer/plugins/mark_multiple_lines'; - // generate list of languages to load - const languages = new Set( - languageDefinition.contains - .filter((component) => Boolean(component.subLanguage)) - .map((component) => component.subLanguage), - ); - - if (languageDefinition.subLanguage) { - languages.add(languageDefinition.subLanguage); - } - - await Promise.all([...languages].map(loadLanguage)); -}; - -const initHighlightJs = async (fileType, content, language) => { - registerPlugins(hljs, fileType, content, true); - await loadLanguage(language); - await loadSubLanguages(hljs.getLanguage(language)); -}; +import { LINES_PER_CHUNK, NEWLINE, ROUGE_TO_HLJS_LANGUAGE_MAP } from '../constants'; const splitByLineBreaks = (content = '') => content.split(/\r?\n/); @@ -57,17 +31,29 @@ const splitIntoChunks = (language, rawContent, highlightedContent) => { return result; }; +const highlightPlugins = (fileType, rawContent, shouldWrapLines) => { + const plugins = [ + wrapChildNodes, + wrapBidiChars, + (result) => linkDependencies(result, fileType, rawContent), + ]; + if (shouldWrapLines) { + plugins.push(wrapMultipleLines); + } + return plugins; +}; + const highlight = async (fileType, rawContent, lang) => { const language = ROUGE_TO_HLJS_LANGUAGE_MAP[lang.toLowerCase()]; - let result; + let highlightedChunks; if (language) { - await initHighlightJs(fileType, rawContent, language); - const highlightedContent = hljs.highlight(rawContent, { language }).value; - result = splitIntoChunks(language, rawContent, highlightedContent); + const plugins = highlightPlugins(fileType, rawContent, true); + const highlightedContent = await highlightContent(lang, rawContent, plugins); + highlightedChunks = splitIntoChunks(language, rawContent, highlightedContent); } - return result; + return highlightedChunks; }; export { highlight, splitIntoChunks }; diff --git a/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_file_content_viewer.vue b/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_file_content_viewer.vue new file mode 100644 index 00000000000000..776713841da117 --- /dev/null +++ b/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_file_content_viewer.vue @@ -0,0 +1,79 @@ + + + diff --git a/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_file_contents.vue b/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_file_contents.vue index 132c4522560ad5..460cdfac9cb587 100644 --- a/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_file_contents.vue +++ b/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_file_contents.vue @@ -1,23 +1,29 @@