From bb2fa45b4e8d9e1f1e3d0842e3663ee41a109624 Mon Sep 17 00:00:00 2001
From: David Cho-Lerat
Date: Thu, 22 Jun 2023 17:04:33 +0200
Subject: [PATCH] SONAR-19638 Add syntax highlighting to code snippets in rule
details
---
server/sonar-web/design-system/package.json | 3 +
.../highlightjs-apex.d.ts} | 34 +-
.../src/@types/highlightjs-sap-abap.d.ts | 26 +
.../src/components/CodeSnippet.tsx | 120 -----
.../src/components/CodeSyntaxHighlighter.tsx | 153 ++++++
.../src/components/Highlighter.tsx | 182 -------
.../components/__tests__/CodeSnippet-test.tsx | 46 --
.../__tests__/CodeSyntaxHighlighter-test.tsx | 53 ++
.../__snapshots__/CodeSnippet-test.tsx.snap | 473 ------------------
.../__snapshots__/Highlighter-test.tsx.snap | 463 -----------------
.../design-system/src/components/index.ts | 2 +-
.../components/RuleDetailsDescription.tsx | 35 +-
.../components/HotspotViewer.tsx | 30 +-
.../components/HotspotViewerRenderer.tsx | 27 +-
.../components/HotspotViewerTabs.tsx | 42 +-
.../__tests__/AnalysisWarningsModal-test.tsx | 65 +--
.../AnalysisWarningsModal-test.tsx.snap | 216 --------
.../js/components/rules/IssueTabViewer.tsx | 23 +-
.../rules/MoreInfoRuleDescription.tsx | 19 +-
.../js/components/rules/RuleDescription.tsx | 39 +-
.../js/components/rules/RuleTabViewer.tsx | 66 +--
.../src/main/js/helpers/mocks/tasks.ts | 4 +-
server/sonar-web/yarn.lock | 34 ++
23 files changed, 491 insertions(+), 1664 deletions(-)
rename server/sonar-web/design-system/src/{components/__tests__/Highlighter-test.tsx => @types/highlightjs-apex.d.ts} (50%)
create mode 100644 server/sonar-web/design-system/src/@types/highlightjs-sap-abap.d.ts
delete mode 100644 server/sonar-web/design-system/src/components/CodeSnippet.tsx
create mode 100644 server/sonar-web/design-system/src/components/CodeSyntaxHighlighter.tsx
delete mode 100644 server/sonar-web/design-system/src/components/Highlighter.tsx
delete mode 100644 server/sonar-web/design-system/src/components/__tests__/CodeSnippet-test.tsx
create mode 100644 server/sonar-web/design-system/src/components/__tests__/CodeSyntaxHighlighter-test.tsx
delete mode 100644 server/sonar-web/design-system/src/components/__tests__/__snapshots__/CodeSnippet-test.tsx.snap
delete mode 100644 server/sonar-web/design-system/src/components/__tests__/__snapshots__/Highlighter-test.tsx.snap
delete mode 100644 server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/AnalysisWarningsModal-test.tsx.snap
diff --git a/server/sonar-web/design-system/package.json b/server/sonar-web/design-system/package.json
index 2ca10017669..3d8cf39ac87 100644
--- a/server/sonar-web/design-system/package.json
+++ b/server/sonar-web/design-system/package.json
@@ -35,6 +35,9 @@
"eslint-plugin-local-rules": "1.3.2",
"eslint-plugin-typescript-sort-keys": "2.3.0",
"highlight.js": "11.7.0",
+ "highlightjs-apex": "1.2.0",
+ "highlightjs-cobol": "0.3.3",
+ "highlightjs-sap-abap": "0.2.0",
"history": "5.3.0",
"jest": "29.5.0",
"postcss": "8.4.21",
diff --git a/server/sonar-web/design-system/src/components/__tests__/Highlighter-test.tsx b/server/sonar-web/design-system/src/@types/highlightjs-apex.d.ts
similarity index 50%
rename from server/sonar-web/design-system/src/components/__tests__/Highlighter-test.tsx
rename to server/sonar-web/design-system/src/@types/highlightjs-apex.d.ts
index c02ca7f6e51..efcfd753359 100644
--- a/server/sonar-web/design-system/src/components/__tests__/Highlighter-test.tsx
+++ b/server/sonar-web/design-system/src/@types/highlightjs-apex.d.ts
@@ -17,34 +17,10 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+declare module 'highlightjs-apex' {
+ import { LanguageFn } from 'highlight.js';
-import { renderWithContext } from '../../helpers/testUtils';
-import { FCProps } from '../../types/misc';
-import { Highlighter } from '../Highlighter';
-
-it('renders correctly', () => {
- expect(setupWithProps().container).toMatchSnapshot();
-});
-
-it('should handle multiple lines of code', () => {
- expect(
- setupWithProps({
- code: `foo: bar
- pleh: help
- stuff:
- foo: bar
- bar: foo`,
- language: 'yaml',
- }).container
- ).toMatchSnapshot();
-});
-
-it('should display edit functions', () => {
- expect(
- setupWithProps({ code: 'One line command', toggleEdit: jest.fn() }).container
- ).toMatchSnapshot();
-});
-
-function setupWithProps(props: Partial> = {}) {
- return renderWithContext();
+ const defineLanguage: LanguageFn;
+ // eslint-disable-next-line import/no-default-export
+ export default defineLanguage;
}
diff --git a/server/sonar-web/design-system/src/@types/highlightjs-sap-abap.d.ts b/server/sonar-web/design-system/src/@types/highlightjs-sap-abap.d.ts
new file mode 100644
index 00000000000..d133b6e7f71
--- /dev/null
+++ b/server/sonar-web/design-system/src/@types/highlightjs-sap-abap.d.ts
@@ -0,0 +1,26 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.
+ */
+declare module 'highlightjs-sap-abap' {
+ import { LanguageFn } from 'highlight.js';
+
+ const defineLanguage: LanguageFn;
+ // eslint-disable-next-line import/no-default-export
+ export default defineLanguage;
+}
diff --git a/server/sonar-web/design-system/src/components/CodeSnippet.tsx b/server/sonar-web/design-system/src/components/CodeSnippet.tsx
deleted file mode 100644
index e1fed4f8808..00000000000
--- a/server/sonar-web/design-system/src/components/CodeSnippet.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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 styled from '@emotion/styled';
-import classNames from 'classnames';
-import tw from 'twin.macro';
-import { themeBorder, themeColor } from '../helpers/theme';
-import { isDefined } from '../helpers/types';
-import { Highlighter, RegisteredLanguages } from './Highlighter';
-import { ClipboardButton } from './clipboard';
-
-interface Props {
- className?: string;
- highlight?: boolean;
- isOneLine?: boolean;
- join?: string;
- language?: RegisteredLanguages;
- noCopy?: boolean;
- render?: string;
- snippet: string | Array;
- toggleEdit?: VoidFunction;
- wrap?: boolean;
-}
-
-// keep this "useless" concatenation for the readability reason
-// eslint-disable-next-line no-useless-concat
-const s = ' \\' + '\n ';
-
-export function CodeSnippet(props: Props) {
- const {
- className,
- isOneLine,
- highlight,
- join = s,
- language,
- noCopy,
- render,
- snippet,
- toggleEdit,
- wrap,
- } = props;
- const snippetArray = Array.isArray(snippet) ? snippet.filter(isDefined) : [snippet];
- const finalSnippet = isOneLine ? snippetArray.join(' ') : snippetArray.join(join);
-
- const isSimpleOneLine = isOneLine && noCopy;
-
- const copyButton = isOneLine ? (
-
- ) : (
-
- );
-
- return (
-
- {!noCopy && copyButton}
-
-
- );
-}
-
-const Wrapper = styled.div`
- background-color: ${themeColor('codeSnippetBackground')};
- border: ${themeBorder('default', 'codeSnippetBorder')};
-
- ${tw`sw-rounded-2`}
- ${tw`sw-relative`}
- ${tw`sw-my-2`}
-
- &.code-snippet-simple-oneline {
- ${tw`sw-my-0`}
- ${tw`sw-rounded-1`}
- }
-`;
-
-const StyledClipboardButton = styled(ClipboardButton)`
- ${tw`sw-select-none`}
- ${tw`sw-body-sm`}
- ${tw`sw-top-6 sw-right-6`}
- ${tw`sw-absolute`}
-
- .code-snippet-highlighted-oneline & {
- ${tw`sw-bottom-2`}
- }
-`;
-
-const StyledSingleLineClipboardButton = styled(StyledClipboardButton)`
- ${tw`sw-top-6 sw-bottom-6`}
-`;
diff --git a/server/sonar-web/design-system/src/components/CodeSyntaxHighlighter.tsx b/server/sonar-web/design-system/src/components/CodeSyntaxHighlighter.tsx
new file mode 100644
index 00000000000..487b2223be6
--- /dev/null
+++ b/server/sonar-web/design-system/src/components/CodeSyntaxHighlighter.tsx
@@ -0,0 +1,153 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 styled from '@emotion/styled';
+import hljs from 'highlight.js';
+import apex from 'highlightjs-apex';
+import cobol from 'highlightjs-cobol';
+import abap from 'highlightjs-sap-abap';
+import tw from 'twin.macro';
+import { themeColor, themeContrast } from '../helpers/theme';
+
+hljs.registerLanguage('abap', abap);
+hljs.registerLanguage('apex', apex);
+hljs.registerLanguage('cobol', cobol);
+
+hljs.registerAliases('azureresourcemanager', { languageName: 'json' });
+hljs.registerAliases('flex', { languageName: 'actionscript' });
+hljs.registerAliases('objc', { languageName: 'objectivec' });
+hljs.registerAliases('plsql', { languageName: 'pgsql' });
+hljs.registerAliases('secrets', { languageName: 'markdown' });
+hljs.registerAliases('web', { languageName: 'xml' });
+hljs.registerAliases(['cloudformation', 'kubernetes'], { languageName: 'yaml' });
+
+interface Props {
+ className?: string;
+ htmlAsString: string;
+ language?: string;
+}
+
+const CODE_REGEXP = '<(code|pre)\\b([^>]*?)>(.+?)<\\/\\1>';
+const GLOBAL_REGEXP = new RegExp(CODE_REGEXP, 'gs');
+const SINGLE_REGEXP = new RegExp(CODE_REGEXP, 's');
+
+const htmlDecode = (escapedCode: string) => {
+ const doc = new DOMParser().parseFromString(escapedCode, 'text/html');
+
+ return doc.documentElement.textContent ?? '';
+};
+
+export function CodeSyntaxHighlighter({ className, htmlAsString, language }: Props) {
+ let highlightedHtmlAsString = htmlAsString;
+
+ htmlAsString.match(GLOBAL_REGEXP)?.forEach((codeBlock) => {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const [, tag, attributes, code] = SINGLE_REGEXP.exec(codeBlock)!;
+
+ const unescapedCode = htmlDecode(code);
+
+ let highlightedCode;
+
+ try {
+ highlightedCode = hljs.highlight(unescapedCode, {
+ ignoreIllegals: true,
+ language: language ?? 'plaintext',
+ });
+ } catch {
+ highlightedCode = hljs.highlight(unescapedCode, {
+ ignoreIllegals: true,
+ language: 'plaintext',
+ });
+ }
+
+ highlightedHtmlAsString = highlightedHtmlAsString.replace(
+ codeBlock,
+ `<${tag}${attributes}>${highlightedCode.value}${tag}>`
+ );
+ });
+
+ return (
+
+ );
+}
+
+const StyledSpan = styled.span`
+ code {
+ ${tw`sw-code`};
+
+ background: ${themeColor('codeSnippetBackground')};
+ color: ${themeColor('codeSnippetBody')};
+
+ &.hljs {
+ padding: unset;
+ }
+ }
+
+ .hljs-meta,
+ .hljs-variable {
+ color: ${themeColor('codeSnippetBody')};
+ }
+
+ .hljs-doctag,
+ .hljs-title,
+ .hljs-title.class_,
+ .hljs-title.function_ {
+ color: ${themeColor('codeSnippetAnnotations')};
+ }
+
+ .hljs-comment {
+ ${tw`sw-code-comment`}
+
+ color: ${themeColor('codeSnippetComments')};
+ }
+
+ .hljs-keyword,
+ .hljs-tag,
+ .hljs-type {
+ color: ${themeColor('codeSnippetKeyword')};
+ }
+
+ .hljs-literal,
+ .hljs-number {
+ color: ${themeColor('codeSnippetConstants')};
+ }
+
+ .hljs-string {
+ color: ${themeColor('codeSnippetString')};
+ }
+
+ .hljs-meta .hljs-keyword {
+ color: ${themeColor('codeSnippetPreprocessingDirective')};
+ }
+
+ mark {
+ ${tw`sw-font-regular`}
+ ${tw`sw-p-1`}
+ ${tw`sw-rounded-1`}
+
+ background-color: ${themeColor('codeSnippetHighlight')};
+ color: ${themeContrast('codeSnippetHighlight')};
+ }
+`;
diff --git a/server/sonar-web/design-system/src/components/Highlighter.tsx b/server/sonar-web/design-system/src/components/Highlighter.tsx
deleted file mode 100644
index 73690313334..00000000000
--- a/server/sonar-web/design-system/src/components/Highlighter.tsx
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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 styled from '@emotion/styled';
-import classNames from 'classnames';
-import hljs from 'highlight.js/lib/core';
-import bash from 'highlight.js/lib/languages/bash';
-import gradle from 'highlight.js/lib/languages/gradle';
-import plaintext from 'highlight.js/lib/languages/plaintext';
-import powershell from 'highlight.js/lib/languages/powershell';
-import properties from 'highlight.js/lib/languages/properties';
-import shell from 'highlight.js/lib/languages/shell';
-import xml from 'highlight.js/lib/languages/xml';
-import yaml from 'highlight.js/lib/languages/yaml';
-import { useMemo } from 'react';
-import tw from 'twin.macro';
-import { translate } from '../helpers/l10n';
-import { themeColor, themeContrast } from '../helpers/theme';
-import { InteractiveIcon } from './InteractiveIcon';
-import { PencilIcon } from './icons';
-
-hljs.registerLanguage('yaml', yaml);
-hljs.registerLanguage('gradle', gradle);
-hljs.registerLanguage('properties', properties);
-hljs.registerLanguage('xml', xml);
-hljs.registerLanguage('bash', bash);
-hljs.registerLanguage('powershell', powershell);
-hljs.registerLanguage('shell', shell);
-hljs.registerLanguage('plaintext', plaintext);
-
-hljs.addPlugin({
- 'after:highlight': (data) => {
- data.value = data.value
- .replace(/<mark>/g, '')
- .replace(/<\/mark>/g, '');
- },
-});
-
-export type RegisteredLanguages =
- | 'bash'
- | 'gradle'
- | 'plaintext'
- | 'powershell'
- | 'properties'
- | 'shell'
- | 'xml'
- | 'yaml';
-
-interface Props {
- className?: string;
- code: string;
- highlight?: boolean;
- isSimpleOneLine?: boolean;
- language?: RegisteredLanguages;
- toggleEdit?: VoidFunction;
- wrap?: boolean;
-}
-
-export function Highlighter({
- className,
- code,
- highlight = true,
- isSimpleOneLine = false,
- language = 'yaml',
- toggleEdit,
- wrap,
-}: Props) {
- const highlighted = useMemo(
- () => hljs.highlight(code, { language: highlight ? language : 'plaintext' }),
- [code, highlight, language]
- );
-
- return (
-
-
- {toggleEdit && (
-
- )}
-
- );
-}
-
-const StyledPre = styled.pre`
- ${tw`sw-flex sw-items-center`}
- ${tw`sw-overflow-x-auto`}
- ${tw`sw-p-6`}
-
- code {
- color: ${themeColor('codeSnippetBody')};
- background: ${themeColor('codeSnippetBackground')};
- ${tw`sw-code`};
-
- &.hljs {
- padding: unset;
- }
- }
-
- .hljs-variable,
- .hljs-meta {
- color: ${themeColor('codeSnippetBody')};
- }
-
- .hljs-doctag,
- .hljs-title,
- .hljs-title.class_,
- .hljs-title.function_ {
- color: ${themeColor('codeSnippetAnnotations')};
- }
-
- .hljs-comment {
- color: ${themeColor('codeSnippetComments')};
-
- ${tw`sw-code-comment`}
- }
-
- .hljs-tag,
- .hljs-type,
- .hljs-keyword {
- color: ${themeColor('codeSnippetKeyword')};
-
- ${tw`sw-code-highlight`}
- }
-
- .hljs-literal,
- .hljs-number {
- color: ${themeColor('codeSnippetConstants')};
- }
-
- .hljs-string {
- color: ${themeColor('codeSnippetString')};
- }
-
- .hljs-meta .hljs-keyword {
- color: ${themeColor('codeSnippetPreprocessingDirective')};
- }
-
- &.code-wrap {
- ${tw`sw-whitespace-pre-wrap`}
- ${tw`sw-break-all`}
- }
-
- mark {
- color: ${themeContrast('codeSnippetHighlight')};
- background-color: ${themeColor('codeSnippetHighlight')};
- ${tw`sw-font-regular`}
- ${tw`sw-rounded-1`}
- ${tw`sw-p-1`}
- }
-
- &.simple-one-line {
- ${tw`sw-min-h-[1.25rem]`}
- ${tw`sw-py-0 sw-px-1`}
- }
-`;
diff --git a/server/sonar-web/design-system/src/components/__tests__/CodeSnippet-test.tsx b/server/sonar-web/design-system/src/components/__tests__/CodeSnippet-test.tsx
deleted file mode 100644
index 87bca8a7bdc..00000000000
--- a/server/sonar-web/design-system/src/components/__tests__/CodeSnippet-test.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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 { screen } from '@testing-library/react';
-import { HelmetProvider } from 'react-helmet-async';
-import { renderWithContext } from '../../helpers/testUtils';
-import { FCProps } from '../../types/misc';
-import { CodeSnippet } from '../CodeSnippet';
-
-it('should show full size when multiline with no editting', () => {
- const { container } = setupWithProps();
- const copyButton = screen.getByRole('button', { name: 'Copy' });
- expect(copyButton).toHaveStyle('top: 1.5rem');
- expect(container).toMatchSnapshot();
-});
-
-it('should show reduced size when single line with no editting', () => {
- const { container } = setupWithProps({ isOneLine: true, snippet: 'foobar' });
- const copyButton = screen.getByRole('button', { name: 'Copy' });
- expect(copyButton).toHaveStyle('top: 1.5rem');
- expect(container).toMatchSnapshot();
-});
-
-function setupWithProps(props: Partial> = {}) {
- return renderWithContext(
-
-
-
- );
-}
diff --git a/server/sonar-web/design-system/src/components/__tests__/CodeSyntaxHighlighter-test.tsx b/server/sonar-web/design-system/src/components/__tests__/CodeSyntaxHighlighter-test.tsx
new file mode 100644
index 00000000000..a55e079b885
--- /dev/null
+++ b/server/sonar-web/design-system/src/components/__tests__/CodeSyntaxHighlighter-test.tsx
@@ -0,0 +1,53 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 { render } from '../../helpers/testUtils';
+import { CodeSyntaxHighlighter } from '../CodeSyntaxHighlighter';
+
+it('renders correctly with no code', () => {
+ const { container } = render(
+ Hello there!
-`;
diff --git a/server/sonar-web/design-system/src/components/index.ts b/server/sonar-web/design-system/src/components/index.ts
index 2724f1199d8..dbe05f21f67 100644
--- a/server/sonar-web/design-system/src/components/index.ts
+++ b/server/sonar-web/design-system/src/components/index.ts
@@ -26,7 +26,7 @@ export { Breadcrumbs } from './Breadcrumbs';
export * from './BubbleChart';
export * from './Card';
export * from './Checkbox';
-export * from './CodeSnippet';
+export * from './CodeSyntaxHighlighter';
export * from './ColorsLegend';
export * from './CoverageIndicator';
export * from './DatePicker';
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsDescription.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsDescription.tsx
index 27c79da9e56..3a66d28122c 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsDescription.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsDescription.tsx
@@ -17,6 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { CodeSyntaxHighlighter } from 'design-system';
import * as React from 'react';
import { updateRule } from '../../../api/rules';
import FormattingTips from '../../../components/common/FormattingTips';
@@ -46,8 +48,8 @@ export default class RuleDetailsDescription extends React.PureComponent {
this.props.onChange(ruleDetails);
+
if (this.mounted) {
this.setState({ submitting: false, descriptionForm: false });
}
@@ -104,7 +107,7 @@ export default class RuleDetailsDescription extends React.PureComponent {
this.setState({
// set description` to the current `mdNote` each time the form is open
- description: this.props.ruleDetails.mdNote || '',
+ description: this.props.ruleDetails.mdNote ?? '',
descriptionForm: true,
});
};
@@ -112,14 +115,13 @@ export default class RuleDetailsDescription extends React.PureComponent (