Have each component in a separate file

This commit is contained in:
Robert
2023-07-17 16:51:23 +01:00
parent a4c0365a95
commit c4ebee8e8d
8 changed files with 234 additions and 215 deletions

View File

@@ -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 {

View File

@@ -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>;

View File

@@ -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}
/>
);
}
})}
</>
);
}

View File

@@ -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>
</>
);
}

View 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}
/>
);
}
}

View File

@@ -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>;
}

View File

@@ -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;
}
}

View File

@@ -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}
/>
);
}
})}
</>
);
}