]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12334 Add primary location in snippets
authorJeremy <jeremy.davis@sonarsource.com>
Tue, 13 Aug 2019 07:19:36 +0000 (09:19 +0200)
committerSonarTech <sonartech@sonarsource.com>
Tue, 13 Aug 2019 18:21:06 +0000 (20:21 +0200)
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetViewer.tsx
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetViewer-test.tsx
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/ComponentSourceSnippetViewer-test.tsx.snap
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/utils-test.ts
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/utils.ts

index 0093e08ea024a68e3e1f637853dbfd738fedf213..651582619c1d115df4ae3e40b8aa802f0c8d260c 100644 (file)
@@ -87,7 +87,12 @@ export default class ComponentSourceSnippetViewer extends React.PureComponent<Pr
   }
 
   createSnippetsFromProps() {
-    const snippets = createSnippets(this.props.snippetGroup.locations, this.props.last);
+    const snippets = createSnippets(
+      this.props.snippetGroup.locations,
+      this.props.last,
+      this.props.issue.secondaryLocations.length > 0 ? this.props.issue : undefined
+    );
+
     this.setState({ snippets });
   }
 
index b4c433d478be8d6f0e8f564f9d5e813c377ee0c8..334a1edfe1b206f1e2979f1b670e6d01c822a6d2 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { mount, ReactWrapper, shallow } from 'enzyme';
-import { times } from 'lodash';
+import { range, times } from 'lodash';
 import * as React from 'react';
 import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
 import { getSources } from '../../../../api/components';
@@ -45,6 +45,29 @@ it('should render correctly', () => {
   expect(shallowRender()).toMatchSnapshot();
 });
 
