aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src
diff options
context:
space:
mode:
authorStas Vilchik <stas-vilchik@users.noreply.github.com>2017-04-13 09:16:32 +0200
committerGitHub <noreply@github.com>2017-04-13 09:16:32 +0200
commit81266317348bddac9d237aee07141024d76db9b4 (patch)
tree5a81cc68aca22e5a6bada170764799bfb8f3f5c8 /server/sonar-web/src
parentee72d1678ac7d13fc093ebb90115eb807f7a2568 (diff)
downloadsonarqube-81266317348bddac9d237aee07141024d76db9b4.tar.gz
sonarqube-81266317348bddac9d237aee07141024d76db9b4.zip
support multiple highlighted symbols (#1927)
Diffstat (limited to 'server/sonar-web/src')
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.js18
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.js17
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/Line.js6
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.js18
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.js4
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/helpers/__tests__/highlight-test.js56
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/helpers/__tests__/indexing-test.js35
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/helpers/highlight.js8
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/helpers/indexing.js12
9 files changed, 136 insertions, 38 deletions
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.js b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.js
index ce5ba82b3bb..c0cae1208f8 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.js
+++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.js
@@ -20,7 +20,7 @@
// @flow
import React from 'react';
import classNames from 'classnames';
-import { uniqBy } from 'lodash';
+import { intersection, uniqBy } from 'lodash';
import SourceViewerHeader from './SourceViewerHeader';
import SourceViewerCode from './SourceViewerCode';
import SourceViewerIssueLocations from './SourceViewerIssueLocations';
@@ -91,7 +91,7 @@ type State = {
duplicatedFiles?: Array<{ key: string }>,
hasSourcesAfter: boolean,
highlightedLine: number | null,
- highlightedSymbol: string | null,
+ highlightedSymbols: Array<string>,
issues?: Array<Issue>,
issuesByLine: { [number]: Array<string> },
issueLocationsByLine: { [number]: Array<LinearIssueLocation> },
@@ -143,7 +143,7 @@ export default class SourceViewerBase extends React.Component {
duplicationsByLine: {},
hasSourcesAfter: false,
highlightedLine: props.highlightedLine || null,
- highlightedSymbol: null,
+ highlightedSymbols: [],
issuesByLine: {},
issueLocationsByLine: {},
issueSecondaryLocationsByIssueByLine: {},
@@ -477,10 +477,12 @@ export default class SourceViewerBase extends React.Component {
this.displayLinePopup(line.line, element);
};
- handleSymbolClick = (symbol: string) => {
- this.setState(prevState => ({
- highlightedSymbol: prevState.highlightedSymbol === symbol ? null : symbol
- }));
+ handleSymbolClick = (symbols: Array<string>) => {
+ this.setState(state => {
+ const shouldDisable = intersection(state.highlightedSymbols, symbols).length > 0;
+ const highlightedSymbols = shouldDisable ? [] : symbols;
+ return { highlightedSymbols };
+ });
};
handleSCMClick = (line: SourceLine, element: HTMLElement) => {
@@ -544,7 +546,7 @@ export default class SourceViewerBase extends React.Component {
hasSourcesAfter={this.state.hasSourcesAfter}
filterLine={this.props.filterLine}
highlightedLine={this.state.highlightedLine}
- highlightedSymbol={this.state.highlightedSymbol}
+ highlightedSymbols={this.state.highlightedSymbols}
issues={this.state.issues}
issuesByLine={this.state.issuesByLine}
issueLocationsByLine={this.state.issueLocationsByLine}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.js b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.js
index be6d1a27173..8b9cfb46bd5 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.js
+++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.js
@@ -19,6 +19,7 @@
*/
// @flow
import React from 'react';
+import { intersection } from 'lodash';
import Line from './components/Line';
import { translate } from '../../helpers/l10n';
import type { Duplication, SourceLine } from './types';
@@ -48,7 +49,7 @@ export default class SourceViewerCode extends React.PureComponent {
hasSourcesAfter: boolean,
hasSourcesBefore: boolean,
highlightedLine: number | null,
- highlightedSymbol: string | null,
+ highlightedSymbols: Array<string>,
issues: Array<Issue>,
issuesByLine: { [number]: Array<string> },
issueLocationsByLine: { [number]: Array<LinearIssueLocation> },
@@ -68,7 +69,7 @@ export default class SourceViewerCode extends React.PureComponent {
onLineClick: (SourceLine, HTMLElement) => void,
onSCMClick: (SourceLine, HTMLElement) => void,
onLocationSelect: (flowIndex: number, locationIndex: number) => void,
- onSymbolClick: (string) => void,
+ onSymbolClick: (Array<string>) => void,
openIssuesByLine: { [number]: boolean },
selectedIssue: string | null,
selectedIssueLocation: IndexedIssueLocation | null,
@@ -124,11 +125,11 @@ export default class SourceViewerCode extends React.PureComponent {
// for the following properties pass null if the line for sure is not impacted
const symbolsForLine = this.props.symbolsByLine[line.line] || [];
- const { highlightedSymbol } = this.props;
- const optimizedHighlightedSymbol = highlightedSymbol != null &&
- symbolsForLine.includes(highlightedSymbol)
- ? highlightedSymbol
- : null;
+ const { highlightedSymbols } = this.props;
+ let optimizedHighlightedSymbols = intersection(symbolsForLine, highlightedSymbols);
+ if (!optimizedHighlightedSymbols.length) {
+ optimizedHighlightedSymbols = EMPTY_ARRAY;
+ }
const optimizedSelectedIssue = selectedIssue != null && issuesForLine.includes(selectedIssue)
? selectedIssue
@@ -155,7 +156,7 @@ export default class SourceViewerCode extends React.PureComponent {
duplicationsCount={duplicationsCount}
filtered={filtered}
highlighted={line.line === this.props.highlightedLine}
- highlightedSymbol={optimizedHighlightedSymbol}
+ highlightedSymbols={optimizedHighlightedSymbols}
issueLocations={this.getIssueLocationsForLine(line)}
issues={issuesForLine}
key={line.line}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/Line.js b/server/sonar-web/src/main/js/components/SourceViewer/components/Line.js
index bc5e80c2e1f..74f3dabbfae 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/Line.js
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/Line.js
@@ -46,7 +46,7 @@ type Props = {
duplicationsCount: number,
filtered: boolean | null,
highlighted: boolean,
- highlightedSymbol: string | null,
+ highlightedSymbols: Array<string>,
issueLocations: Array<LinearIssueLocation>,
issues: Array<string>,
line: SourceLine,
@@ -60,7 +60,7 @@ type Props = {
onIssuesClose: (SourceLine) => void,
onSCMClick: (SourceLine, HTMLElement) => void,
onLocationSelect: (flowIndex: number, locationIndex: number) => void,
- onSymbolClick: (string) => void,
+ onSymbolClick: (Array<string>) => void,
openIssues: boolean,
previousLine?: SourceLine,
selectedIssue: string | null,
@@ -136,7 +136,7 @@ export default class Line extends React.PureComponent {
</td>}
<LineCode
- highlightedSymbol={this.props.highlightedSymbol}
+ highlightedSymbols={this.props.highlightedSymbols}
issueKeys={this.props.issues}
issueLocations={this.props.issueLocations}
line={line}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.js b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.js
index d8da31965d3..b5813f76365 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.js
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.js
@@ -36,13 +36,13 @@ import type {
} from '../helpers/indexing';
type Props = {
- highlightedSymbol: string | null,
+ highlightedSymbols: Array<string>,
issueKeys: Array<string>,
issueLocations: Array<LinearIssueLocation>,
line: SourceLine,
onIssueSelect: (issueKey: string) => void,
onLocationSelect: (flowIndex: number, locationIndex: number) => void,
- onSymbolClick: (symbol: string) => void,
+ onSymbolClick: (Array<string>) => void,
// $FlowFixMe
secondaryIssueLocations: Array<IndexedIssueLocation>,
secondaryIssueLocationMessages: Array<IndexedIssueLocationMessage>,
@@ -109,9 +109,9 @@ export default class LineCode extends React.PureComponent {
handleSymbolClick = (e: Object) => {
e.preventDefault();
- const key = e.currentTarget.className.match(/sym-\d+/);
- if (key && key[0]) {
- this.props.onSymbolClick(key[0]);
+ const keys = e.currentTarget.className.match(/sym-\d+/g);
+ if (keys.length > 0) {
+ this.props.onSymbolClick(keys);
}
};
@@ -165,7 +165,7 @@ export default class LineCode extends React.PureComponent {
render() {
const {
- highlightedSymbol,
+ highlightedSymbols,
issueKeys,
issueLocations,
line,
@@ -179,9 +179,9 @@ export default class LineCode extends React.PureComponent {
let tokens = [...this.state.tokens];
- if (highlightedSymbol) {
- tokens = highlightSymbol(tokens, highlightedSymbol);
- }
+ highlightedSymbols.forEach(symbol => {
+ tokens = highlightSymbol(tokens, symbol);
+ });
if (issueLocations.length > 0) {
tokens = highlightIssueLocations(tokens, issueLocations);
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.js b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.js
index fa78dfa5afb..3cc6793b214 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.js
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.js
@@ -33,7 +33,7 @@ it('render code', () => {
const selectedIssueLocation = { from: 6, to: 9, line: 3, flowIndex: 0, locationIndex: 0 };
const wrapper = shallow(
<LineCode
- highlightedSymbol="sym1"
+ highlightedSymbols={['sym1']}
issueKeys={['issue-1', 'issue-2']}
issueLocations={issueLocations}
line={line}
@@ -61,7 +61,7 @@ it('should handle empty location message', () => {
const selectedIssueLocation = { from: 6, to: 9, line: 3, flowIndex: 0, locationIndex: 0 };
const wrapper = shallow(
<LineCode
- highlightedSymbol="sym1"
+ highlightedSymbols={['sym1']}
issueKeys={['issue-1', 'issue-2']}
issueLocations={issueLocations}
line={line}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/helpers/__tests__/highlight-test.js b/server/sonar-web/src/main/js/components/SourceViewer/helpers/__tests__/highlight-test.js
new file mode 100644
index 00000000000..9a7e5439277
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/SourceViewer/helpers/__tests__/highlight-test.js
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.
+ */
+// @flow
+import { highlightSymbol } from '../highlight';
+
+describe('highlightSymbol', () => {
+ it('should not highlight symbols with similar beginning', () => {
+ // test all positions of sym-X in the string: beginning, middle and ending
+ const tokens = [
+ { className: 'sym-18 b', text: 'foo' },
+ { className: 'a sym-18', text: 'foo' },
+ { className: 'a sym-18 b', text: 'foo' },
+ { className: 'sym-1 d', text: 'bar' },
+ { className: 'c sym-1', text: 'bar' },
+ { className: 'c sym-1 d', text: 'bar' }
+ ];
+ expect(highlightSymbol(tokens, 'sym-1')).toEqual([
+ { className: 'sym-18 b', text: 'foo' },
+ { className: 'a sym-18', text: 'foo' },
+ { className: 'a sym-18 b', text: 'foo' },
+ { className: 'sym-1 d highlighted', text: 'bar' },
+ { className: 'c sym-1 highlighted', text: 'bar' },
+ { className: 'c sym-1 d highlighted', text: 'bar' }
+ ]);
+ });
+
+ it('should highlight symbols marked twice', () => {
+ const tokens = [
+ { className: 'sym sym-1 sym sym-2', text: 'foo' },
+ { className: 'sym sym-1', text: 'bar' },
+ { className: 'sym sym-2', text: 'qux' }
+ ];
+ expect(highlightSymbol(tokens, 'sym-1')).toEqual([
+ { className: 'sym sym-1 sym sym-2 highlighted', text: 'foo' },
+ { className: 'sym sym-1 highlighted', text: 'bar' },
+ { className: 'sym sym-2', text: 'qux' }
+ ]);
+ });
+});
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/helpers/__tests__/indexing-test.js b/server/sonar-web/src/main/js/components/SourceViewer/helpers/__tests__/indexing-test.js
new file mode 100644
index 00000000000..ef5f66e2d72
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/SourceViewer/helpers/__tests__/indexing-test.js
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 { symbolsByLine } from '../indexing';
+
+describe('symbolsByLine', () => {
+ it('should highlight symbols marked twice', () => {
+ const lines = [
+ { line: 1, code: '<span class="sym-54 sym"><span class="sym-56 sym">foo</span></span>' },
+ { line: 2, code: '<span class="sym-56 sym">bar</span>' },
+ { line: 3, code: '<span class="k">qux</span>' }
+ ];
+ expect(symbolsByLine(lines)).toEqual({
+ 1: ['sym-54', 'sym-56'],
+ 2: ['sym-56'],
+ 3: []
+ });
+ });
+});
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/helpers/highlight.js b/server/sonar-web/src/main/js/components/SourceViewer/helpers/highlight.js
index c742f2b0d4c..8bba53b8755 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/helpers/highlight.js
+++ b/server/sonar-web/src/main/js/components/SourceViewer/helpers/highlight.js
@@ -45,13 +45,15 @@ export const splitByTokens = (code: string, rootClassName: string = ''): Tokens
return tokens;
};
-export const highlightSymbol = (tokens: Tokens, symbol: string): Tokens =>
- tokens.map(
+export const highlightSymbol = (tokens: Tokens, symbol: string): Tokens => {
+ const symbolRegExp = new RegExp(`\\b${symbol}\\b`);
+ return tokens.map(
token =>
- token.className.includes(symbol)
+ symbolRegExp.test(token.className)
? { ...token, className: `${token.className} highlighted` }
: token
);
+};
/**
* Intersect two ranges
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/helpers/indexing.js b/server/sonar-web/src/main/js/components/SourceViewer/helpers/indexing.js
index b1c79ac0f11..36bf7e73b3a 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/helpers/indexing.js
+++ b/server/sonar-web/src/main/js/components/SourceViewer/helpers/indexing.js
@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
+import { flatten } from 'lodash';
import { splitByTokens } from './highlight';
import { getLinearLocations, getIssueLocations } from './issueLocations';
import type { Issue } from '../../issue/types';
@@ -149,12 +150,13 @@ export const symbolsByLine = (sources: Array<SourceLine>) => {
const index = {};
sources.forEach(line => {
const tokens = splitByTokens(line.code);
- index[line.line] = tokens
- .map(token => {
- const key = token.className.match(/sym-\d+/);
- return key && key[0];
+ const symbols = flatten(
+ tokens.map(token => {
+ const keys = token.className.match(/sym-\d+/g);
+ return keys != null ? keys : [];
})
- .filter(key => key);
+ );
+ index[line.line] = symbols.filter(key => key);
});
return index;
};