From: Jeremy Davis Date: Fri, 17 Apr 2020 12:11:01 +0000 (+0200) Subject: SONAR-13260 Add primary location component when missing X-Git-Tag: 8.3.0.34182~17 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=d7173668ad32a0f19a59f8b9612112c43852c2aa;p=sonarqube.git SONAR-13260 Add primary location component when missing --- diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetViewer.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetViewer.tsx index 5519d70d6b3..c3bc8885b47 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetViewer.tsx +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetViewer.tsx @@ -88,10 +88,13 @@ export default class ComponentSourceSnippetViewer extends React.PureComponent 0 + locations: snippetGroup.locations, + issue, + addIssueLocation: + issue.secondaryLocations.length > 0 && snippetGroup.component.key === issue.component }); this.setState({ snippets }); @@ -354,7 +357,8 @@ export default class ComponentSourceSnippetViewer extends React.PureComponent + isFlow ? last && index === snippets.length - 1 : index === 0; + return (
diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewerWrapper.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewerWrapper.tsx index 0fd57eb1956..2326b711d7e 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewerWrapper.tsx +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewerWrapper.tsx @@ -31,7 +31,7 @@ import { isDuplicationBlockInRemovedComponent } from '../../../components/SourceViewer/helpers/duplications'; import { - duplicationsByLine, + duplicationsByLine as getDuplicationsByLine, issuesByComponentAndLine } from '../../../components/SourceViewer/helpers/indexing'; import { SourceViewerContext } from '../../../components/SourceViewer/SourceViewerContext'; @@ -99,7 +99,7 @@ export default class CrossComponentSourceViewerWrapper extends React.PureCompone this.setState(state => ({ duplicatedFiles: r.files, duplications: r.duplications, - duplicationsByLine: duplicationsByLine(r.duplications), + duplicationsByLine: getDuplicationsByLine(r.duplications), linePopup: r.duplications.length === 1 ? { component, index: 0, line: line.line, name: 'duplications' } @@ -223,9 +223,10 @@ export default class CrossComponentSourceViewerWrapper extends React.PureCompone ); } + const { issue, locations } = this.props; const { components, duplications, duplicationsByLine, linePopup } = this.state; const issuesByComponent = issuesByComponentAndLine(this.props.issues); - const locationsByComponent = groupLocationsByComponent(this.props.locations, components); + const locationsByComponent = groupLocationsByComponent(issue, locations, components); return (
@@ -240,12 +241,13 @@ export default class CrossComponentSourceViewerWrapper extends React.PureCompone } return ( { const snippetGroup: T.SnippetGroup = { locations: [ mockFlowLocation({ - component: 'a', + component: issue.component, textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 } }), mockFlowLocation({ - component: 'a', + component: issue.component, textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 } }) ], - ...mockSnippetsByComponent('a', [...range(2, 17), ...range(29, 39), ...range(69, 79)]) + ...mockSnippetsByComponent(issue.component, [ + ...range(2, 17), + ...range(29, 39), + ...range(69, 79) + ]) }; const wrapper = shallowRender({ issue, snippetGroup }); expect(wrapper.state('snippets')).toHaveLength(3); @@ -71,6 +75,36 @@ it('should render correctly with secondary locations', () => { expect(wrapper.state('snippets')[2]).toEqual({ index: 2, start: 69, end: 79 }); }); +it('should render correctly with flows', () => { + // issue with flows but no secondary locations + const issue = mockIssue(true, { + secondaryLocations: [], + textRange: { startLine: 7, endLine: 7, startOffset: 5, endOffset: 10 } + }); + + const snippetGroup: T.SnippetGroup = { + locations: [ + mockFlowLocation({ + component: issue.component, + textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 } + }), + mockFlowLocation({ + component: issue.component, + textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 } + }) + ], + ...mockSnippetsByComponent(issue.component, [ + ...range(2, 17), + ...range(29, 39), + ...range(69, 79) + ]) + }; + const wrapper = shallowRender({ issue, snippetGroup }); + expect(wrapper.state('snippets')).toHaveLength(2); + expect(wrapper.state('snippets')[0]).toEqual({ index: 0, start: 29, end: 39 }); + expect(wrapper.state('snippets')[1]).toEqual({ index: 1, start: 69, end: 79 }); +}); + it('should expand block', async () => { (getSources as jest.Mock).mockResolvedValueOnce( Object.values(mockSnippetsByComponent('a', range(6, 59)).sources) diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/utils-test.ts index 70408a02079..6c6d15d9d49 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/utils-test.ts +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/utils-test.ts @@ -26,11 +26,12 @@ import { createSnippets, expandSnippet, groupLocationsByComponent } from '../uti describe('groupLocationsByComponent', () => { it('should handle empty args', () => { - expect(groupLocationsByComponent([], {})).toEqual([]); + expect(groupLocationsByComponent(mockIssue(), [], {})).toEqual([]); }); it('should group correctly', () => { const results = groupLocationsByComponent( + mockIssue(), [ mockFlowLocation({ textRange: { startLine: 16, startOffset: 10, endLine: 16, endOffset: 14 } @@ -50,6 +51,7 @@ describe('groupLocationsByComponent', () => { it('should preserve step order when jumping between files', () => { const results = groupLocationsByComponent( + mockIssue(), [ mockFlowLocation({ component: 'A.js', diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/utils.ts b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/utils.ts index 12b5629fca5..1a778699d9a 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/utils.ts +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/utils.ts @@ -42,7 +42,7 @@ function collision([startA, endA]: number[], [startB, endB]: number[]) { return !(startA > endB + MERGE_DISTANCE || endA < startB - MERGE_DISTANCE); } -function getPrimaryLocation(issue: T.Issue): T.FlowLocation { +export function getPrimaryLocation(issue: T.Issue): T.FlowLocation { return { component: issue.component, textRange: issue.textRange || { @@ -128,6 +128,7 @@ export function linesForSnippets(snippets: T.Snippet[], componentLines: T.LineMa } export function groupLocationsByComponent( + issue: T.Issue, locations: T.FlowLocation[], components: { [key: string]: T.SnippetsByComponent } ) { @@ -135,14 +136,25 @@ export function groupLocationsByComponent( let currentGroup: T.SnippetGroup; const groups: T.SnippetGroup[] = []; + const addGroup = (loc: T.FlowLocation) => { + currentGroup = { + ...(components[loc.component] || unknownComponent(loc.component)), + locations: [] + }; + groups.push(currentGroup); + currentComponent = loc.component; + }; + + if ( + issue.secondaryLocations.length > 0 && + locations.every(loc => loc.component !== issue.component) + ) { + addGroup(getPrimaryLocation(issue)); + } + locations.forEach((loc, index) => { if (loc.component !== currentComponent) { - currentGroup = { - ...(components[loc.component] || unknownComponent(loc.component)), - locations: [] - }; - groups.push(currentGroup); - currentComponent = loc.component; + addGroup(loc); } loc.index = index; currentGroup.locations.push(loc);