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}` + ); + }); + + 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!

+ +

There's no code here.

+ `} + /> + ); + + // eslint-disable-next-line testing-library/no-node-access + expect(container.getElementsByClassName('hljs-string').length).toBe(0); +}); + +it('renders correctly with code', () => { + const { container } = render( + Hello there!

+ +

There's some "code" here.

+ `} + language="typescript" + /> + ); + + // eslint-disable-next-line testing-library/no-node-access + expect(container.getElementsByClassName('hljs-string').length).toBe(1); +}); diff --git a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/CodeSnippet-test.tsx.snap b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/CodeSnippet-test.tsx.snap deleted file mode 100644 index ec00317fed2..00000000000 --- a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/CodeSnippet-test.tsx.snap +++ /dev/null @@ -1,473 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should show full size when multiline with no editting 1`] = ` -.emotion-0 { - background-color: rgb(252,252,253); - border: 1px solid rgb(225,230,243); - border-radius: 0.5rem; - position: relative; - margin-top: 0.5rem; - margin-bottom: 0.5rem; -} - -.emotion-0.code-snippet-simple-oneline { - margin-top: 0; - margin-bottom: 0; - border-radius: 0.25rem; -} - -.emotion-4 { - box-sizing: border-box; - -webkit-text-decoration: none; - text-decoration: none; - outline: none; - border: var(--border); - color: var(--color); - background-color: var(--background); - -webkit-transition: background-color 0.2s ease,outline 0.2s ease; - transition: background-color 0.2s ease,outline 0.2s ease; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - height: 2.25rem; - font-family: Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; - font-size: 0.875rem; - line-height: 1.25rem; - font-weight: 600; - padding-left: 1rem; - padding-right: 1rem; - padding-top: 0.5rem; - padding-bottom: 0.5rem; - border-radius: 0.5rem; - cursor: pointer; - --background: rgb(255,255,255); - --backgroundHover: rgb(239,242,249); - --color: rgb(62,67,87); - --focus: rgba(197,205,223,0.2); - --border: 1px solid rgb(197,205,223); - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - font-family: Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; - font-size: 0.875rem; - line-height: 1.25rem; - font-weight: 400; - right: 1.5rem; - top: 1.5rem; - position: absolute; -} - -.emotion-4:hover { - color: var(--color); - background-color: var(--backgroundHover); -} - -.emotion-4:focus, -.emotion-4:active { - color: var(--color); - outline: 4px solid var(--focus); -} - -.emotion-4:disabled, -.emotion-4:disabled:hover { - color: rgb(166,173,194); - background-color: rgb(239,242,249); - border: 1px solid rgb(197,205,223); - cursor: not-allowed; -} - -.emotion-4>svg { - margin-right: 0.25rem; -} - -.emotion-4 [disabled] { - pointer-events: none; -} - -.code-snippet-highlighted-oneline .emotion-4 { - bottom: 0.5rem; -} - -.emotion-6 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - overflow-x: auto; - padding: 1.5rem; -} - -.emotion-6 code { - color: rgb(51,53,60); - background: rgb(252,252,253); - font-family: Ubuntu Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; - font-size: 0.875rem; - line-height: 1.125rem; - font-weight: 400; -} - -.emotion-6 code.hljs { - padding: unset; -} - -.emotion-6 .hljs-variable, -.emotion-6 .hljs-meta { - color: rgb(51,53,60); -} - -.emotion-6 .hljs-doctag, -.emotion-6 .hljs-title, -.emotion-6 .hljs-title.class_, -.emotion-6 .hljs-title.function_ { - color: rgb(34,84,192); -} - -.emotion-6 .hljs-comment { - color: rgb(109,111,119); - font-family: Ubuntu Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; - font-size: 0.875rem; - line-height: 1.125rem; - font-style: italic; -} - -.emotion-6 .hljs-tag, -.emotion-6 .hljs-type, -.emotion-6 .hljs-keyword { - color: rgb(152,29,150); - font-family: Ubuntu Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; - font-size: 0.875rem; - line-height: 1.125rem; - font-weight: 700; -} - -.emotion-6 .hljs-literal, -.emotion-6 .hljs-number { - color: rgb(126,83,5); -} - -.emotion-6 .hljs-string { - color: rgb(32,105,31); -} - -.emotion-6 .hljs-meta .hljs-keyword { - color: rgb(47,103,48); -} - -.emotion-6.code-wrap { - white-space: pre-wrap; - word-break: break-all; -} - -.emotion-6 mark { - color: rgb(217,45,32); - background-color: rgb(197,205,223); - font-weight: 400; - border-radius: 0.25rem; - padding: 0.25rem; -} - -.emotion-6.simple-one-line { - min-height: 1.25rem; - padding-left: 0.25rem; - padding-right: 0.25rem; - padding-top: 0; - padding-bottom: 0; -} - -
-
- -
-      
-        
-          foo
-        
-        
-
-        
-          bar
-        
-      
-    
-
-
-`; - -exports[`should show reduced size when single line with no editting 1`] = ` -.emotion-0 { - background-color: rgb(252,252,253); - border: 1px solid rgb(225,230,243); - border-radius: 0.5rem; - position: relative; - margin-top: 0.5rem; - margin-bottom: 0.5rem; -} - -.emotion-0.code-snippet-simple-oneline { - margin-top: 0; - margin-bottom: 0; - border-radius: 0.25rem; -} - -.emotion-4 { - box-sizing: border-box; - -webkit-text-decoration: none; - text-decoration: none; - outline: none; - border: var(--border); - color: var(--color); - background-color: var(--background); - -webkit-transition: background-color 0.2s ease,outline 0.2s ease; - transition: background-color 0.2s ease,outline 0.2s ease; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - height: 2.25rem; - font-family: Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; - font-size: 0.875rem; - line-height: 1.25rem; - font-weight: 600; - padding-left: 1rem; - padding-right: 1rem; - padding-top: 0.5rem; - padding-bottom: 0.5rem; - border-radius: 0.5rem; - cursor: pointer; - --background: rgb(255,255,255); - --backgroundHover: rgb(239,242,249); - --color: rgb(62,67,87); - --focus: rgba(197,205,223,0.2); - --border: 1px solid rgb(197,205,223); - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - font-family: Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; - font-size: 0.875rem; - line-height: 1.25rem; - font-weight: 400; - right: 1.5rem; - top: 1.5rem; - position: absolute; - bottom: 1.5rem; - top: 1.5rem; -} - -.emotion-4:hover { - color: var(--color); - background-color: var(--backgroundHover); -} - -.emotion-4:focus, -.emotion-4:active { - color: var(--color); - outline: 4px solid var(--focus); -} - -.emotion-4:disabled, -.emotion-4:disabled:hover { - color: rgb(166,173,194); - background-color: rgb(239,242,249); - border: 1px solid rgb(197,205,223); - cursor: not-allowed; -} - -.emotion-4>svg { - margin-right: 0.25rem; -} - -.emotion-4 [disabled] { - pointer-events: none; -} - -.code-snippet-highlighted-oneline .emotion-4 { - bottom: 0.5rem; -} - -.emotion-6 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - overflow-x: auto; - padding: 1.5rem; -} - -.emotion-6 code { - color: rgb(51,53,60); - background: rgb(252,252,253); - font-family: Ubuntu Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; - font-size: 0.875rem; - line-height: 1.125rem; - font-weight: 400; -} - -.emotion-6 code.hljs { - padding: unset; -} - -.emotion-6 .hljs-variable, -.emotion-6 .hljs-meta { - color: rgb(51,53,60); -} - -.emotion-6 .hljs-doctag, -.emotion-6 .hljs-title, -.emotion-6 .hljs-title.class_, -.emotion-6 .hljs-title.function_ { - color: rgb(34,84,192); -} - -.emotion-6 .hljs-comment { - color: rgb(109,111,119); - font-family: Ubuntu Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; - font-size: 0.875rem; - line-height: 1.125rem; - font-style: italic; -} - -.emotion-6 .hljs-tag, -.emotion-6 .hljs-type, -.emotion-6 .hljs-keyword { - color: rgb(152,29,150); - font-family: Ubuntu Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; - font-size: 0.875rem; - line-height: 1.125rem; - font-weight: 700; -} - -.emotion-6 .hljs-literal, -.emotion-6 .hljs-number { - color: rgb(126,83,5); -} - -.emotion-6 .hljs-string { - color: rgb(32,105,31); -} - -.emotion-6 .hljs-meta .hljs-keyword { - color: rgb(47,103,48); -} - -.emotion-6.code-wrap { - white-space: pre-wrap; - word-break: break-all; -} - -.emotion-6 mark { - color: rgb(217,45,32); - background-color: rgb(197,205,223); - font-weight: 400; - border-radius: 0.25rem; - padding: 0.25rem; -} - -.emotion-6.simple-one-line { - min-height: 1.25rem; - padding-left: 0.25rem; - padding-right: 0.25rem; - padding-top: 0; - padding-bottom: 0; -} - -
-
- -
-      
-        
-          foobar
-        
-      
-    
-
-
-`; diff --git a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/Highlighter-test.tsx.snap b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/Highlighter-test.tsx.snap deleted file mode 100644 index d7fed058225..00000000000 --- a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/Highlighter-test.tsx.snap +++ /dev/null @@ -1,463 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders correctly 1`] = ` -.emotion-0 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - overflow-x: auto; - padding: 1.5rem; -} - -.emotion-0 code { - color: rgb(51,53,60); - background: rgb(252,252,253); - font-family: Ubuntu Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; - font-size: 0.875rem; - line-height: 1.125rem; - font-weight: 400; -} - -.emotion-0 code.hljs { - padding: unset; -} - -.emotion-0 .hljs-variable, -.emotion-0 .hljs-meta { - color: rgb(51,53,60); -} - -.emotion-0 .hljs-doctag, -.emotion-0 .hljs-title, -.emotion-0 .hljs-title.class_, -.emotion-0 .hljs-title.function_ { - color: rgb(34,84,192); -} - -.emotion-0 .hljs-comment { - color: rgb(109,111,119); - font-family: Ubuntu Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; - font-size: 0.875rem; - line-height: 1.125rem; - font-style: italic; -} - -.emotion-0 .hljs-tag, -.emotion-0 .hljs-type, -.emotion-0 .hljs-keyword { - color: rgb(152,29,150); - font-family: Ubuntu Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; - font-size: 0.875rem; - line-height: 1.125rem; - font-weight: 700; -} - -.emotion-0 .hljs-literal, -.emotion-0 .hljs-number { - color: rgb(126,83,5); -} - -.emotion-0 .hljs-string { - color: rgb(32,105,31); -} - -.emotion-0 .hljs-meta .hljs-keyword { - color: rgb(47,103,48); -} - -.emotion-0.code-wrap { - white-space: pre-wrap; - word-break: break-all; -} - -.emotion-0 mark { - color: rgb(217,45,32); - background-color: rgb(197,205,223); - font-weight: 400; - border-radius: 0.25rem; - padding: 0.25rem; -} - -.emotion-0.simple-one-line { - min-height: 1.25rem; - padding-left: 0.25rem; - padding-right: 0.25rem; - padding-top: 0; - padding-bottom: 0; -} - -
-
-    
-      
-        foo\\nbar
-      
-    
-  
-
-`; - -exports[`should display edit functions 1`] = ` -.emotion-0 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - overflow-x: auto; - padding: 1.5rem; -} - -.emotion-0 code { - color: rgb(51,53,60); - background: rgb(252,252,253); - font-family: Ubuntu Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; - font-size: 0.875rem; - line-height: 1.125rem; - font-weight: 400; -} - -.emotion-0 code.hljs { - padding: unset; -} - -.emotion-0 .hljs-variable, -.emotion-0 .hljs-meta { - color: rgb(51,53,60); -} - -.emotion-0 .hljs-doctag, -.emotion-0 .hljs-title, -.emotion-0 .hljs-title.class_, -.emotion-0 .hljs-title.function_ { - color: rgb(34,84,192); -} - -.emotion-0 .hljs-comment { - color: rgb(109,111,119); - font-family: Ubuntu Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; - font-size: 0.875rem; - line-height: 1.125rem; - font-style: italic; -} - -.emotion-0 .hljs-tag, -.emotion-0 .hljs-type, -.emotion-0 .hljs-keyword { - color: rgb(152,29,150); - font-family: Ubuntu Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; - font-size: 0.875rem; - line-height: 1.125rem; - font-weight: 700; -} - -.emotion-0 .hljs-literal, -.emotion-0 .hljs-number { - color: rgb(126,83,5); -} - -.emotion-0 .hljs-string { - color: rgb(32,105,31); -} - -.emotion-0 .hljs-meta .hljs-keyword { - color: rgb(47,103,48); -} - -.emotion-0.code-wrap { - white-space: pre-wrap; - word-break: break-all; -} - -.emotion-0 mark { - color: rgb(217,45,32); - background-color: rgb(197,205,223); - font-weight: 400; - border-radius: 0.25rem; - padding: 0.25rem; -} - -.emotion-0.simple-one-line { - min-height: 1.25rem; - padding-left: 0.25rem; - padding-right: 0.25rem; - padding-top: 0; - padding-bottom: 0; -} - -.emotion-3 { - box-sizing: border-box; - border: none; - outline: none; - -webkit-text-decoration: none; - text-decoration: none; - color: var(--color); - background-color: var(--background); - -webkit-transition: background-color 0.2s ease,outline 0.2s ease,color 0.2s ease; - transition: background-color 0.2s ease,outline 0.2s ease,color 0.2s ease; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - cursor: pointer; - height: 2.25rem; - border-radius: 0.5rem; - padding-left: 0.625rem; - padding-right: 0.625rem; - --background: transparent; - --backgroundHover: rgb(232,235,255); - --color: rgb(75,86,187); - --colorHover: rgb(43,51,104); - --focus: rgba(93,108,208,0.2); -} - -.emotion-3:hover, -.emotion-3:focus, -.emotion-3:active { - color: var(--colorHover); - background-color: var(--backgroundHover); -} - -.emotion-3:focus, -.emotion-3:active { - outline: 4px solid var(--focus); -} - -.emotion-3:disabled, -.emotion-3:disabled:hover { - color: rgb(166,173,194); - background-color: var(--background); - cursor: not-allowed; -} - -
-
-    
-      
-        One
-      
-       
-      
-        line
-      
-       
-      
-        command
-      
-    
-    
-  
-
-`; - -exports[`should handle multiple lines of code 1`] = ` -.emotion-0 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - overflow-x: auto; - padding: 1.5rem; -} - -.emotion-0 code { - color: rgb(51,53,60); - background: rgb(252,252,253); - font-family: Ubuntu Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; - font-size: 0.875rem; - line-height: 1.125rem; - font-weight: 400; -} - -.emotion-0 code.hljs { - padding: unset; -} - -.emotion-0 .hljs-variable, -.emotion-0 .hljs-meta { - color: rgb(51,53,60); -} - -.emotion-0 .hljs-doctag, -.emotion-0 .hljs-title, -.emotion-0 .hljs-title.class_, -.emotion-0 .hljs-title.function_ { - color: rgb(34,84,192); -} - -.emotion-0 .hljs-comment { - color: rgb(109,111,119); - font-family: Ubuntu Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; - font-size: 0.875rem; - line-height: 1.125rem; - font-style: italic; -} - -.emotion-0 .hljs-tag, -.emotion-0 .hljs-type, -.emotion-0 .hljs-keyword { - color: rgb(152,29,150); - font-family: Ubuntu Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; - font-size: 0.875rem; - line-height: 1.125rem; - font-weight: 700; -} - -.emotion-0 .hljs-literal, -.emotion-0 .hljs-number { - color: rgb(126,83,5); -} - -.emotion-0 .hljs-string { - color: rgb(32,105,31); -} - -.emotion-0 .hljs-meta .hljs-keyword { - color: rgb(47,103,48); -} - -.emotion-0.code-wrap { - white-space: pre-wrap; - word-break: break-all; -} - -.emotion-0 mark { - color: rgb(217,45,32); - background-color: rgb(197,205,223); - font-weight: 400; - border-radius: 0.25rem; - padding: 0.25rem; -} - -.emotion-0.simple-one-line { - min-height: 1.25rem; - padding-left: 0.25rem; - padding-right: 0.25rem; - padding-top: 0; - padding-bottom: 0; -} - -
-
-    
-      
-        foo:
-      
-       
-      
-        bar
-      
-      
-              
-      
-        pleh:
-      
-       
-      
-        help
-      
-      
-              
-      
-        stuff:
-      
-      
-               
-      
-        foo:
-      
-       
-      
-        bar
-      
-      
-               
-      
-        bar:
-      
-       
-      
-        foo
-      
-    
-  
-
-`; 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 (
{this.props.ruleDetails.htmlNote !== undefined && ( -
)} + {this.props.canWrite && ( + {this.props.ruleDetails.mdNote !== undefined && ( <>