/* * 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 React from 'react'; import { intersection } from 'lodash'; import Line from './components/Line'; import { translate } from '../../helpers/l10n'; import { getLinearLocations } from './helpers/issueLocations'; import type { Duplication, SourceLine } from './types'; import type { Issue, FlowLocation } from '../issue/types'; import type { LinearIssueLocation } from './helpers/indexing'; const EMPTY_ARRAY = []; const ZERO_LINE = { code: '', duplicated: false, line: 0 }; export default class SourceViewerCode extends React.PureComponent { props: {| displayAllIssues: boolean, duplications?: Array, duplicationsByLine: { [number]: Array }, duplicatedFiles?: Array<{ key: string }>, filterLine?: SourceLine => boolean, hasSourcesAfter: boolean, hasSourcesBefore: boolean, highlightedLine: number | null, highlightedLocations?: Array, highlightedLocationMessage?: { index: number, text: string }, highlightedSymbols: Array, issues: Array, issuesByLine: { [number]: Array }, issueLocationsByLine: { [number]: Array }, loadDuplications: SourceLine => void, loadSourcesAfter: () => void, loadSourcesBefore: () => void, loadingSourcesAfter: boolean, loadingSourcesBefore: boolean, onCoverageClick: (SourceLine, HTMLElement) => void, onDuplicationClick: (number, number) => void, onIssueChange: Issue => void, onIssueSelect: string => void, onIssueUnselect: () => void, onIssuesOpen: SourceLine => void, onIssuesClose: SourceLine => void, onLineClick: (SourceLine, HTMLElement) => void, onLocationSelect?: number => void, onSCMClick: (SourceLine, HTMLElement) => void, onSymbolClick: (Array) => void, openIssuesByLine: { [number]: boolean }, scroll?: HTMLElement => void, selectedIssue: string | null, sources: Array, symbolsByLine: { [number]: Array } |}; getDuplicationsForLine(line: SourceLine) { return this.props.duplicationsByLine[line.line] || EMPTY_ARRAY; } getIssuesForLine(line: SourceLine): Array { return this.props.issuesByLine[line.line] || EMPTY_ARRAY; } getIssueLocationsForLine(line: SourceLine) { return this.props.issueLocationsByLine[line.line] || EMPTY_ARRAY; } getSecondaryIssueLocationsForLine( line: SourceLine ): Array<{ from: number, to: number, line: number, index: number, startLine: number }> { const { highlightedLocations } = this.props; if (!highlightedLocations) { return EMPTY_ARRAY; } return highlightedLocations.reduce((locations, location, index) => { const linearLocations = getLinearLocations(location.textRange) .filter(l => l.line === line.line) .map(l => ({ ...l, startLine: location.textRange.startLine, index })); return [...locations, ...linearLocations]; }, []); } renderLine = ( line: SourceLine, index: number, displayCoverage: boolean, displayDuplications: boolean, displayFiltered: boolean, displayIssues: boolean ) => { const { filterLine, highlightedLocationMessage, selectedIssue, sources } = this.props; const filtered = filterLine ? filterLine(line) : null; const secondaryIssueLocations = this.getSecondaryIssueLocationsForLine(line); const duplicationsCount = this.props.duplications ? this.props.duplications.length : 0; const issuesForLine = this.getIssuesForLine(line); // for the following properties pass null if the line for sure is not impacted const symbolsForLine = this.props.symbolsByLine[line.line] || []; const { highlightedSymbols } = this.props; let optimizedHighlightedSymbols = intersection(symbolsForLine, highlightedSymbols); if (!optimizedHighlightedSymbols.length) { optimizedHighlightedSymbols = undefined; } const optimizedSelectedIssue = selectedIssue != null && issuesForLine.find(issue => issue.key === selectedIssue) ? selectedIssue : null; const optimizedSecondaryIssueLocations = secondaryIssueLocations.length > 0 ? secondaryIssueLocations : EMPTY_ARRAY; const optimizedLocationMessage = highlightedLocationMessage != null && optimizedSecondaryIssueLocations.some( location => location.index === highlightedLocationMessage.index ) ? highlightedLocationMessage : undefined; return ( 0 ? sources[index - 1] : undefined} scroll={this.props.scroll} secondaryIssueLocations={optimizedSecondaryIssueLocations} selectedIssue={optimizedSelectedIssue} /> ); }; render() { const { sources } = this.props; const hasCoverage = sources.some(s => s.coverageStatus != null); const hasDuplications = sources.some(s => s.duplicated); const displayFiltered = this.props.filterLine != null; const hasIssues = this.props.issues.length > 0; const hasFileIssues = hasIssues && this.props.issues.some(issue => !issue.textRange); return (
{this.props.hasSourcesBefore &&
{this.props.loadingSourcesBefore ?
{translate('source_viewer.loading_more_code')}
: }
} {hasFileIssues && this.renderLine( ZERO_LINE, -1, hasCoverage, hasDuplications, displayFiltered, hasIssues )} {sources.map((line, index) => this.renderLine(line, index, hasCoverage, hasDuplications, displayFiltered, hasIssues) )}
{this.props.hasSourcesAfter &&
{this.props.loadingSourcesAfter ?
{translate('source_viewer.loading_more_code')}
: }
}
); } }