+it('should render correctly with secondary locations', () => {
+  // issue with secondary locations but no flows
+  const issue = mockIssue(true, {
+    flows: [],
+    textRange: { startLine: 5, endLine: 5, startOffset: 5, endOffset: 10 }
+  });
+
+  const snippetGroup: T.SnippetGroup = {
+    locations: [
+      mockFlowLocation({
+        component: 'a',
+        textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 }
+      }),
+      mockFlowLocation({
+        component: 'a',
+        textRange: { startLine: 54, endLine: 54, startOffset: 0, endOffset: 0 }
+      })
+    ],
+    ...mockSnippetsByComponent('a', [...range(3, 15), 32, 33, 34, 35, 36, 52, 53, 54, 55, 56])
+  };
+  expect(shallowRender({ issue, snippetGroup })).toMatchSnapshot();
+});
+
 it('should expand block', async () => {
   (getSources as jest.Mock).mockResolvedValueOnce(
     Object.values(mockSnippetsByComponent('a', [22, 23, 24, 25, 26, 27, 28, 29, 30, 31]).sources)
index 136d07a4e9e1c24be2f1d38b1f2ee2dff5bc1158..ba353d16b39e4ad0ced143610d1b1456c564cb7c 100644 (file)
@@ -34,3 +34,610 @@ exports[`should render correctly 1`] = `
   />
 </div>
 `;
+
+exports[`should render correctly with secondary locations 1`] = `
+<div
+  className="component-source-container"
+>
+  <SourceViewerHeaderSlim
+    branchLike={
+      Object {
+        "analysisDate": "2018-01-01",
+        "isMain": true,
+        "name": "master",
+      }
+    }
+    expandable={true}
+    loading={false}
+    onExpand={[Function]}
+    sourceViewerFile={
+      Object {
+        "key": "a",
+        "measures": Object {
+          "coverage": "85.2",
+          "duplicationDensity": "1.0",
+          "issues": "12",
+          "lines": "56",
+        },
+        "path": "a",
+        "project": "my-project",
+        "projectName": "MyProject",
+        "q": "FIL",
+        "uuid": "foo-bar",
+      }
+    }
+  />
+  <div
+    id="snippet-wrapper-0"
+    key="0"
+  >
+    <SnippetViewer
+      branchLike={
+        Object {
+          "analysisDate": "2018-01-01",
+          "isMain": true,
+          "name": "master",
+        }
+      }
+      component={
+        Object {
+          "key": "a",
+          "measures": Object {
+            "coverage": "85.2",
+            "duplicationDensity": "1.0",
+            "issues": "12",
+            "lines": "56",
+          },
+          "path": "a",
+          "project": "my-project",
+          "projectName": "MyProject",
+          "q": "FIL",
+          "uuid": "foo-bar",
+        }
+      }
+      expandBlock={[Function]}
+      handleCloseIssues={[Function]}
+      handleLinePopupToggle={[Function]}
+      handleOpenIssues={[Function]}
+      handleSymbolClick={[Function]}
+      highlightedLocationMessage={
+        Object {
+          "index": 0,
+          "text": "",
+        }
+      }
+      highlightedSymbols={Array []}
+      index={0}
+      issue={
+        Object {
+          "actions": Array [],
+          "component": "main.js",
+          "componentLongName": "main.js",
+          "componentQualifier": "FIL",
+          "componentUuid": "foo1234",
+          "creationDate": "2017-03-01T09:36:01+0100",
+          "flows": Array [],
+          "fromHotspot": false,
+          "key": "AVsae-CQS-9G3txfbFN2",
+          "line": 25,
+          "message": "Reduce the number of conditional operators (4) used in the expression",
+          "organization": "myorg",
+          "project": "myproject",
+          "projectKey": "foo",
+          "projectName": "Foo",
+          "projectOrganization": "org",
+          "rule": "javascript:S1067",
+          "ruleName": "foo",
+          "secondaryLocations": Array [
+            Object {
+              "component": "main.js",
+              "textRange": Object {
+                "endLine": 2,
+                "endOffset": 2,
+                "startLine": 1,
+                "startOffset": 1,
+              },
+            },
+            Object {
+              "component": "main.js",
+              "textRange": Object {
+                "endLine": 2,
+                "endOffset": 2,
+                "startLine": 1,
+                "startOffset": 1,
+              },
+            },
+          ],
+          "severity": "MAJOR",
+          "status": "OPEN",
+          "textRange": Object {
+            "endLine": 5,
+            "endOffset": 10,
+            "startLine": 5,
+            "startOffset": 5,
+          },
+          "transitions": Array [],
+          "type": "BUG",
+        }
+      }
+      issuesByLine={Object {}}
+      last={false}
+      loadDuplications={[Function]}
+      locations={Array []}
+      locationsByLine={Object {}}
+      onIssueChange={[MockFunction]}
+      onIssuePopupToggle={[MockFunction]}
+      onLocationSelect={[MockFunction]}
+      openIssuesByLine={Object {}}
+      renderDuplicationPopup={[Function]}
+      scroll={[MockFunction]}
+      snippet={
+        Array [
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 3,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 4,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 5,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 6,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 7,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 8,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 9,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 10,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 11,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 12,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 13,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 14,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+        ]
+      }
+    />
+  </div>
+  <div
+    id="snippet-wrapper-1"
+    key="1"
+  >
+    <SnippetViewer
+      branchLike={
+        Object {
+          "analysisDate": "2018-01-01",
+          "isMain": true,
+          "name": "master",
+        }
+      }
+      component={
+        Object {
+          "key": "a",
+          "measures": Object {
+            "coverage": "85.2",
+            "duplicationDensity": "1.0",
+            "issues": "12",
+            "lines": "56",
+          },
+          "path": "a",
+          "project": "my-project",
+          "projectName": "MyProject",
+          "q": "FIL",
+          "uuid": "foo-bar",
+        }
+      }
+      expandBlock={[Function]}
+      handleCloseIssues={[Function]}
+      handleLinePopupToggle={[Function]}
+      handleOpenIssues={[Function]}
+      handleSymbolClick={[Function]}
+      highlightedLocationMessage={
+        Object {
+          "index": 0,
+          "text": "",
+        }
+      }
+      highlightedSymbols={Array []}
+      index={1}
+      issue={
+        Object {
+          "actions": Array [],
+          "component": "main.js",
+          "componentLongName": "main.js",
+          "componentQualifier": "FIL",
+          "componentUuid": "foo1234",
+          "creationDate": "2017-03-01T09:36:01+0100",
+          "flows": Array [],
+          "fromHotspot": false,
+          "key": "AVsae-CQS-9G3txfbFN2",
+          "line": 25,
+          "message": "Reduce the number of conditional operators (4) used in the expression",
+          "organization": "myorg",
+          "project": "myproject",
+          "projectKey": "foo",
+          "projectName": "Foo",
+          "projectOrganization": "org",
+          "rule": "javascript:S1067",
+          "ruleName": "foo",
+          "secondaryLocations": Array [
+            Object {
+              "component": "main.js",
+              "textRange": Object {
+                "endLine": 2,
+                "endOffset": 2,
+                "startLine": 1,
+                "startOffset": 1,
+              },
+            },
+            Object {
+              "component": "main.js",
+              "textRange": Object {
+                "endLine": 2,
+                "endOffset": 2,
+                "startLine": 1,
+                "startOffset": 1,
+              },
+            },
+          ],
+          "severity": "MAJOR",
+          "status": "OPEN",
+          "textRange": Object {
+            "endLine": 5,
+            "endOffset": 10,
+            "startLine": 5,
+            "startOffset": 5,
+          },
+          "transitions": Array [],
+          "type": "BUG",
+        }
+      }
+      issuesByLine={Object {}}
+      last={false}
+      loadDuplications={[Function]}
+      locations={Array []}
+      locationsByLine={Object {}}
+      onIssueChange={[MockFunction]}
+      onIssuePopupToggle={[MockFunction]}
+      onLocationSelect={[MockFunction]}
+      openIssuesByLine={Object {}}
+      renderDuplicationPopup={[Function]}
+      scroll={[MockFunction]}
+      snippet={
+        Array [
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 32,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 33,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 34,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 35,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 36,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+        ]
+      }
+    />
+  </div>
+  <div
+    id="snippet-wrapper-2"
+    key="2"
+  >
+    <SnippetViewer
+      branchLike={
+        Object {
+          "analysisDate": "2018-01-01",
+          "isMain": true,
+          "name": "master",
+        }
+      }
+      component={
+        Object {
+          "key": "a",
+          "measures": Object {
+            "coverage": "85.2",
+            "duplicationDensity": "1.0",
+            "issues": "12",
+            "lines": "56",
+          },
+          "path": "a",
+          "project": "my-project",
+          "projectName": "MyProject",
+          "q": "FIL",
+          "uuid": "foo-bar",
+        }
+      }
+      expandBlock={[Function]}
+      handleCloseIssues={[Function]}
+      handleLinePopupToggle={[Function]}
+      handleOpenIssues={[Function]}
+      handleSymbolClick={[Function]}
+      highlightedLocationMessage={
+        Object {
+          "index": 0,
+          "text": "",
+        }
+      }
+      highlightedSymbols={Array []}
+      index={2}
+      issue={
+        Object {
+          "actions": Array [],
+          "component": "main.js",
+          "componentLongName": "main.js",
+          "componentQualifier": "FIL",
+          "componentUuid": "foo1234",
+          "creationDate": "2017-03-01T09:36:01+0100",
+          "flows": Array [],
+          "fromHotspot": false,
+          "key": "AVsae-CQS-9G3txfbFN2",
+          "line": 25,
+          "message": "Reduce the number of conditional operators (4) used in the expression",
+          "organization": "myorg",
+          "project": "myproject",
+          "projectKey": "foo",
+          "projectName": "Foo",
+          "projectOrganization": "org",
+          "rule": "javascript:S1067",
+          "ruleName": "foo",
+          "secondaryLocations": Array [
+            Object {
+              "component": "main.js",
+              "textRange": Object {
+                "endLine": 2,
+                "endOffset": 2,
+                "startLine": 1,
+                "startOffset": 1,
+              },
+            },
+            Object {
+              "component": "main.js",
+              "textRange": Object {
+                "endLine": 2,
+                "endOffset": 2,
+                "startLine": 1,
+                "startOffset": 1,
+              },
+            },
+          ],
+          "severity": "MAJOR",
+          "status": "OPEN",
+          "textRange": Object {
+            "endLine": 5,
+            "endOffset": 10,
+            "startLine": 5,
+            "startOffset": 5,
+          },
+          "transitions": Array [],
+          "type": "BUG",
+        }
+      }
+      issuesByLine={Object {}}
+      last={false}
+      loadDuplications={[Function]}
+      locations={Array []}
+      locationsByLine={Object {}}
+      onIssueChange={[MockFunction]}
+      onIssuePopupToggle={[MockFunction]}
+      onLocationSelect={[MockFunction]}
+      openIssuesByLine={Object {}}
+      renderDuplicationPopup={[Function]}
+      scroll={[MockFunction]}
+      snippet={
+        Array [
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 52,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 53,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 54,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 55,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+          Object {
+            "code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
+            "coverageStatus": "covered",
+            "coveredConditions": 2,
+            "duplicated": false,
+            "isNew": true,
+            "line": 56,
+            "scmAuthor": "simon.brandhof@sonarsource.com",
+            "scmDate": "2018-12-11T10:48:39+0100",
+            "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
+          },
+        ]
+      }
+    />
+  </div>
+</div>
+`;
index 7d17cb3d81c12e3a3be630d78377df0732ac7570..a682cf767d2b5fb3dd69ab12c120d943e2821e48 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { mockFlowLocation, mockSnippetsByComponent } from '../../../../helpers/testMocks';
+import {
+  mockFlowLocation,
+  mockIssue,
+  mockSnippetsByComponent
+} from '../../../../helpers/testMocks';
 import { createSnippets, expandSnippet, groupLocationsByComponent } from '../utils';
 
 describe('groupLocationsByComponent', () => {
@@ -138,6 +142,24 @@ describe('createSnippets', () => {
     expect(results[0]).toEqual({ index: 0, start: 14, end: 24 });
     expect(results[1]).toEqual({ index: 1, start: 45, end: 49 });
   });
+
+  it("should prepend the issue's main location if necessary", () => {
+    const results = createSnippets(
+      [
+        mockFlowLocation({
+          textRange: { startLine: 47, startOffset: 2, endLine: 47, endOffset: 3 }
+        }),
+        mockFlowLocation({
+          textRange: { startLine: 22, startOffset: 2, endLine: 22, endOffset: 3 }
+        })
+      ],
+      false,
+      mockIssue(false, { textRange: { startLine: 5, endLine: 5, startOffset: 0, endOffset: 0 } })
+    );
+
+    expect(results).toHaveLength(3);
+    expect(results[0]).toEqual({ index: 0, start: 3, end: 14 });
+  });
 });
 
 describe('expandSnippet', () => {
index c900f543268814f23ed8ab29c2b7b74030d0ad1e..fb31d71156390d5a59224b2aa4c75155faae890c 100644 (file)
@@ -42,46 +42,65 @@ function collision([startA, endA]: number[], [startB, endB]: number[]) {
   return !(startA > endB + MERGE_DISTANCE || endA < startB - MERGE_DISTANCE);
 }
 
-export function createSnippets(locations: T.FlowLocation[], last: boolean): T.Snippet[] {
+function getPrimaryLocation(issue: T.Issue): T.FlowLocation {
+  return {
+    component: issue.component,
+    textRange: issue.textRange || {
+      endLine: 0,
+      endOffset: 0,
+      startLine: 0,
+      startOffset: 0
+    }
+  };
+}
+
+export function createSnippets(
+  locations: T.FlowLocation[],
+  last: boolean,
+  issue?: T.Issue
+): T.Snippet[] {
   // For each location's range (2 above and 2 below), and then compare with other ranges
   // to merge snippets that collide.
-  return locations.reduce((snippets: T.Snippet[], loc, index) => {
-    const startIndex = Math.max(1, loc.textRange.startLine - LINES_ABOVE);
-    const endIndex =
-      loc.textRange.endLine +
-      (last && index === locations.length - 1 ? LINES_BELOW_LAST : LINES_BELOW);
-
-    let firstCollision: { start: number; end: number } | undefined;
-
-    // Remove ranges that collide into the first collision
-    snippets = snippets.filter(snippet => {
-      if (collision([snippet.start, snippet.end], [startIndex, endIndex])) {
-        let keep = false;
-        // Check if we've already collided
-        if (!firstCollision) {
-          firstCollision = snippet;
-          keep = true;
+  return (issue ? [getPrimaryLocation(issue), ...locations] : locations).reduce(
+    (snippets: T.Snippet[], loc, index) => {
+      const startIndex = Math.max(1, loc.textRange.startLine - LINES_ABOVE);
+      const endIndex =
+        loc.textRange.endLine +
+        (issue || (last && index === locations.length - 1) ? LINES_BELOW_LAST : LINES_BELOW);
+
+      let firstCollision: { start: number; end: number } | undefined;
+
+      // Remove ranges that collide into the first collision
+      snippets = snippets.filter(snippet => {
+        if (collision([snippet.start, snippet.end], [startIndex, endIndex])) {
+          let keep = false;
+          // Check if we've already collided
+          if (!firstCollision) {
+            firstCollision = snippet;
+            keep = true;
+          }
+          // Merge with first collision:
+          firstCollision.start = Math.min(startIndex, snippet.start, firstCollision.start);
+          firstCollision.end = Math.max(endIndex, snippet.end, firstCollision.end);
+
+          // remove the range if it was not the first collision
+          return keep;
         }
-        // Merge with first collision:
-        firstCollision.start = Math.min(startIndex, snippet.start, firstCollision.start);
-        firstCollision.end = Math.max(endIndex, snippet.end, firstCollision.end);
+        return true;
+      });
 
-        // remove the range if it was not the first collision
-        return keep;
+      if (firstCollision === undefined) {
+        snippets.push({
+          start: startIndex,
+          end: endIndex,
+          index
+        });
       }
-      return true;
-    });
-
-    if (firstCollision === undefined) {
-      snippets.push({
-        start: startIndex,
-        end: endIndex,
-        index
-      });
-    }
 
-    return snippets;
-  }, []);
+      return snippets;
+    },
+    []
+  );
 }
 
 export function linesForSnippets(snippets: T.Snippet[], componentLines: T.LineMap) {