diff options
author | David Cho-Lerat <david.lerat@sonarsource.com> | 2022-11-29 17:26:51 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-12-01 20:03:12 +0000 |
commit | fd9fe4dba0dbb7c359613131e64e84110ea75a09 (patch) | |
tree | 539a99ec8cab0a0b8a3e831d2116f676712be777 | |
parent | 6c42508ad46f28bd0e3c29a245fcd095a9714e6f (diff) | |
download | sonarqube-fd9fe4dba0dbb7c359613131e64e84110ea75a09.tar.gz sonarqube-fd9fe4dba0dbb7c359613131e64e84110ea75a09.zip |
SONAR-17592 Code highlighting for issue messages and code locations
26 files changed, 453 insertions, 35 deletions
diff --git a/server/sonar-web/src/main/js/app/theme.js b/server/sonar-web/src/main/js/app/theme.js index f84b78a6ebd..4882dcc8eed 100644 --- a/server/sonar-web/src/main/js/app/theme.js +++ b/server/sonar-web/src/main/js/app/theme.js @@ -181,8 +181,11 @@ module.exports = { neutral800: '#333333', white: '#FFFFFF', + whitea18: 'rgba(255, 255, 255, 0.18)', + whitea60: 'rgba(255, 255, 255, 0.60)', black: '#000000', + blacka06: 'rgba(0, 0, 0, 0.06)', blacka38: 'rgba(0, 0, 0, 0.38)', blacka60: 'rgba(0, 0, 0, 0.60)', blacka75: 'rgba(0, 0, 0, 0.75)', diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx index 243ac925d1d..3286e4f6360 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx @@ -25,6 +25,7 @@ import { updateIssue } from '../../../components/issue/actions'; import IssueActionsBar from '../../../components/issue/components/IssueActionsBar'; import IssueChangelog from '../../../components/issue/components/IssueChangelog'; import IssueMessageTags from '../../../components/issue/components/IssueMessageTags'; +import { IssueMessageHighlighting } from '../../../components/issue/IssueMessageHighlighting'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { isInput, isShortcut } from '../../../helpers/keyboardEventHelpers'; import { KeyboardKeys } from '../../../helpers/keycodes'; @@ -136,7 +137,12 @@ export default class IssueHeader extends React.PureComponent<Props, State> { <> <div className="display-flex-center display-flex-space-between big-padded-top"> <h1 className="text-bold spacer-right"> - <span className="spacer-right">{issue.message}</span> + <span className="spacer-right issue-header" aria-label={issue.message}> + <IssueMessageHighlighting + message={issue.message} + messageFormattings={issue.messageFormattings} + /> + </span> <IssueMessageTags engine={issue.externalRuleEngine} quickFixAvailable={quickFixAvailable} diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx index 59504f3543b..5ff855693b6 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx @@ -20,6 +20,7 @@ import classNames from 'classnames'; import * as React from 'react'; import { ButtonPlain } from '../../../components/controls/buttons'; +import { IssueMessageHighlighting } from '../../../components/issue/IssueMessageHighlighting'; import FlowsList from '../../../components/locations/FlowsList'; import LocationsList from '../../../components/locations/LocationsList'; import TypeHelper from '../../../components/shared/TypeHelper'; @@ -82,7 +83,10 @@ export default class ConciseIssueBox extends React.PureComponent<Props> { innerRef={(node) => (this.messageElement = node)} onClick={this.handleClick} > - {issue.message} + <IssueMessageHighlighting + message={issue.message} + messageFormattings={issue.messageFormattings} + /> </ButtonPlain> <div className="concise-issue-box-attributes"> <TypeHelper className="display-block little-spacer-right" type={issue.type} /> diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueBox-test.tsx.snap index d1fc86dd005..bf45b6e28e5 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueBox-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueBox-test.tsx.snap @@ -11,7 +11,9 @@ exports[`should render correctly 1`] = ` innerRef={[Function]} onClick={[Function]} > - Reduce the number of conditional operators (4) used in the expression + <IssueMessageHighlighting + message="Reduce the number of conditional operators (4) used in the expression" + /> </ButtonPlain> <div className="concise-issue-box-attributes" @@ -70,7 +72,9 @@ exports[`should render correctly 2`] = ` innerRef={[Function]} onClick={[Function]} > - Reduce the number of conditional operators (4) used in the expression + <IssueMessageHighlighting + message="Reduce the number of conditional operators (4) used in the expression" + /> </ButtonPlain> <div className="concise-issue-box-attributes" diff --git a/server/sonar-web/src/main/js/apps/issues/styles.css b/server/sonar-web/src/main/js/apps/issues/styles.css index b544224e116..d5d1019dec5 100644 --- a/server/sonar-web/src/main/js/apps/issues/styles.css +++ b/server/sonar-web/src/main/js/apps/issues/styles.css @@ -68,6 +68,10 @@ transition: background-color 0.3s ease, border-color 0.3s ease; } +.concise-issue-box .issue-message-highlight-CODE { + background-color: var(--blacka06); +} + .concise-issue-box:hover { border: 2px dashed var(--blue); } @@ -87,8 +91,7 @@ .concise-issue-box-message:hover, .concise-issue-box-message:active { display: block; - overflow: hidden; - text-overflow: ellipsis; + word-break: break-word; font-weight: bold !important; color: inherit !important; text-align: left; @@ -175,6 +178,11 @@ } } +.issue-header .issue-message-highlight-CODE { + background-color: var(--blacka06); + border-radius: 5px; +} + .issue-location { display: inline-block; vertical-align: top; diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx index e14ce3f6e4f..61415ca2f34 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx @@ -20,9 +20,11 @@ import classNames from 'classnames'; import * as React from 'react'; import { IssueSourceViewerScrollContext } from '../../../apps/issues/components/IssueSourceViewerScrollContext'; +import { MessageFormatting } from '../../../types/issues'; import { LinearIssueLocation, SourceLine } from '../../../types/types'; import LocationIndex from '../../common/LocationIndex'; import Tooltip from '../../controls/Tooltip'; +import { IssueMessageHighlighting } from '../../issue/IssueMessageHighlighting'; import { highlightIssueLocations, highlightSymbol, @@ -95,8 +97,11 @@ export default class LineCode extends React.PureComponent<React.PropsWithChildre const selected = highlightedLocationMessage !== undefined && highlightedLocationMessage.index === marker; const loc = secondaryIssueLocations.find((loc) => loc.index === marker); - const message = loc && loc.text; - renderedTokens.push(this.renderMarker(marker, message, selected, leadingMarker)); + const message = loc?.text; + const messageFormattings = loc?.textFormatting; + renderedTokens.push( + this.renderMarker(marker, message, messageFormattings, selected, leadingMarker) + ); }); } renderedTokens.push( @@ -112,12 +117,24 @@ export default class LineCode extends React.PureComponent<React.PropsWithChildre return renderedTokens; } - renderMarker(index: number, message: string | undefined, selected: boolean, leading: boolean) { + renderMarker( + index: number, + message: string | undefined, + messageFormattings: MessageFormatting[] | undefined, + selected: boolean, + leading: boolean + ) { const { onLocationSelect } = this.props; const onClick = onLocationSelect ? () => onLocationSelect(index) : undefined; return ( - <Tooltip key={`marker-${index}`} overlay={message} placement="top"> + <Tooltip + key={`marker-${index}`} + overlay={ + <IssueMessageHighlighting message={message} messageFormattings={messageFormattings} /> + } + placement="top" + > <LocationIndex leading={leading} onClick={onClick} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap index 308cb3b368f..24aff0fb75d 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap @@ -108,7 +108,11 @@ exports[`render code: with secondary location 1`] = ` </span> <Tooltip key="marker-1" - overlay="secondary-location-msg" + overlay={ + <IssueMessageHighlighting + message="secondary-location-msg" + /> + } placement="top" > <LocationIndex diff --git a/server/sonar-web/src/main/js/components/SourceViewer/helpers/issueLocations.ts b/server/sonar-web/src/main/js/components/SourceViewer/helpers/issueLocations.ts index 241e36ac4b2..8a39a3db3d1 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/helpers/issueLocations.ts +++ b/server/sonar-web/src/main/js/components/SourceViewer/helpers/issueLocations.ts @@ -51,6 +51,7 @@ export function getSecondaryIssueLocationsForLine( startLine: location.textRange.startLine, index: location.index, text: location.msg, + textFormatting: location.msgFormattings, })) : []; return [...locations, ...linearLocations]; diff --git a/server/sonar-web/src/main/js/components/common/LocationMessage.css b/server/sonar-web/src/main/js/components/common/LocationMessage.css index a9269cd242a..f72b16629a8 100644 --- a/server/sonar-web/src/main/js/components/common/LocationMessage.css +++ b/server/sonar-web/src/main/js/components/common/LocationMessage.css @@ -23,8 +23,7 @@ line-height: 16px; padding: 0 6px; font-size: var(--smallFontSize); - text-overflow: ellipsis; - overflow: hidden; + word-break: break-word; } .location-index + .location-message { diff --git a/server/sonar-web/src/main/js/components/controls/Tooltip.css b/server/sonar-web/src/main/js/components/controls/Tooltip.css index cd9696a00a2..fc00a8e5d29 100644 --- a/server/sonar-web/src/main/js/components/controls/Tooltip.css +++ b/server/sonar-web/src/main/js/components/controls/Tooltip.css @@ -120,6 +120,10 @@ pointer-events: none; } +.tooltip .issue-message-highlight-CODE { + background-color: var(--whitea18); +} + @keyframes fadeIn { from { opacity: 0; diff --git a/server/sonar-web/src/main/js/components/issue/Issue.css b/server/sonar-web/src/main/js/components/issue/Issue.css index 397d8781c14..d42221c1de7 100644 --- a/server/sonar-web/src/main/js/components/issue/Issue.css +++ b/server/sonar-web/src/main/js/components/issue/Issue.css @@ -262,6 +262,15 @@ margin: 10px 0px; } +.issue-message-highlight-CODE { + background-color: var(--whitea60); + border-radius: 4px; + font-family: var(--sourceCodeFontFamily); + font-weight: 400; + line-height: 1.4em; + padding: 2px 2px 0; +} + .issue-message-box.secondary-issue { background-color: var(--secondIssueBgColor); } diff --git a/server/sonar-web/src/main/js/components/issue/IssueMessageBox.tsx b/server/sonar-web/src/main/js/components/issue/IssueMessageBox.tsx index 2abe1d8da8e..bad13effc47 100644 --- a/server/sonar-web/src/main/js/components/issue/IssueMessageBox.tsx +++ b/server/sonar-web/src/main/js/components/issue/IssueMessageBox.tsx @@ -23,6 +23,7 @@ import { colors } from '../../app/theme'; import { Issue } from '../../types/types'; import IssueTypeIcon from '../icons/IssueTypeIcon'; import './Issue.css'; +import { IssueMessageHighlighting } from './IssueMessageHighlighting'; export interface IssueMessageBoxProps { selected: boolean; @@ -50,7 +51,10 @@ export function IssueMessageBox(props: IssueMessageBoxProps, ref: React.Forwarde fill={colors.baseFontColor} query={issue.type} /> - {issue.message} + <IssueMessageHighlighting + message={issue.message} + messageFormattings={issue.messageFormattings} + /> </div> ); } diff --git a/server/sonar-web/src/main/js/components/issue/IssueMessageHighlighting.tsx b/server/sonar-web/src/main/js/components/issue/IssueMessageHighlighting.tsx new file mode 100644 index 00000000000..df82725fc69 --- /dev/null +++ b/server/sonar-web/src/main/js/components/issue/IssueMessageHighlighting.tsx @@ -0,0 +1,102 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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 classNames from 'classnames'; +import * as React from 'react'; +import { MessageFormatting, MessageFormattingType } from '../../types/issues'; + +export interface IssueMessageHighlightingProps { + message?: string; + messageFormattings?: MessageFormatting[]; +} + +export function IssueMessageHighlighting(props: IssueMessageHighlightingProps) { + const { message, messageFormattings } = props; + + if (!message) { + return null; + } + + if (!(messageFormattings && messageFormattings.length > 0)) { + return <>{message}</>; + } + + let previousEnd = 0; + + const sanitizedFormattings = [...messageFormattings] + .sort((a, b) => a.start - b.start) + .reduce((acc, messageFormatting) => { + const { type } = messageFormatting; + + if (type !== MessageFormattingType.CODE) { + return acc; + } + + const { start } = messageFormatting; + let { end } = messageFormatting; + + end = Math.min(message.length, end); + + if (start < 0 || end === start || end < start) { + return acc; + } + + if (acc.length > 0) { + const { start: previousStart, end: previousEnd } = acc[acc.length - 1]; + + if (start <= previousEnd) { + acc[acc.length - 1] = { + start: previousStart, + end: Math.max(previousEnd, end), + type, + }; + + return acc; + } + } + + acc.push({ start, end, type }); + + return acc; + }, [] as typeof messageFormattings); + + return ( + <span> + {sanitizedFormattings.map(({ start, end, type }) => { + const beginning = previousEnd; + previousEnd = end; + + return ( + <React.Fragment key={`${message}-${start}-${end}`}> + {message.slice(beginning, start)} + <span + className={classNames({ + 'issue-message-highlight-CODE': type === MessageFormattingType.CODE, + })} + > + {message.slice(start, end)} + </span> + </React.Fragment> + ); + })} + + {message.slice(previousEnd)} + </span> + ); +} diff --git a/server/sonar-web/src/main/js/components/issue/__tests__/IssueMessageHighlighting-test.tsx b/server/sonar-web/src/main/js/components/issue/__tests__/IssueMessageHighlighting-test.tsx new file mode 100644 index 00000000000..9a45e3fa72e --- /dev/null +++ b/server/sonar-web/src/main/js/components/issue/__tests__/IssueMessageHighlighting-test.tsx @@ -0,0 +1,74 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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 React from 'react'; +import { renderComponent } from '../../../helpers/testReactTestingUtils'; +import { MessageFormattingType } from '../../../types/issues'; +import { + IssueMessageHighlighting, + IssueMessageHighlightingProps, +} from '../IssueMessageHighlighting'; + +it.each([ + [undefined, undefined], + ['message', undefined], + ['message', []], + ['message', [{ start: 1, end: 4, type: 'something else' as MessageFormattingType }]], + [ + 'message', + [ + { start: 5, end: 6, type: MessageFormattingType.CODE }, + { start: 1, end: 4, type: MessageFormattingType.CODE }, + ], + ], + [ + 'a somewhat longer message with overlapping ranges', + [{ start: -1, end: 1, type: MessageFormattingType.CODE }], + ], + [ + 'a somewhat longer message with overlapping ranges', + [{ start: 48, end: 70, type: MessageFormattingType.CODE }], + ], + [ + 'a somewhat longer message with overlapping ranges', + [{ start: 0, end: 0, type: MessageFormattingType.CODE }], + ], + [ + 'a somewhat longer message with overlapping ranges', + [ + { start: 11, end: 17, type: MessageFormattingType.CODE }, + { start: 2, end: 25, type: MessageFormattingType.CODE }, + { start: 25, end: 2, type: MessageFormattingType.CODE }, + ], + ], + [ + 'a somewhat longer message with overlapping ranges', + [ + { start: 18, end: 30, type: MessageFormattingType.CODE }, + { start: 2, end: 25, type: MessageFormattingType.CODE }, + ], + ], +])('should format the string with highlights', (message, messageFormattings) => { + const { asFragment } = renderIssueMessageHighlighting({ message, messageFormattings }); + expect(asFragment()).toMatchSnapshot(); +}); + +function renderIssueMessageHighlighting(props: Partial<IssueMessageHighlightingProps> = {}) { + return renderComponent(<IssueMessageHighlighting {...props} />); +} diff --git a/server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueMessageHighlighting-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueMessageHighlighting-test.tsx.snap new file mode 100644 index 00000000000..00802d08bc0 --- /dev/null +++ b/server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueMessageHighlighting-test.tsx.snap @@ -0,0 +1,100 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should format the string with highlights 1`] = `<DocumentFragment />`; + +exports[`should format the string with highlights 2`] = ` +<DocumentFragment> + message +</DocumentFragment> +`; + +exports[`should format the string with highlights 3`] = ` +<DocumentFragment> + message +</DocumentFragment> +`; + +exports[`should format the string with highlights 4`] = ` +<DocumentFragment> + <span> + message + </span> +</DocumentFragment> +`; + +exports[`should format the string with highlights 5`] = ` +<DocumentFragment> + <span> + m + <span + class="issue-message-highlight-CODE" + > + ess + </span> + a + <span + class="issue-message-highlight-CODE" + > + g + </span> + e + </span> +</DocumentFragment> +`; + +exports[`should format the string with highlights 6`] = ` +<DocumentFragment> + <span> + a somewhat longer message with overlapping ranges + </span> +</DocumentFragment> +`; + +exports[`should format the string with highlights 7`] = ` +<DocumentFragment> + <span> + a somewhat longer message with overlapping range + <span + class="issue-message-highlight-CODE" + > + s + </span> + </span> +</DocumentFragment> +`; + +exports[`should format the string with highlights 8`] = ` +<DocumentFragment> + <span> + a somewhat longer message with overlapping ranges + </span> +</DocumentFragment> +`; + +exports[`should format the string with highlights 9`] = ` +<DocumentFragment> + <span> + a + <span + class="issue-message-highlight-CODE" + > + somewhat longer message + </span> + with overlapping ranges + </span> +</DocumentFragment> +`; + +exports[`should format the string with highlights 10`] = ` +<DocumentFragment> + <span> + a + <span + class="issue-message-highlight-CODE" + > + somewhat longer message with + </span> + overlapping ranges + </span> +</DocumentFragment> +`; diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueMessage.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueMessage.tsx index c692a2f01ae..2b2b0519867 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueMessage.tsx +++ b/server/sonar-web/src/main/js/components/issue/components/IssueMessage.tsx @@ -20,8 +20,10 @@ import * as React from 'react'; import { ButtonLink } from '../../../components/controls/buttons'; import { translate } from '../../../helpers/l10n'; +import { MessageFormatting } from '../../../types/issues'; import { RuleStatus } from '../../../types/rules'; import { WorkspaceContext } from '../../workspace/context'; +import { IssueMessageHighlighting } from '../IssueMessageHighlighting'; import IssueMessageTags from './IssueMessageTags'; export interface IssueMessageProps { @@ -29,20 +31,30 @@ export interface IssueMessageProps { quickFixAvailable?: boolean; displayWhyIsThisAnIssue?: boolean; message: string; + messageFormattings?: MessageFormatting[]; ruleKey: string; ruleStatus?: RuleStatus; } export default function IssueMessage(props: IssueMessageProps) { - const { engine, quickFixAvailable, message, ruleKey, ruleStatus, displayWhyIsThisAnIssue } = - props; + const { + engine, + quickFixAvailable, + message, + messageFormattings, + ruleKey, + ruleStatus, + displayWhyIsThisAnIssue, + } = props; const { openRule } = React.useContext(WorkspaceContext); return ( <> <div className="display-inline-flex-center issue-message break-word"> - <span className="spacer-right">{message}</span> + <span className="spacer-right"> + <IssueMessageHighlighting message={message} messageFormattings={messageFormattings} /> + </span> <IssueMessageTags engine={engine} quickFixAvailable={quickFixAvailable} diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx index 3fb1ec8dae3..bb90389607c 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx +++ b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx @@ -80,6 +80,7 @@ export default function IssueTitleBar(props: IssueTitleBarProps) { quickFixAvailable={issue.quickFixAvailable} displayWhyIsThisAnIssue={displayWhyIsThisAnIssue} message={issue.message} + messageFormattings={issue.messageFormattings} ruleKey={issue.rule} ruleStatus={issue.ruleStatus as RuleStatus | undefined} /> diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueMessage-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueMessage-test.tsx.snap index 3a8e73822e2..bc1d76c8876 100644 --- a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueMessage-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueMessage-test.tsx.snap @@ -8,7 +8,9 @@ exports[`should render correctly: default 1`] = ` <span className="spacer-right" > - Reduce the number of conditional operators (4) used in the expression + <IssueMessageHighlighting + message="Reduce the number of conditional operators (4) used in the expression" + /> </span> <IssueMessageTags /> </div> @@ -30,7 +32,9 @@ exports[`should render correctly: hide why is it an issue 1`] = ` <span className="spacer-right" > - Reduce the number of conditional operators (4) used in the expression + <IssueMessageHighlighting + message="Reduce the number of conditional operators (4) used in the expression" + /> </span> <IssueMessageTags /> </div> @@ -45,7 +49,9 @@ exports[`should render correctly: is deprecated rule 1`] = ` <span className="spacer-right" > - Reduce the number of conditional operators (4) used in the expression + <IssueMessageHighlighting + message="Reduce the number of conditional operators (4) used in the expression" + /> </span> <IssueMessageTags ruleStatus="DEPRECATED" @@ -69,7 +75,9 @@ exports[`should render correctly: is removed rule 1`] = ` <span className="spacer-right" > - Reduce the number of conditional operators (4) used in the expression + <IssueMessageHighlighting + message="Reduce the number of conditional operators (4) used in the expression" + /> </span> <IssueMessageTags ruleStatus="REMOVED" @@ -93,7 +101,9 @@ exports[`should render correctly: with engine info 1`] = ` <span className="spacer-right" > - Reduce the number of conditional operators (4) used in the expression + <IssueMessageHighlighting + message="Reduce the number of conditional operators (4) used in the expression" + /> </span> <IssueMessageTags engine="js" @@ -117,7 +127,9 @@ exports[`should render correctly: with quick fix 1`] = ` <span className="spacer-right" > - Reduce the number of conditional operators (4) used in the expression + <IssueMessageHighlighting + message="Reduce the number of conditional operators (4) used in the expression" + /> </span> <IssueMessageTags quickFixAvailable={true} diff --git a/server/sonar-web/src/main/js/components/locations/CrossFileLocationNavigator.tsx b/server/sonar-web/src/main/js/components/locations/CrossFileLocationNavigator.tsx index cb6581f1b3b..da04e78f600 100644 --- a/server/sonar-web/src/main/js/components/locations/CrossFileLocationNavigator.tsx +++ b/server/sonar-web/src/main/js/components/locations/CrossFileLocationNavigator.tsx @@ -20,6 +20,7 @@ import * as React from 'react'; import { translateWithParameters } from '../../helpers/l10n'; import { collapsePath } from '../../helpers/path'; +import { MessageFormatting } from '../../types/issues'; import { FlowLocation } from '../../types/types'; import './CrossFileLocationNavigator.css'; import SingleFileLocationNavigator from './SingleFileLocationNavigator'; @@ -105,12 +106,17 @@ export default class CrossFileLocationNavigator extends React.PureComponent<Prop return groups; }; - renderLocation = (index: number, message: string | undefined) => { + renderLocation = ( + index: number, + message: string | undefined, + messageFormattings: MessageFormatting[] | undefined + ) => { return ( <SingleFileLocationNavigator index={index} key={index} message={message} + messageFormattings={messageFormattings} onClick={this.props.onLocationSelect} selected={index === this.props.selectedLocationIndex} /> @@ -132,18 +138,28 @@ export default class CrossFileLocationNavigator extends React.PureComponent<Prop </div> {group.locations.length > 0 && ( <div className="location-file-locations"> - {onlyFirst && this.renderLocation(firstLocationIndex, group.locations[0].msg)} + {onlyFirst && + this.renderLocation( + firstLocationIndex, + group.locations[0].msg, + group.locations[0].msgFormattings + )} {onlyLast && this.renderLocation( firstLocationIndex + lastLocationIndex, - group.locations[lastLocationIndex].msg + group.locations[lastLocationIndex].msg, + group.locations[lastLocationIndex].msgFormattings )} {!onlyFirst && !onlyLast && group.locations.map((location, index) => - this.renderLocation(firstLocationIndex + index, location.msg) + this.renderLocation( + firstLocationIndex + index, + location.msg, + location.msgFormattings + ) )} </div> )} diff --git a/server/sonar-web/src/main/js/components/locations/FlowsList.tsx b/server/sonar-web/src/main/js/components/locations/FlowsList.tsx index 088ef497a2f..6c8532567c7 100644 --- a/server/sonar-web/src/main/js/components/locations/FlowsList.tsx +++ b/server/sonar-web/src/main/js/components/locations/FlowsList.tsx @@ -74,6 +74,7 @@ export default function FlowsList(props: Props) { <SingleFileLocationNavigator index={locIndex} message={location.msg} + messageFormattings={location.msgFormattings} onClick={props.onLocationSelect} selected={locIndex === selectedLocationIndex} /> diff --git a/server/sonar-web/src/main/js/components/locations/LocationsList.tsx b/server/sonar-web/src/main/js/components/locations/LocationsList.tsx index 635095b65af..cdc62d64339 100644 --- a/server/sonar-web/src/main/js/components/locations/LocationsList.tsx +++ b/server/sonar-web/src/main/js/components/locations/LocationsList.tsx @@ -52,13 +52,14 @@ export default class LocationsList extends React.PureComponent<Props> { ); } return ( - <ul className="spacer-top "> + <ul className="spacer-top"> {locations.map((location, index) => ( // eslint-disable-next-line react/no-array-index-key <li className="display-flex-column" key={index}> <SingleFileLocationNavigator index={index} message={location.msg} + messageFormattings={location.msgFormattings} onClick={this.props.onLocationSelect} selected={index === selectedLocationIndex} /> diff --git a/server/sonar-web/src/main/js/components/locations/SingleFileLocationNavigator.tsx b/server/sonar-web/src/main/js/components/locations/SingleFileLocationNavigator.tsx index 5a7e2819f52..c4dcced8497 100644 --- a/server/sonar-web/src/main/js/components/locations/SingleFileLocationNavigator.tsx +++ b/server/sonar-web/src/main/js/components/locations/SingleFileLocationNavigator.tsx @@ -19,14 +19,17 @@ */ import classNames from 'classnames'; import * as React from 'react'; +import { MessageFormatting } from '../../types/issues'; import LocationIndex from '../common/LocationIndex'; import LocationMessage from '../common/LocationMessage'; import { ButtonPlain } from '../controls/buttons'; +import { IssueMessageHighlighting } from '../issue/IssueMessageHighlighting'; import './SingleFileLocationNavigator.css'; interface Props { index: number; message: string | undefined; + messageFormattings?: MessageFormatting[]; onClick: (index: number) => void; selected: boolean; } @@ -59,7 +62,7 @@ export default class SingleFileLocationNavigator extends React.PureComponent<Pro }; render() { - const { index, message, selected } = this.props; + const { index, message, messageFormattings, selected } = this.props; return ( <ButtonPlain @@ -73,7 +76,9 @@ export default class SingleFileLocationNavigator extends React.PureComponent<Pro onClick={this.handleClick} > <LocationIndex>{index + 1}</LocationIndex> - <LocationMessage>{message}</LocationMessage> + <LocationMessage> + {<IssueMessageHighlighting message={message} messageFormattings={messageFormattings} />} + </LocationMessage> </ButtonPlain> ); } diff --git a/server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/LocationsList-test.tsx.snap b/server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/LocationsList-test.tsx.snap index 7e992bb77ff..8421b5c343e 100644 --- a/server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/LocationsList-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/LocationsList-test.tsx.snap @@ -2,7 +2,7 @@ exports[`should render locations in the same file 1`] = ` <ul - className="spacer-top " + className="spacer-top" > <li className="display-flex-column" diff --git a/server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/SingleFileLocationsNavigator-test.tsx.snap b/server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/SingleFileLocationsNavigator-test.tsx.snap index ec2618e04cc..3231f68233d 100644 --- a/server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/SingleFileLocationsNavigator-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/locations/__tests__/__snapshots__/SingleFileLocationsNavigator-test.tsx.snap @@ -12,7 +12,11 @@ exports[`should render correctly: index 1 1`] = ` <LocationIndex> 1 </LocationIndex> - <LocationMessage /> + <LocationMessage> + <IssueMessageHighlighting + message="" + /> + </LocationMessage> </ButtonPlain> `; @@ -28,6 +32,10 @@ exports[`should render correctly: index 2 1`] = ` <LocationIndex> 2 </LocationIndex> - <LocationMessage /> + <LocationMessage> + <IssueMessageHighlighting + message="" + /> + </LocationMessage> </ButtonPlain> `; diff --git a/server/sonar-web/src/main/js/types/issues.ts b/server/sonar-web/src/main/js/types/issues.ts index e4b1bbf3312..bf885d57bd5 100644 --- a/server/sonar-web/src/main/js/types/issues.ts +++ b/server/sonar-web/src/main/js/types/issues.ts @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { FlowLocation, Issue, Paging, TextRange } from './types'; +import { Issue, Paging, TextRange } from './types'; import { UserBase } from './users'; export enum IssueType { @@ -49,6 +49,24 @@ interface Comment { updatable: boolean; } +export interface MessageFormatting { + start: number; + end: number; + type: MessageFormattingType; +} + +export enum MessageFormattingType { + CODE = 'CODE', +} + +export interface RawFlowLocation { + component: string; + index?: number; + msg?: string; + msgFormattings?: MessageFormatting[]; + textRange: TextRange; +} + export interface RawIssue { actions: string[]; transitions?: string[]; @@ -60,10 +78,11 @@ export interface RawIssue { flows?: Array<{ type?: string; description?: string; - locations?: Array<Omit<FlowLocation, 'componentName'>>; + locations?: RawFlowLocation[]; }>; key: string; line?: number; + messageFormattings?: MessageFormatting[]; project: string; rule: string; resolution?: string; diff --git a/server/sonar-web/src/main/js/types/types.ts b/server/sonar-web/src/main/js/types/types.ts index e62a646955f..806046112e8 100644 --- a/server/sonar-web/src/main/js/types/types.ts +++ b/server/sonar-web/src/main/js/types/types.ts @@ -20,6 +20,7 @@ import { RuleDescriptionSection } from '../apps/coding-rules/rule'; import { ComponentQualifier } from './component'; +import { MessageFormatting } from './issues'; import { UserActive, UserBase } from './users'; export type Dict<T> = { [key: string]: T }; @@ -206,6 +207,7 @@ export interface FlowLocation { componentName?: string; index?: number; msg?: string; + msgFormattings?: MessageFormatting[]; textRange: TextRange; } @@ -252,6 +254,7 @@ export interface Issue { flowsWithType: Flow[]; line?: number; message: string; + messageFormattings?: MessageFormatting[]; project: string; projectName: string; projectKey: string; @@ -324,6 +327,7 @@ export interface LinearIssueLocation { line: number; startLine?: number; text?: string; + textFormatting?: MessageFormatting[]; to: number; } |