Have each component in a separate file
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { Location } from "./locations";
|
||||
import { Location } from "./locations/Location";
|
||||
import { CellValue } from "../../common/bqrs-cli-types";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -20,7 +20,8 @@ import { parseSarifLocation, isNoLocation } from "../../common/sarif-utils";
|
||||
import { ScrollIntoViewHelper } from "./scroll-into-view-helper";
|
||||
import { sendTelemetry } from "../common/telemetry";
|
||||
import { AlertTableHeader } from "./alert-table-header";
|
||||
import { SarifLocation, SarifMessageWithLocations } from "./locations";
|
||||
import { SarifMessageWithLocations } from "./locations/SarifMessageWithLocations";
|
||||
import { SarifLocation } from "./locations/SarifLocation";
|
||||
|
||||
export type AlertTableProps = ResultTableProps & {
|
||||
resultSet: InterpretedResultSet<SarifInterpretationData>;
|
||||
|
||||
@@ -1,213 +0,0 @@
|
||||
import * as React from "react";
|
||||
import * as Sarif from "sarif";
|
||||
import { ResolvableLocationValue, UrlValue } from "../../common/bqrs-cli-types";
|
||||
import { convertNonPrintableChars } from "../../common/text-utils";
|
||||
import {
|
||||
isLineColumnLoc,
|
||||
isStringLoc,
|
||||
isWholeFileLoc,
|
||||
tryGetResolvableLocation,
|
||||
} from "../../common/bqrs-utils";
|
||||
import {
|
||||
parseSarifLocation,
|
||||
parseSarifPlainTextMessage,
|
||||
} from "../../common/sarif-utils";
|
||||
import { basename } from "path";
|
||||
import { jumpToLocation } from "./result-table-utils";
|
||||
import { useCallback, useMemo } from "react";
|
||||
|
||||
function NonLocation({
|
||||
msg,
|
||||
locationHint,
|
||||
}: {
|
||||
msg?: string;
|
||||
locationHint?: string;
|
||||
}) {
|
||||
if (msg === undefined) return null;
|
||||
return <span title={locationHint}>{msg}</span>;
|
||||
}
|
||||
|
||||
function ClickableLocation({
|
||||
loc,
|
||||
label,
|
||||
databaseUri,
|
||||
title,
|
||||
jumpToLocationCallback,
|
||||
}: {
|
||||
loc: ResolvableLocationValue;
|
||||
label: string;
|
||||
databaseUri: string;
|
||||
title?: string;
|
||||
jumpToLocationCallback?: () => void;
|
||||
}): JSX.Element {
|
||||
const jumpToLocationHandler = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
jumpToLocation(loc, databaseUri);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (jumpToLocationCallback) {
|
||||
jumpToLocationCallback();
|
||||
}
|
||||
},
|
||||
[loc, databaseUri, jumpToLocationCallback],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/*
|
||||
eslint-disable-next-line
|
||||
jsx-a11y/anchor-is-valid,
|
||||
*/}
|
||||
<a
|
||||
href="#"
|
||||
className="vscode-codeql__result-table-location-link"
|
||||
title={title}
|
||||
onClick={jumpToLocationHandler}
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A clickable location link.
|
||||
*/
|
||||
export function Location({
|
||||
loc,
|
||||
label,
|
||||
databaseUri,
|
||||
title,
|
||||
jumpToLocationCallback,
|
||||
}: {
|
||||
loc?: UrlValue;
|
||||
label?: string;
|
||||
databaseUri?: string;
|
||||
title?: string;
|
||||
jumpToLocationCallback?: () => void;
|
||||
}): JSX.Element {
|
||||
const resolvableLoc = useMemo(() => tryGetResolvableLocation(loc), [loc]);
|
||||
const displayLabel = useMemo(() => convertNonPrintableChars(label!), [label]);
|
||||
if (loc === undefined) {
|
||||
return <NonLocation msg={displayLabel} />;
|
||||
} else if (isStringLoc(loc)) {
|
||||
return <a href={loc}>{loc}</a>;
|
||||
} else if (databaseUri === undefined || resolvableLoc === undefined) {
|
||||
return <NonLocation msg={displayLabel} locationHint={title} />;
|
||||
} else {
|
||||
return (
|
||||
<ClickableLocation
|
||||
loc={resolvableLoc}
|
||||
label={displayLabel}
|
||||
databaseUri={databaseUri}
|
||||
title={title}
|
||||
jumpToLocationCallback={jumpToLocationCallback}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A clickable SARIF location link.
|
||||
*
|
||||
* Custom text can be provided, but otherwise the text will be
|
||||
* a human-readable form of the location itself.
|
||||
*/
|
||||
export function SarifLocation({
|
||||
text,
|
||||
loc,
|
||||
sourceLocationPrefix,
|
||||
databaseUri,
|
||||
jumpToLocationCallback,
|
||||
}: {
|
||||
text?: string;
|
||||
loc?: Sarif.Location;
|
||||
sourceLocationPrefix: string;
|
||||
databaseUri: string;
|
||||
jumpToLocationCallback: () => void;
|
||||
}) {
|
||||
const parsedLoc = useMemo(
|
||||
() => loc && parseSarifLocation(loc, sourceLocationPrefix),
|
||||
[loc, sourceLocationPrefix],
|
||||
);
|
||||
if (parsedLoc === undefined || "hint" in parsedLoc) {
|
||||
return (
|
||||
<NonLocation
|
||||
msg={text || "[no location]"}
|
||||
locationHint={parsedLoc?.hint}
|
||||
/>
|
||||
);
|
||||
} else if (isWholeFileLoc(parsedLoc)) {
|
||||
return (
|
||||
<Location
|
||||
loc={parsedLoc}
|
||||
label={text || `${basename(parsedLoc.userVisibleFile)}`}
|
||||
databaseUri={databaseUri}
|
||||
title={text ? undefined : `${parsedLoc.userVisibleFile}`}
|
||||
jumpToLocationCallback={jumpToLocationCallback}
|
||||
/>
|
||||
);
|
||||
} else if (isLineColumnLoc(parsedLoc)) {
|
||||
return (
|
||||
<Location
|
||||
loc={parsedLoc}
|
||||
label={
|
||||
text ||
|
||||
`${basename(parsedLoc.userVisibleFile)}:${parsedLoc.startLine}:${
|
||||
parsedLoc.startColumn
|
||||
}`
|
||||
}
|
||||
databaseUri={databaseUri}
|
||||
title={text ? undefined : `${parsedLoc.userVisibleFile}`}
|
||||
jumpToLocationCallback={jumpToLocationCallback}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a SARIF message and populates clickable locations.
|
||||
*/
|
||||
export function SarifMessageWithLocations({
|
||||
msg,
|
||||
relatedLocations,
|
||||
sourceLocationPrefix,
|
||||
databaseUri,
|
||||
jumpToLocationCallback,
|
||||
}: {
|
||||
msg: string;
|
||||
relatedLocations: Sarif.Location[];
|
||||
sourceLocationPrefix: string;
|
||||
databaseUri: string;
|
||||
jumpToLocationCallback: () => void;
|
||||
}) {
|
||||
const relatedLocationsById: Map<number, Sarif.Location> = new Map();
|
||||
for (const loc of relatedLocations) {
|
||||
if (loc.id !== undefined) {
|
||||
relatedLocationsById.set(loc.id, loc);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{parseSarifPlainTextMessage(msg).map((part, i) => {
|
||||
if (typeof part === "string") {
|
||||
return <span key={i}>{part}</span>;
|
||||
} else {
|
||||
return (
|
||||
<SarifLocation
|
||||
key={i}
|
||||
text={part.text}
|
||||
loc={relatedLocationsById.get(part.dest)}
|
||||
sourceLocationPrefix={sourceLocationPrefix}
|
||||
databaseUri={databaseUri}
|
||||
jumpToLocationCallback={jumpToLocationCallback}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import * as React from "react";
|
||||
import { useCallback } from "react";
|
||||
import { ResolvableLocationValue } from "../../../common/bqrs-cli-types";
|
||||
import { jumpToLocation } from "../result-table-utils";
|
||||
|
||||
/**
|
||||
* A clickable location link.
|
||||
*/
|
||||
export function ClickableLocation({
|
||||
loc,
|
||||
label,
|
||||
databaseUri,
|
||||
title,
|
||||
jumpToLocationCallback,
|
||||
}: {
|
||||
loc: ResolvableLocationValue;
|
||||
label: string;
|
||||
databaseUri: string;
|
||||
title?: string;
|
||||
jumpToLocationCallback?: () => void;
|
||||
}): JSX.Element {
|
||||
const jumpToLocationHandler = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
jumpToLocation(loc, databaseUri);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (jumpToLocationCallback) {
|
||||
jumpToLocationCallback();
|
||||
}
|
||||
},
|
||||
[loc, databaseUri, jumpToLocationCallback],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/*
|
||||
eslint-disable-next-line
|
||||
jsx-a11y/anchor-is-valid,
|
||||
*/}
|
||||
<a
|
||||
href="#"
|
||||
className="vscode-codeql__result-table-location-link"
|
||||
title={title}
|
||||
onClick={jumpToLocationHandler}
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
}
|
||||
48
extensions/ql-vscode/src/view/results/locations/Location.tsx
Normal file
48
extensions/ql-vscode/src/view/results/locations/Location.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import * as React from "react";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { UrlValue } from "../../../common/bqrs-cli-types";
|
||||
import {
|
||||
isStringLoc,
|
||||
tryGetResolvableLocation,
|
||||
} from "../../../common/bqrs-utils";
|
||||
import { convertNonPrintableChars } from "../../../common/text-utils";
|
||||
import { NonClickableLocation } from "./NonClickableLocation";
|
||||
import { ClickableLocation } from "./ClickableLocation";
|
||||
|
||||
/**
|
||||
* A location link. Will be clickable if a location URL and database URI are provided.
|
||||
*/
|
||||
export function Location({
|
||||
loc,
|
||||
label,
|
||||
databaseUri,
|
||||
title,
|
||||
jumpToLocationCallback,
|
||||
}: {
|
||||
loc?: UrlValue;
|
||||
label?: string;
|
||||
databaseUri?: string;
|
||||
title?: string;
|
||||
jumpToLocationCallback?: () => void;
|
||||
}): JSX.Element {
|
||||
const resolvableLoc = useMemo(() => tryGetResolvableLocation(loc), [loc]);
|
||||
const displayLabel = useMemo(() => convertNonPrintableChars(label!), [label]);
|
||||
if (loc === undefined) {
|
||||
return <NonClickableLocation msg={displayLabel} />;
|
||||
} else if (isStringLoc(loc)) {
|
||||
return <a href={loc}>{loc}</a>;
|
||||
} else if (databaseUri === undefined || resolvableLoc === undefined) {
|
||||
return <NonClickableLocation msg={displayLabel} locationHint={title} />;
|
||||
} else {
|
||||
return (
|
||||
<ClickableLocation
|
||||
loc={resolvableLoc}
|
||||
label={displayLabel}
|
||||
databaseUri={databaseUri}
|
||||
title={title}
|
||||
jumpToLocationCallback={jumpToLocationCallback}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import * as React from "react";
|
||||
|
||||
/**
|
||||
* A non-clickable location for when there isn't a valid link.
|
||||
* Designed to fit in with the other types of location components.
|
||||
*/
|
||||
export function NonClickableLocation({
|
||||
msg,
|
||||
locationHint,
|
||||
}: {
|
||||
msg?: string;
|
||||
locationHint?: string;
|
||||
}) {
|
||||
if (msg === undefined) return null;
|
||||
return <span title={locationHint}>{msg}</span>;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import * as React from "react";
|
||||
import * as Sarif from "sarif";
|
||||
import { isLineColumnLoc, isWholeFileLoc } from "../../../common/bqrs-utils";
|
||||
import { parseSarifLocation } from "../../../common/sarif-utils";
|
||||
import { basename } from "path";
|
||||
import { useMemo } from "react";
|
||||
import { NonClickableLocation } from "./NonClickableLocation";
|
||||
import { Location } from "./Location";
|
||||
|
||||
/**
|
||||
* A clickable SARIF location link.
|
||||
*
|
||||
* Custom text can be provided, but otherwise the text will be
|
||||
* a human-readable form of the location itself.
|
||||
*/
|
||||
export function SarifLocation({
|
||||
text,
|
||||
loc,
|
||||
sourceLocationPrefix,
|
||||
databaseUri,
|
||||
jumpToLocationCallback,
|
||||
}: {
|
||||
text?: string;
|
||||
loc?: Sarif.Location;
|
||||
sourceLocationPrefix: string;
|
||||
databaseUri: string;
|
||||
jumpToLocationCallback: () => void;
|
||||
}) {
|
||||
const parsedLoc = useMemo(
|
||||
() => loc && parseSarifLocation(loc, sourceLocationPrefix),
|
||||
[loc, sourceLocationPrefix],
|
||||
);
|
||||
if (parsedLoc === undefined || "hint" in parsedLoc) {
|
||||
return (
|
||||
<NonClickableLocation
|
||||
msg={text || "[no location]"}
|
||||
locationHint={parsedLoc?.hint}
|
||||
/>
|
||||
);
|
||||
} else if (isWholeFileLoc(parsedLoc)) {
|
||||
return (
|
||||
<Location
|
||||
loc={parsedLoc}
|
||||
label={text || `${basename(parsedLoc.userVisibleFile)}`}
|
||||
databaseUri={databaseUri}
|
||||
title={text ? undefined : `${parsedLoc.userVisibleFile}`}
|
||||
jumpToLocationCallback={jumpToLocationCallback}
|
||||
/>
|
||||
);
|
||||
} else if (isLineColumnLoc(parsedLoc)) {
|
||||
return (
|
||||
<Location
|
||||
loc={parsedLoc}
|
||||
label={
|
||||
text ||
|
||||
`${basename(parsedLoc.userVisibleFile)}:${parsedLoc.startLine}:${
|
||||
parsedLoc.startColumn
|
||||
}`
|
||||
}
|
||||
databaseUri={databaseUri}
|
||||
title={text ? undefined : `${parsedLoc.userVisibleFile}`}
|
||||
jumpToLocationCallback={jumpToLocationCallback}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import * as React from "react";
|
||||
import * as Sarif from "sarif";
|
||||
import { parseSarifPlainTextMessage } from "../../../common/sarif-utils";
|
||||
import { SarifLocation } from "./SarifLocation";
|
||||
|
||||
/**
|
||||
* Parses a SARIF message and populates clickable locations.
|
||||
*/
|
||||
export function SarifMessageWithLocations({
|
||||
msg,
|
||||
relatedLocations,
|
||||
sourceLocationPrefix,
|
||||
databaseUri,
|
||||
jumpToLocationCallback,
|
||||
}: {
|
||||
msg: string;
|
||||
relatedLocations: Sarif.Location[];
|
||||
sourceLocationPrefix: string;
|
||||
databaseUri: string;
|
||||
jumpToLocationCallback: () => void;
|
||||
}) {
|
||||
const relatedLocationsById: Map<number, Sarif.Location> = new Map();
|
||||
for (const loc of relatedLocations) {
|
||||
if (loc.id !== undefined) {
|
||||
relatedLocationsById.set(loc.id, loc);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{parseSarifPlainTextMessage(msg).map((part, i) => {
|
||||
if (typeof part === "string") {
|
||||
return <span key={i}>{part}</span>;
|
||||
} else {
|
||||
return (
|
||||
<SarifLocation
|
||||
key={i}
|
||||
text={part.text}
|
||||
loc={relatedLocationsById.get(part.dest)}
|
||||
sourceLocationPrefix={sourceLocationPrefix}
|
||||
databaseUri={databaseUri}
|
||||
jumpToLocationCallback={jumpToLocationCallback}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user