diff options
author | Lucas <97296331+lucas-paulger-sonarsource@users.noreply.github.com> | 2024-07-29 15:47:33 +0300 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-08-13 20:02:46 +0000 |
commit | 176045866b4d946f1ff764719d29024de10c261c (patch) | |
tree | d8c0514a939f905dc9187d632bf1bf9850993b67 /server/sonar-web/design-system/src | |
parent | 294afa38afda74f98f15f1edca798c67322502b8 (diff) | |
download | sonarqube-176045866b4d946f1ff764719d29024de10c261c.tar.gz sonarqube-176045866b4d946f1ff764719d29024de10c261c.zip |
SONAR-22498 CodeSnippet supports issue indicator for jupyter preview (#11414)
Diffstat (limited to 'server/sonar-web/design-system/src')
4 files changed, 265 insertions, 1 deletions
diff --git a/server/sonar-web/design-system/src/components/CodeSyntaxHighlighter.tsx b/server/sonar-web/design-system/src/components/CodeSyntaxHighlighter.tsx index 502cc4a9eae..1a35a36d10a 100644 --- a/server/sonar-web/design-system/src/components/CodeSyntaxHighlighter.tsx +++ b/server/sonar-web/design-system/src/components/CodeSyntaxHighlighter.tsx @@ -25,7 +25,7 @@ import cobol from 'highlightjs-cobol'; import abap from 'highlightjs-sap-abap'; import tw from 'twin.macro'; import { themeColor, themeContrast } from '../helpers/theme'; -import { hljsUnderlinePlugin } from '../sonar-aligned/hljs/HljsUnderlinePlugin'; +import { hljsIssueIndicatorPlugin, hljsUnderlinePlugin } from '../sonar-aligned'; hljs.registerLanguage('abap', abap); hljs.registerLanguage('apex', apex); @@ -39,6 +39,7 @@ hljs.registerAliases('secrets', { languageName: 'markdown' }); hljs.registerAliases('web', { languageName: 'xml' }); hljs.registerAliases(['cloudformation', 'kubernetes'], { languageName: 'yaml' }); +hljs.addPlugin(hljsIssueIndicatorPlugin); hljs.addPlugin(hljsUnderlinePlugin); interface Props { diff --git a/server/sonar-web/design-system/src/sonar-aligned/hljs/HljsIssueIndicatorPlugin.ts b/server/sonar-web/design-system/src/sonar-aligned/hljs/HljsIssueIndicatorPlugin.ts new file mode 100644 index 00000000000..08d32b3468e --- /dev/null +++ b/server/sonar-web/design-system/src/sonar-aligned/hljs/HljsIssueIndicatorPlugin.ts @@ -0,0 +1,135 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { BeforeHighlightContext, HighlightResult } from 'highlight.js'; + +const BREAK_LINE_REGEXP = /\n/g; + +export class HljsIssueIndicatorPlugin { + static readonly LINE_WRAPPER_STYLE = [ + 'display: inline-grid', + 'grid-template-rows: auto', + 'grid-template-columns: 26px 1fr', + 'align-items: center', + ].join(';'); + + private issueKeys: { [key: string]: string[] }; + static readonly LINE_WRAPPER_OPEN_TAG = `<div style="${this.LINE_WRAPPER_STYLE}">`; + static readonly LINE_WRAPPER_CLOSE_TAG = `</div>`; + static readonly EMPTY_INDICATOR_COLUMN = `<div></div>`; + public lineIssueIndicatorElement(issueKey: string) { + return `<div id="issue-key-${issueKey}"></div>`; + } + + constructor() { + this.issueKeys = {}; + } + + 'before:highlight'(data: BeforeHighlightContext) { + data.code = this.extractIssue(data.code); + } + + 'after:highlight'(data: HighlightResult) { + if (Object.keys(this.issueKeys).length > 0) { + data.value = this.addIssueIndicator(data.value); + } + // reset issueKeys for next CodeSnippet + this.issueKeys = {}; + } + + addIssuesToLines = (sourceLines: string[], issues: { [line: number]: string[] }) => { + return sourceLines.map((line, lineIndex) => { + const issuesByLine = issues[lineIndex]; + if (!issues || !issuesByLine) { + return line; + } + + return `[ISSUE_KEYS:${issuesByLine.join(',')}]${line}`; + }); + }; + + private getLines(text: string) { + if (text.length === 0) { + return []; + } + return text.split(BREAK_LINE_REGEXP); + } + + private extractIssue(inputHtml: string) { + const lines = this.getLines(inputHtml); + const issueKeysPattern = /\[ISSUE_KEYS:([^\]]+)\](.+)/; + const removeIssueKeysPattern = /\[ISSUE_KEYS:[^\]]+\](.+)/; + + const wrappedLines = lines.map((line, index) => { + const match = issueKeysPattern.exec(line); + + if (match) { + const issueKeys = match[1].split(','); + if (!this.issueKeys[index]) { + this.issueKeys[index] = issueKeys; + } else { + this.issueKeys[index].push(...issueKeys); + } + } + + const result = removeIssueKeysPattern.exec(line); + + return result ? result[1] : line; + }); + + return wrappedLines.join('\n'); + } + + private addIssueIndicator(inputHtml: string) { + const lines = this.getLines(inputHtml); + + const wrappedLines = lines.map((line, index) => { + const issueKeys = this.issueKeys[index]; + + if (issueKeys) { + // the react portal looks for the first issue key + const referenceIssueKey = issueKeys[0]; + return [ + HljsIssueIndicatorPlugin.LINE_WRAPPER_OPEN_TAG, + this.lineIssueIndicatorElement(referenceIssueKey), + '<div>', + line, + '</div>', + HljsIssueIndicatorPlugin.LINE_WRAPPER_CLOSE_TAG, + ].join(''); + } + + // Keep the correct structure when at least one line has issues + return [ + HljsIssueIndicatorPlugin.LINE_WRAPPER_OPEN_TAG, + HljsIssueIndicatorPlugin.EMPTY_INDICATOR_COLUMN, + '<div>', + line, + '</div>', + HljsIssueIndicatorPlugin.LINE_WRAPPER_CLOSE_TAG, + ].join(''); + }); + + return wrappedLines.join('\n'); + } +} + +const hljsIssueIndicatorPlugin = new HljsIssueIndicatorPlugin(); +export { hljsIssueIndicatorPlugin }; diff --git a/server/sonar-web/design-system/src/sonar-aligned/hljs/__tests__/HljsIssueIndicatorPlugin-test.ts b/server/sonar-web/design-system/src/sonar-aligned/hljs/__tests__/HljsIssueIndicatorPlugin-test.ts new file mode 100644 index 00000000000..2760ee2c65b --- /dev/null +++ b/server/sonar-web/design-system/src/sonar-aligned/hljs/__tests__/HljsIssueIndicatorPlugin-test.ts @@ -0,0 +1,126 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { BeforeHighlightContext, HighlightResult } from 'highlight.js'; +import { hljsIssueIndicatorPlugin, HljsIssueIndicatorPlugin } from '../HljsIssueIndicatorPlugin'; + +describe('HljsIssueIndicatorPlugin', () => { + it('should prepend to the line the issues that were found', () => { + expect( + hljsIssueIndicatorPlugin.addIssuesToLines(['line1', 'line2', 'line3', `line4`, 'line5'], { + 1: ['123abd', '234asd'], + }), + ).toEqual(['line1', '[ISSUE_KEYS:123abd,234asd]line2', 'line3', `line4`, 'line5']); + + expect( + hljsIssueIndicatorPlugin.addIssuesToLines(['line1', 'line2', 'line3', `line4`, 'line5'], { + 1: ['123abd'], + }), + ).toEqual(['line1', '[ISSUE_KEYS:123abd]line2', 'line3', `line4`, 'line5']); + }); + describe('when tokens exist in the code snippet', () => { + it('should indicate an issue on a line', () => { + const inputHtml = { + code: hljsIssueIndicatorPlugin + .addIssuesToLines(['line1', 'line2', 'line3', `line4`, 'line5'], { 1: ['123abd'] }) + .join('\n'), + } as BeforeHighlightContext; + const result = { + value: ['line1', `line2`, 'line3', `line4`, 'line5'].join('\n'), + } as HighlightResult; + + //find issue keys + hljsIssueIndicatorPlugin['before:highlight'](inputHtml); + //add the issue indicator html + hljsIssueIndicatorPlugin['after:highlight'](result); + + expect(result.value).toEqual( + [ + `${HljsIssueIndicatorPlugin.LINE_WRAPPER_OPEN_TAG}${HljsIssueIndicatorPlugin.EMPTY_INDICATOR_COLUMN}<div>line1</div>${HljsIssueIndicatorPlugin.LINE_WRAPPER_CLOSE_TAG}`, + `${HljsIssueIndicatorPlugin.LINE_WRAPPER_OPEN_TAG}<div id="issue-key-123abd"></div><div>line2</div>${HljsIssueIndicatorPlugin.LINE_WRAPPER_CLOSE_TAG}`, + `${HljsIssueIndicatorPlugin.LINE_WRAPPER_OPEN_TAG}${HljsIssueIndicatorPlugin.EMPTY_INDICATOR_COLUMN}<div>line3</div>${HljsIssueIndicatorPlugin.LINE_WRAPPER_CLOSE_TAG}`, + `${HljsIssueIndicatorPlugin.LINE_WRAPPER_OPEN_TAG}${HljsIssueIndicatorPlugin.EMPTY_INDICATOR_COLUMN}<div>line4</div>${HljsIssueIndicatorPlugin.LINE_WRAPPER_CLOSE_TAG}`, + `${HljsIssueIndicatorPlugin.LINE_WRAPPER_OPEN_TAG}${HljsIssueIndicatorPlugin.EMPTY_INDICATOR_COLUMN}<div>line5</div>${HljsIssueIndicatorPlugin.LINE_WRAPPER_CLOSE_TAG}`, + ].join('\n'), + ); + }); + + it('should support multiple issues found on one line', () => { + const inputHtml = { + code: hljsIssueIndicatorPlugin + .addIssuesToLines(['line1', 'line2 issue2', 'line3', `line4`, 'line5'], { + 1: ['123abd', '234asd'], + }) + .join('\n'), + } as BeforeHighlightContext; + const result = { + value: ['line1', `line2 issue2`, 'line3', `line4`, 'line5'].join('\n'), + } as HighlightResult; + + //find issue keys + hljsIssueIndicatorPlugin['before:highlight'](inputHtml); + //add the issue indicator html + hljsIssueIndicatorPlugin['after:highlight'](result); + + expect(result.value).toEqual( + [ + `${HljsIssueIndicatorPlugin.LINE_WRAPPER_OPEN_TAG}${HljsIssueIndicatorPlugin.EMPTY_INDICATOR_COLUMN}<div>line1</div>${HljsIssueIndicatorPlugin.LINE_WRAPPER_CLOSE_TAG}`, + `${HljsIssueIndicatorPlugin.LINE_WRAPPER_OPEN_TAG}<div id="issue-key-123abd"></div><div>line2 issue2</div>${HljsIssueIndicatorPlugin.LINE_WRAPPER_CLOSE_TAG}`, + `${HljsIssueIndicatorPlugin.LINE_WRAPPER_OPEN_TAG}${HljsIssueIndicatorPlugin.EMPTY_INDICATOR_COLUMN}<div>line3</div>${HljsIssueIndicatorPlugin.LINE_WRAPPER_CLOSE_TAG}`, + `${HljsIssueIndicatorPlugin.LINE_WRAPPER_OPEN_TAG}${HljsIssueIndicatorPlugin.EMPTY_INDICATOR_COLUMN}<div>line4</div>${HljsIssueIndicatorPlugin.LINE_WRAPPER_CLOSE_TAG}`, + `${HljsIssueIndicatorPlugin.LINE_WRAPPER_OPEN_TAG}${HljsIssueIndicatorPlugin.EMPTY_INDICATOR_COLUMN}<div>line5</div>${HljsIssueIndicatorPlugin.LINE_WRAPPER_CLOSE_TAG}`, + ].join('\n'), + ); + }); + + it('should not render anything if no source code is passed', () => { + const inputHtml = { + code: '', + } as BeforeHighlightContext; + const result = { + value: '', + } as HighlightResult; + + //find issue keys + hljsIssueIndicatorPlugin['before:highlight'](inputHtml); + //add the issue indicator html + hljsIssueIndicatorPlugin['after:highlight'](result); + + expect(result.value).toEqual(''); + }); + }); + + describe('when no tokens exist in the code snippet', () => { + it('should not change the source', () => { + const inputHtml = { + code: ['line1', `line2`, 'line3', `line4`, 'line5'].join('\n'), + } as BeforeHighlightContext; + const result = { + value: ['line1', `line2`, 'line3', `line4`, 'line5'].join('\n'), + } as HighlightResult; + + //find issue keys + hljsIssueIndicatorPlugin['before:highlight'](inputHtml); + //add the issue indicator html + hljsIssueIndicatorPlugin['after:highlight'](result); + + expect(result.value).toEqual(['line1', 'line2', 'line3', 'line4', 'line5'].join('\n')); + }); + }); +}); diff --git a/server/sonar-web/design-system/src/sonar-aligned/hljs/index.ts b/server/sonar-web/design-system/src/sonar-aligned/hljs/index.ts index b7394666777..0816564955e 100644 --- a/server/sonar-web/design-system/src/sonar-aligned/hljs/index.ts +++ b/server/sonar-web/design-system/src/sonar-aligned/hljs/index.ts @@ -17,4 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +export { hljsIssueIndicatorPlugin } from './HljsIssueIndicatorPlugin'; export { hljsUnderlinePlugin } from './HljsUnderlinePlugin'; |