Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4546616871 | ||
|
|
b201c45e2d | ||
|
|
3364af5305 | ||
|
|
d6af6e7c4c | ||
|
|
f9f454509b | ||
|
|
9f0453a80a | ||
|
|
af2a592e3d | ||
|
|
a4fcbd093c | ||
|
|
f02c007bdb | ||
|
|
66874823aa | ||
|
|
1a59467707 | ||
|
|
78383e376c | ||
|
|
32cf05cb4b | ||
|
|
1ec47a4eef | ||
|
|
4d2f84d599 | ||
|
|
a858b39f26 | ||
|
|
5d71ce41a8 | ||
|
|
76354c4518 | ||
|
|
4e310ce652 | ||
|
|
68bfa00707 | ||
|
|
51ef674cc5 | ||
|
|
d2612e2dca | ||
|
|
1db3dda533 | ||
|
|
9c3b0b24c9 | ||
|
|
df06c75070 | ||
|
|
b3c44ea317 | ||
|
|
d6673c4ede | ||
|
|
d68e270e90 | ||
|
|
97b9c43ae1 | ||
|
|
7e00e9cd2f | ||
|
|
459d606751 | ||
|
|
a069ae45fb | ||
|
|
a6225c5edf | ||
|
|
64d4439815 | ||
|
|
b11a9e2c88 | ||
|
|
75ab23bbd8 | ||
|
|
5bab2421ea | ||
|
|
d5edbca4e4 | ||
|
|
1ee349d2ef | ||
|
|
79f89c9e4b | ||
|
|
2fbdefe4f0 | ||
|
|
01403aeee7 | ||
|
|
9f7f34a87c | ||
|
|
987b92ab1e | ||
|
|
6fd2579c9c | ||
|
|
c9e1a64d3a | ||
|
|
03f330da52 | ||
|
|
4f6d72a9ce | ||
|
|
b5d3a612d8 | ||
|
|
48209e7732 | ||
|
|
9934449ae9 | ||
|
|
fdc4d97f93 | ||
|
|
b1ca9418b4 | ||
|
|
7d3a350a28 | ||
|
|
ca4c511227 | ||
|
|
8b4f2d2009 |
4
.github/workflows/e2e-tests.yml
vendored
4
.github/workflows/e2e-tests.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
|
||||
- name: Start containers
|
||||
working-directory: extensions/ql-vscode/test/e2e
|
||||
run: docker-compose -f "docker-compose.yml" up -d --build
|
||||
run: docker compose -f "docker-compose.yml" up -d --build
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
working-directory: extensions/ql-vscode
|
||||
@@ -43,4 +43,4 @@ jobs:
|
||||
- name: Stop containers
|
||||
working-directory: extensions/ql-vscode/test/e2e
|
||||
if: always()
|
||||
run: docker-compose -f "docker-compose.yml" down -v
|
||||
run: docker compose -f "docker-compose.yml" down -v
|
||||
|
||||
@@ -1 +1 @@
|
||||
v20.9.0
|
||||
v20.14.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
import { addons } from "@storybook/manager-api";
|
||||
import { Addon_TypesEnum } from "@storybook/types";
|
||||
import { Addon_TypesEnum } from "storybook/internal/types";
|
||||
import { ThemeSelector } from "./ThemeSelector";
|
||||
|
||||
const ADDON_ID = "vscode-theme-addon";
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
# CodeQL for Visual Studio Code: Changelog
|
||||
|
||||
## [UNRELEASED]
|
||||
## 1.14.0 - 7 August 2024
|
||||
|
||||
- Add Python support to the CodeQL Model Editor. [#3676](https://github.com/github/vscode-codeql/pull/3676)
|
||||
- Update variant analysis view to display the length of the shortest path for path queries. [#3671](https://github.com/github/vscode-codeql/pull/3671)
|
||||
- Remove support for CodeQL CLI versions older than 2.15.5. [#3681](https://github.com/github/vscode-codeql/pull/3681)
|
||||
|
||||
## 1.13.1 - 29 May 2024
|
||||
|
||||
|
||||
@@ -77,5 +77,8 @@ export function copyWasmFiles() {
|
||||
// to configure the path to the WASM file. So, source-map will always load the file from `__dirname/mappings.wasm`.
|
||||
// In version 0.8.0, it may be possible to do this properly by calling SourceMapConsumer.initialize by
|
||||
// using the "browser" field in source-map's package.json to load the WASM file from a given file path.
|
||||
return src("node_modules/source-map/lib/mappings.wasm").pipe(dest("out"));
|
||||
return src("node_modules/source-map/lib/mappings.wasm", {
|
||||
// WASM is a binary format, so don't try to re-encode it as text.
|
||||
encoding: false,
|
||||
}).pipe(dest("out"));
|
||||
}
|
||||
|
||||
6593
extensions/ql-vscode/package-lock.json
generated
6593
extensions/ql-vscode/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
||||
"description": "CodeQL for Visual Studio Code",
|
||||
"author": "GitHub",
|
||||
"private": true,
|
||||
"version": "1.13.2",
|
||||
"version": "1.14.0",
|
||||
"publisher": "GitHub",
|
||||
"license": "MIT",
|
||||
"icon": "media/VS-marketplace-CodeQL-icon.png",
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^1.82.0",
|
||||
"node": "^20.9.0",
|
||||
"node": "^20.14.0",
|
||||
"npm": ">=7.20.6"
|
||||
},
|
||||
"categories": [
|
||||
@@ -1790,8 +1790,7 @@
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.trimCache",
|
||||
"when": "codeql.supportsTrimCache"
|
||||
"command": "codeQL.trimCache"
|
||||
}
|
||||
],
|
||||
"editor/context": [
|
||||
@@ -1972,7 +1971,7 @@
|
||||
"@octokit/plugin-retry": "^6.0.1",
|
||||
"@octokit/plugin-throttling": "^8.0.0",
|
||||
"@octokit/rest": "^20.0.2",
|
||||
"@vscode/codicons": "^0.0.35",
|
||||
"@vscode/codicons": "^0.0.36",
|
||||
"@vscode/debugadapter": "^1.59.0",
|
||||
"@vscode/debugprotocol": "^1.65.0",
|
||||
"@vscode/webview-ui-toolkit": "^1.0.1",
|
||||
@@ -1998,35 +1997,35 @@
|
||||
"tmp-promise": "^3.0.2",
|
||||
"tree-kill": "^1.2.2",
|
||||
"vscode-extension-telemetry": "^0.1.6",
|
||||
"vscode-jsonrpc": "^8.0.2",
|
||||
"vscode-jsonrpc": "^8.2.1",
|
||||
"vscode-languageclient": "^8.0.2",
|
||||
"yauzl": "^2.10.0",
|
||||
"zip-a-folder": "^3.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.6",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.18.6",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.24.7",
|
||||
"@babel/preset-env": "^7.24.4",
|
||||
"@babel/preset-react": "^7.24.1",
|
||||
"@babel/preset-typescript": "^7.21.4",
|
||||
"@faker-js/faker": "^8.4.1",
|
||||
"@github/markdownlint-github": "^0.6.2",
|
||||
"@playwright/test": "^1.40.1",
|
||||
"@storybook/addon-a11y": "^8.1.10",
|
||||
"@storybook/addon-actions": "^8.1.10",
|
||||
"@storybook/addon-essentials": "^8.1.10",
|
||||
"@storybook/addon-interactions": "^8.1.10",
|
||||
"@storybook/addon-links": "^8.1.10",
|
||||
"@storybook/addon-a11y": "^8.2.7",
|
||||
"@storybook/addon-actions": "^8.2.7",
|
||||
"@storybook/addon-essentials": "^8.2.7",
|
||||
"@storybook/addon-interactions": "^8.2.7",
|
||||
"@storybook/addon-links": "^8.2.7",
|
||||
"@storybook/blocks": "^8.0.2",
|
||||
"@storybook/components": "^8.0.2",
|
||||
"@storybook/csf": "^0.1.8",
|
||||
"@storybook/icons": "^1.2.9",
|
||||
"@storybook/manager-api": "^8.1.10",
|
||||
"@storybook/react": "^8.1.10",
|
||||
"@storybook/react-vite": "^8.1.10",
|
||||
"@storybook/theming": "^8.1.10",
|
||||
"@testing-library/dom": "^10.1.0",
|
||||
"@testing-library/jest-dom": "^6.4.6",
|
||||
"@storybook/components": "^8.2.7",
|
||||
"@storybook/csf": "^0.1.11",
|
||||
"@storybook/icons": "^1.2.10",
|
||||
"@storybook/manager-api": "^8.2.7",
|
||||
"@storybook/react": "^8.2.7",
|
||||
"@storybook/react-vite": "^8.2.7",
|
||||
"@storybook/theming": "^8.2.4",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.4.8",
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/child-process-promise": "^2.2.1",
|
||||
@@ -2039,7 +2038,7 @@
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/js-yaml": "^4.0.6",
|
||||
"@types/nanoid": "^3.0.0",
|
||||
"@types/node": "20.9.*",
|
||||
"@types/node": "20.14.*",
|
||||
"@types/node-fetch": "^2.5.2",
|
||||
"@types/react": "^18.3.1",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
@@ -2071,10 +2070,10 @@
|
||||
"eslint-plugin-jest-dom": "^5.2.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-react": "^7.34.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-storybook": "^0.8.0",
|
||||
"glob": "^10.0.0",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp": "^5.0.0",
|
||||
"gulp-esbuild": "^0.12.0",
|
||||
"gulp-replace": "^1.1.3",
|
||||
"gulp-typescript": "^5.0.1",
|
||||
@@ -2088,7 +2087,7 @@
|
||||
"npm-run-all": "^4.1.5",
|
||||
"patch-package": "^8.0.0",
|
||||
"prettier": "^3.2.5",
|
||||
"storybook": "^8.1.10",
|
||||
"storybook": "^8.2.7",
|
||||
"tar-stream": "^3.1.7",
|
||||
"through2": "^4.0.2",
|
||||
"ts-jest": "^29.1.4",
|
||||
|
||||
@@ -12,6 +12,7 @@ interface VersionResult {
|
||||
export interface CliFeatures {
|
||||
featuresInVersionResult?: boolean;
|
||||
mrvaPackCreate?: boolean;
|
||||
generateSummarySymbolMap?: boolean;
|
||||
}
|
||||
|
||||
export interface VersionAndFeatures {
|
||||
|
||||
@@ -1211,10 +1211,15 @@ export class CodeQLCliServer implements Disposable {
|
||||
outputPath: string,
|
||||
endSummaryPath: string,
|
||||
): Promise<string> {
|
||||
const supportsGenerateSummarySymbolMap =
|
||||
await this.cliConstraints.supportsGenerateSummarySymbolMap();
|
||||
const subcommandArgs = [
|
||||
"--format=text",
|
||||
`--end-summary=${endSummaryPath}`,
|
||||
"--sourcemap",
|
||||
...(supportsGenerateSummarySymbolMap
|
||||
? ["--summary-symbol-map", "--minify-output"]
|
||||
: []),
|
||||
inputPath,
|
||||
outputPath,
|
||||
];
|
||||
@@ -1750,14 +1755,6 @@ export class CodeQLCliServer implements Disposable {
|
||||
this._versionChangedListeners.forEach((listener) =>
|
||||
listener(newVersionAndFeatures),
|
||||
);
|
||||
// this._version is only undefined upon config change, so we reset CLI-based context key only when necessary.
|
||||
await this.app.commands.execute(
|
||||
"setContext",
|
||||
"codeql.supportsTrimCache",
|
||||
newVersionAndFeatures.version.compare(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_TRIM_CACHE,
|
||||
) >= 0,
|
||||
);
|
||||
} catch (e) {
|
||||
this._versionChangedListeners.forEach((listener) =>
|
||||
listener(undefined),
|
||||
@@ -1912,13 +1909,7 @@ function shouldDebugCliServer() {
|
||||
export class CliVersionConstraint {
|
||||
// The oldest version of the CLI that we support. This is used to determine
|
||||
// whether to show a warning about the CLI being too old on startup.
|
||||
public static OLDEST_SUPPORTED_CLI_VERSION = new SemVer("2.14.6");
|
||||
|
||||
/**
|
||||
* CLI version where the query server supports the `evaluation/trimCache` method
|
||||
* with `codeql database cleanup --mode=trim` semantics.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_TRIM_CACHE = new SemVer("2.15.1");
|
||||
public static OLDEST_SUPPORTED_CLI_VERSION = new SemVer("v2.15.5");
|
||||
|
||||
public static CLI_VERSION_WITHOUT_MRVA_EXTENSIBLE_PREDICATE_HACK = new SemVer(
|
||||
"2.16.1",
|
||||
@@ -1953,4 +1944,8 @@ export class CliVersionConstraint {
|
||||
async supportsMrvaPackCreate(): Promise<boolean> {
|
||||
return (await this.cli.getFeatures()).mrvaPackCreate === true;
|
||||
}
|
||||
|
||||
async supportsGenerateSummarySymbolMap(): Promise<boolean> {
|
||||
return (await this.cli.getFeatures()).generateSummarySymbolMap === true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +147,21 @@ interface SetStateMsg {
|
||||
parsedResultSets: ParsedResultSets;
|
||||
}
|
||||
|
||||
export interface UserSettings {
|
||||
/** Whether to display links to the dataflow models that generated particular nodes in a flow path. */
|
||||
shouldShowProvenance: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_USER_SETTINGS: UserSettings = {
|
||||
shouldShowProvenance: false,
|
||||
};
|
||||
|
||||
/** Message indicating that the user's configuration settings have changed. */
|
||||
interface SetUserSettingsMsg {
|
||||
t: "setUserSettings";
|
||||
userSettings: UserSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Message indicating that the results view should display interpreted
|
||||
* results.
|
||||
@@ -191,6 +206,7 @@ interface UntoggleShowProblemsMsg {
|
||||
export type IntoResultsViewMsg =
|
||||
| ResultsUpdatingMsg
|
||||
| SetStateMsg
|
||||
| SetUserSettingsMsg
|
||||
| ShowInterpretedPageMsg
|
||||
| NavigateMsg
|
||||
| UntoggleShowProblemsMsg;
|
||||
@@ -208,13 +224,15 @@ export type FromResultsViewMsg =
|
||||
| OpenFileMsg;
|
||||
|
||||
/**
|
||||
* Message from the results view to open a database source
|
||||
* Message from the results view to open a source
|
||||
* file at the provided location.
|
||||
*/
|
||||
interface ViewSourceFileMsg {
|
||||
t: "viewSourceFile";
|
||||
loc: UrlValueResolvable;
|
||||
databaseUri: string;
|
||||
/** URI of the database whose source archive contains the file, or `undefined` to open a file from
|
||||
* the local disk. The latter case is used for opening links to data extension model files. */
|
||||
databaseUri: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -341,7 +359,8 @@ interface ChangeCompareMessage {
|
||||
|
||||
export type ToCompareViewMessage =
|
||||
| SetComparisonQueryInfoMessage
|
||||
| SetComparisonsMessage;
|
||||
| SetComparisonsMessage
|
||||
| SetUserSettingsMsg;
|
||||
|
||||
/**
|
||||
* Message to the compare view that sets the metadata of the compared queries.
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import type { Log, Tool } from "sarif";
|
||||
import type { Log } from "sarif";
|
||||
import { createReadStream } from "fs-extra";
|
||||
import { connectTo } from "stream-json/Assembler";
|
||||
import { getErrorMessage } from "./helpers-pure";
|
||||
import { withParser } from "stream-json/filters/Pick";
|
||||
|
||||
const DUMMY_TOOL: Tool = { driver: { name: "" } };
|
||||
import { withParser } from "stream-json/filters/Ignore";
|
||||
|
||||
export async function sarifParser(
|
||||
interpretedResultsPath: string,
|
||||
): Promise<Log> {
|
||||
try {
|
||||
// Parse the SARIF file into token streams, filtering out only the results array.
|
||||
// Parse the SARIF file into token streams, filtering out some of the larger subtrees that we
|
||||
// don't need.
|
||||
const pipeline = createReadStream(interpretedResultsPath).pipe(
|
||||
withParser({ filter: "runs.0.results" }),
|
||||
withParser({
|
||||
// We don't need to run's `artifacts` property, nor the driver's `notifications` property.
|
||||
filter: /^runs\.\d+\.(artifacts|tool\.driver\.notifications)/,
|
||||
}),
|
||||
);
|
||||
|
||||
// Creates JavaScript objects from the token stream
|
||||
@@ -38,15 +40,17 @@ export async function sarifParser(
|
||||
});
|
||||
|
||||
asm.on("done", (asm) => {
|
||||
const log: Log = {
|
||||
version: "2.1.0",
|
||||
runs: [
|
||||
{
|
||||
tool: DUMMY_TOOL,
|
||||
results: asm.current ?? [],
|
||||
},
|
||||
],
|
||||
};
|
||||
const log = asm.current;
|
||||
|
||||
// Do some trivial validation. This isn't a full validation of the SARIF file, but it's at
|
||||
// least enough to ensure that we're not trying to parse complete garbage later.
|
||||
if (log.runs === undefined || log.runs.length < 1) {
|
||||
reject(
|
||||
new Error(
|
||||
"Invalid SARIF file: expecting at least one run with result.",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
resolve(log);
|
||||
alreadyDone = true;
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
getResultSetNames,
|
||||
} from "./result-set-names";
|
||||
import { compareInterpretedResults } from "./interpreted-results";
|
||||
import { isCanary } from "../config";
|
||||
|
||||
interface ComparePair {
|
||||
from: CompletedLocalQueryInfo;
|
||||
@@ -116,6 +117,13 @@ export class CompareView extends AbstractWebview<
|
||||
panel.reveal(undefined, true);
|
||||
await this.waitForPanelLoaded();
|
||||
|
||||
await this.postMessage({
|
||||
t: "setUserSettings",
|
||||
userSettings: {
|
||||
shouldShowProvenance: isCanary(),
|
||||
},
|
||||
});
|
||||
|
||||
await this.postMessage({
|
||||
t: "setComparisonQueryInfo",
|
||||
stats: {
|
||||
|
||||
@@ -840,7 +840,6 @@ const LLM_GENERATION_DEV_ENDPOINT = new Setting(
|
||||
const MODEL_EVALUATION = new Setting("evaluation", MODEL_SETTING);
|
||||
const MODEL_PACK_LOCATION = new Setting("packLocation", MODEL_SETTING);
|
||||
const MODEL_PACK_NAME = new Setting("packName", MODEL_SETTING);
|
||||
const ENABLE_PYTHON = new Setting("enablePython", MODEL_SETTING);
|
||||
|
||||
export type ModelConfigPackVariables = {
|
||||
database: string;
|
||||
@@ -857,7 +856,6 @@ export interface ModelConfig {
|
||||
variables: ModelConfigPackVariables,
|
||||
): string;
|
||||
getPackName(languageId: string, variables: ModelConfigPackVariables): string;
|
||||
enablePython: boolean;
|
||||
}
|
||||
|
||||
export class ModelConfigListener extends ConfigListener implements ModelConfig {
|
||||
@@ -919,10 +917,6 @@ export class ModelConfigListener extends ConfigListener implements ModelConfig {
|
||||
variables,
|
||||
);
|
||||
}
|
||||
|
||||
public get enablePython(): boolean {
|
||||
return !!ENABLE_PYTHON.getValue<boolean>();
|
||||
}
|
||||
}
|
||||
|
||||
const GITHUB_DATABASE_SETTING = new Setting("githubDatabase", ROOT_SETTING);
|
||||
|
||||
@@ -37,11 +37,12 @@ export const shownLocationLineDecoration =
|
||||
/**
|
||||
* Resolves the specified CodeQL location to a URI into the source archive.
|
||||
* @param loc CodeQL location to resolve. Must have a non-empty value for `loc.file`.
|
||||
* @param databaseItem Database in which to resolve the file location.
|
||||
* @param databaseItem Database in which to resolve the file location, or `undefined` to resolve
|
||||
* from the local file system.
|
||||
*/
|
||||
function resolveFivePartLocation(
|
||||
loc: UrlValueLineColumnLocation,
|
||||
databaseItem: DatabaseItem,
|
||||
databaseItem: DatabaseItem | undefined,
|
||||
): Location {
|
||||
// `Range` is a half-open interval, and is zero-based. CodeQL locations are closed intervals, and
|
||||
// are one-based. Adjust accordingly.
|
||||
@@ -52,7 +53,10 @@ function resolveFivePartLocation(
|
||||
Math.max(1, loc.endColumn),
|
||||
);
|
||||
|
||||
return new Location(databaseItem.resolveSourceFile(loc.uri), range);
|
||||
return new Location(
|
||||
databaseItem?.resolveSourceFile(loc.uri) ?? Uri.parse(loc.uri),
|
||||
range,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,22 +66,26 @@ function resolveFivePartLocation(
|
||||
*/
|
||||
function resolveWholeFileLocation(
|
||||
loc: UrlValueWholeFileLocation,
|
||||
databaseItem: DatabaseItem,
|
||||
databaseItem: DatabaseItem | undefined,
|
||||
): Location {
|
||||
// A location corresponding to the start of the file.
|
||||
const range = new Range(0, 0, 0, 0);
|
||||
return new Location(databaseItem.resolveSourceFile(loc.uri), range);
|
||||
return new Location(
|
||||
databaseItem?.resolveSourceFile(loc.uri) ?? Uri.parse(loc.uri),
|
||||
range,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to resolve the specified CodeQL location to a URI into the source archive. If no exact location
|
||||
* can be resolved, returns `undefined`.
|
||||
* @param loc CodeQL location to resolve
|
||||
* @param databaseItem Database in which to resolve the file location.
|
||||
* @param databaseItem Database in which to resolve the file location, or `undefined` to resolve
|
||||
* from the local file system.
|
||||
*/
|
||||
export function tryResolveLocation(
|
||||
loc: UrlValueResolvable | undefined,
|
||||
databaseItem: DatabaseItem,
|
||||
databaseItem: DatabaseItem | undefined,
|
||||
): Location | undefined {
|
||||
if (!loc) {
|
||||
return;
|
||||
@@ -95,7 +103,7 @@ export function tryResolveLocation(
|
||||
|
||||
export async function showResolvableLocation(
|
||||
loc: UrlValueResolvable,
|
||||
databaseItem: DatabaseItem,
|
||||
databaseItem: DatabaseItem | undefined,
|
||||
logger: Logger,
|
||||
): Promise<void> {
|
||||
try {
|
||||
@@ -151,13 +159,14 @@ export async function showLocation(location?: Location) {
|
||||
}
|
||||
|
||||
export async function jumpToLocation(
|
||||
databaseUri: string,
|
||||
databaseUri: string | undefined,
|
||||
loc: UrlValueResolvable,
|
||||
databaseManager: DatabaseManager,
|
||||
logger: Logger,
|
||||
) {
|
||||
const databaseItem = databaseManager.findDatabaseItem(Uri.parse(databaseUri));
|
||||
if (databaseItem !== undefined) {
|
||||
await showResolvableLocation(loc, databaseItem, logger);
|
||||
}
|
||||
const databaseItem =
|
||||
databaseUri !== undefined
|
||||
? databaseManager.findDatabaseItem(Uri.parse(databaseUri))
|
||||
: undefined;
|
||||
await showResolvableLocation(loc, databaseItem, logger);
|
||||
}
|
||||
|
||||
@@ -537,6 +537,14 @@ export class ResultsView extends AbstractWebview<
|
||||
resultSetNames,
|
||||
};
|
||||
|
||||
await this.postMessage({
|
||||
t: "setUserSettings",
|
||||
userSettings: {
|
||||
// Only show provenance info in canary mode for now.
|
||||
shouldShowProvenance: isCanary(),
|
||||
},
|
||||
});
|
||||
|
||||
await this.postMessage({
|
||||
t: "setState",
|
||||
interpretation: interpretationPage,
|
||||
|
||||
@@ -33,6 +33,7 @@ export function decodeBqrsToMethods(
|
||||
let libraryVersion: string | undefined;
|
||||
let type: ModeledMethodType;
|
||||
let classification: CallClassification;
|
||||
let endpointKindColumn: string | BqrsEntityValue | undefined;
|
||||
let endpointType: EndpointType | undefined = undefined;
|
||||
|
||||
if (mode === Mode.Application) {
|
||||
@@ -47,6 +48,7 @@ export function decodeBqrsToMethods(
|
||||
libraryVersion,
|
||||
type,
|
||||
classification,
|
||||
endpointKindColumn,
|
||||
] = tuple as ApplicationModeTuple;
|
||||
} else {
|
||||
[
|
||||
@@ -58,6 +60,7 @@ export function decodeBqrsToMethods(
|
||||
supported,
|
||||
library,
|
||||
type,
|
||||
endpointKindColumn,
|
||||
] = tuple as FrameworkModeTuple;
|
||||
|
||||
classification = CallClassification.Unknown;
|
||||
@@ -68,13 +71,18 @@ export function decodeBqrsToMethods(
|
||||
}
|
||||
|
||||
if (definition.endpointTypeForEndpoint) {
|
||||
endpointType = definition.endpointTypeForEndpoint({
|
||||
endpointType,
|
||||
packageName,
|
||||
typeName,
|
||||
methodName,
|
||||
methodParameters,
|
||||
});
|
||||
endpointType = definition.endpointTypeForEndpoint(
|
||||
{
|
||||
endpointType,
|
||||
packageName,
|
||||
typeName,
|
||||
methodName,
|
||||
methodParameters,
|
||||
},
|
||||
typeof endpointKindColumn === "object"
|
||||
? endpointKindColumn.label
|
||||
: endpointKindColumn,
|
||||
);
|
||||
}
|
||||
|
||||
if (endpointType === undefined) {
|
||||
|
||||
@@ -174,11 +174,14 @@ export type ModelsAsDataLanguage = {
|
||||
* be determined by heuristics.
|
||||
* @param method The method to get the endpoint type for. The endpoint type can be undefined if the
|
||||
* query does not return an endpoint type.
|
||||
* @param endpointKind An optional column that may be provided by the query to help determine the
|
||||
* endpoint type.
|
||||
*/
|
||||
endpointTypeForEndpoint?: (
|
||||
method: Omit<MethodDefinition, "endpointType"> & {
|
||||
endpointType: EndpointType | undefined;
|
||||
},
|
||||
endpointKind: string | undefined,
|
||||
) => EndpointType | undefined;
|
||||
predicates: ModelsAsDataLanguagePredicates;
|
||||
modelGeneration?: ModelsAsDataLanguageModelGeneration;
|
||||
|
||||
@@ -4,7 +4,26 @@ import { EndpointType } from "../../method";
|
||||
|
||||
const memberTokenRegex = /^Member\[(.+)]$/;
|
||||
|
||||
export function parsePythonAccessPath(path: string): {
|
||||
// In Python, the type can contain both the package name and the type name.
|
||||
export function parsePythonType(type: string) {
|
||||
// The first part is always the package name. All remaining parts are the type
|
||||
// name.
|
||||
|
||||
const parts = type.split(".");
|
||||
const packageName = parts.shift() ?? "";
|
||||
|
||||
return {
|
||||
packageName,
|
||||
typeName: parts.join("."),
|
||||
};
|
||||
}
|
||||
|
||||
// The type name can also be specified in the type, so this will combine
|
||||
// the already parsed type name and the type name from the access path.
|
||||
export function parsePythonAccessPath(
|
||||
path: string,
|
||||
shortTypeName: string,
|
||||
): {
|
||||
typeName: string;
|
||||
methodName: string;
|
||||
endpointType: EndpointType;
|
||||
@@ -13,8 +32,12 @@ export function parsePythonAccessPath(path: string): {
|
||||
const tokens = parseAccessPathTokens(path);
|
||||
|
||||
if (tokens.length === 0) {
|
||||
const typeName = shortTypeName.endsWith("!")
|
||||
? shortTypeName.slice(0, -1)
|
||||
: shortTypeName;
|
||||
|
||||
return {
|
||||
typeName: "",
|
||||
typeName,
|
||||
methodName: "",
|
||||
endpointType: EndpointType.Method,
|
||||
path: "",
|
||||
@@ -23,6 +46,10 @@ export function parsePythonAccessPath(path: string): {
|
||||
|
||||
const typeParts = [];
|
||||
let endpointType = EndpointType.Function;
|
||||
// If a short type name was given and it doesn't end in a `!`, then this refers to a method.
|
||||
if (shortTypeName !== "" && !shortTypeName.endsWith("!")) {
|
||||
endpointType = EndpointType.Method;
|
||||
}
|
||||
|
||||
let remainingTokens: typeof tokens = [];
|
||||
|
||||
@@ -32,6 +59,7 @@ export function parsePythonAccessPath(path: string): {
|
||||
if (memberMatch) {
|
||||
typeParts.push(memberMatch[1]);
|
||||
} else if (token.text === "Instance") {
|
||||
// Alternative way of specifying that this refers to a method.
|
||||
endpointType = EndpointType.Method;
|
||||
} else {
|
||||
remainingTokens = tokens.slice(i);
|
||||
@@ -40,9 +68,22 @@ export function parsePythonAccessPath(path: string): {
|
||||
}
|
||||
|
||||
const methodName = typeParts.pop() ?? "";
|
||||
const typeName = typeParts.join(".");
|
||||
let typeName = typeParts.join(".");
|
||||
const remainingPath = remainingTokens.map((token) => token.text).join(".");
|
||||
|
||||
if (shortTypeName !== "") {
|
||||
if (shortTypeName.endsWith("!")) {
|
||||
// The actual type name is the name without the `!`.
|
||||
shortTypeName = shortTypeName.slice(0, -1);
|
||||
}
|
||||
|
||||
if (typeName !== "") {
|
||||
typeName = `${shortTypeName}.${typeName}`;
|
||||
} else {
|
||||
typeName = shortTypeName;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
methodName,
|
||||
typeName,
|
||||
@@ -51,53 +92,59 @@ export function parsePythonAccessPath(path: string): {
|
||||
};
|
||||
}
|
||||
|
||||
export function parsePythonTypeAndPath(
|
||||
type: string,
|
||||
path: string,
|
||||
): {
|
||||
packageName: string;
|
||||
typeName: string;
|
||||
methodName: string;
|
||||
endpointType: EndpointType;
|
||||
path: string;
|
||||
} {
|
||||
const { packageName, typeName: shortTypeName } = parsePythonType(type);
|
||||
const {
|
||||
typeName,
|
||||
methodName,
|
||||
endpointType,
|
||||
path: remainingPath,
|
||||
} = parsePythonAccessPath(path, shortTypeName);
|
||||
|
||||
return {
|
||||
packageName,
|
||||
typeName,
|
||||
methodName,
|
||||
endpointType,
|
||||
path: remainingPath,
|
||||
};
|
||||
}
|
||||
|
||||
export function pythonMethodSignature(typeName: string, methodName: string) {
|
||||
return `${typeName}#${methodName}`;
|
||||
}
|
||||
|
||||
function pythonTypePath(typeName: string) {
|
||||
if (typeName === "") {
|
||||
export function pythonType(
|
||||
packageName: string,
|
||||
typeName: string,
|
||||
endpointType: EndpointType,
|
||||
) {
|
||||
if (typeName !== "" && packageName !== "") {
|
||||
return `${packageName}.${typeName}${endpointType === EndpointType.Function ? "!" : ""}`;
|
||||
}
|
||||
|
||||
return `${packageName}${typeName}`;
|
||||
}
|
||||
|
||||
export function pythonMethodPath(methodName: string) {
|
||||
if (methodName === "") {
|
||||
return "";
|
||||
}
|
||||
|
||||
return typeName
|
||||
.split(".")
|
||||
.map((part) => `Member[${part}]`)
|
||||
.join(".");
|
||||
return `Member[${methodName}]`;
|
||||
}
|
||||
|
||||
export function pythonMethodPath(
|
||||
typeName: string,
|
||||
methodName: string,
|
||||
endpointType: EndpointType,
|
||||
) {
|
||||
if (methodName === "") {
|
||||
return pythonTypePath(typeName);
|
||||
}
|
||||
|
||||
const typePath = pythonTypePath(typeName);
|
||||
|
||||
let result = typePath;
|
||||
if (typePath !== "" && endpointType === EndpointType.Method) {
|
||||
result += ".Instance";
|
||||
}
|
||||
|
||||
if (result !== "") {
|
||||
result += ".";
|
||||
}
|
||||
|
||||
result += `Member[${methodName}]`;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function pythonPath(
|
||||
typeName: string,
|
||||
methodName: string,
|
||||
endpointType: EndpointType,
|
||||
path: string,
|
||||
) {
|
||||
const methodPath = pythonMethodPath(typeName, methodName, endpointType);
|
||||
export function pythonPath(methodName: string, path: string) {
|
||||
const methodPath = pythonMethodPath(methodName);
|
||||
if (methodPath === "") {
|
||||
return path;
|
||||
}
|
||||
@@ -111,7 +158,24 @@ export function pythonPath(
|
||||
|
||||
export function pythonEndpointType(
|
||||
method: Omit<MethodDefinition, "endpointType">,
|
||||
endpointKind: string | undefined,
|
||||
): EndpointType {
|
||||
switch (endpointKind) {
|
||||
case "Function":
|
||||
return EndpointType.Function;
|
||||
case "InstanceMethod":
|
||||
return EndpointType.Method;
|
||||
case "ClassMethod":
|
||||
return EndpointType.ClassMethod;
|
||||
case "StaticMethod":
|
||||
return EndpointType.StaticMethod;
|
||||
case "InitMethod":
|
||||
return EndpointType.Constructor;
|
||||
case "Class":
|
||||
return EndpointType.Class;
|
||||
}
|
||||
|
||||
// Legacy behavior for when the kind column is missing.
|
||||
if (
|
||||
method.methodParameters.startsWith("(self,") ||
|
||||
method.methodParameters === "(self)"
|
||||
@@ -120,3 +184,12 @@ export function pythonEndpointType(
|
||||
}
|
||||
return EndpointType.Function;
|
||||
}
|
||||
|
||||
export function hasPythonSelfArgument(endpointType: EndpointType): boolean {
|
||||
// Instance methods and class methods both use `Argument[self]` for the first parameter. The first
|
||||
// parameter after self is called `Argument[0]`.
|
||||
return (
|
||||
endpointType === EndpointType.Method ||
|
||||
endpointType === EndpointType.ClassMethod
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,44 +4,48 @@ import { Mode } from "../../shared/mode";
|
||||
import type { MethodArgument } from "../../method";
|
||||
import { EndpointType, getArgumentsList } from "../../method";
|
||||
import {
|
||||
parsePythonAccessPath,
|
||||
hasPythonSelfArgument,
|
||||
parsePythonTypeAndPath,
|
||||
pythonEndpointType,
|
||||
pythonMethodPath,
|
||||
pythonMethodSignature,
|
||||
pythonPath,
|
||||
pythonType,
|
||||
} from "./access-paths";
|
||||
|
||||
export const python: ModelsAsDataLanguage = {
|
||||
availableModes: [Mode.Framework],
|
||||
createMethodSignature: ({ typeName, methodName }) =>
|
||||
`${typeName}#${methodName}`,
|
||||
endpointTypeForEndpoint: (method) => pythonEndpointType(method),
|
||||
endpointTypeForEndpoint: (method, endpointKind) =>
|
||||
pythonEndpointType(method, endpointKind),
|
||||
predicates: {
|
||||
source: {
|
||||
extensiblePredicate: sharedExtensiblePredicates.source,
|
||||
supportedKinds: sharedKinds.source,
|
||||
supportedEndpointTypes: [EndpointType.Method, EndpointType.Function],
|
||||
supportedEndpointTypes: [
|
||||
EndpointType.Method,
|
||||
EndpointType.Function,
|
||||
EndpointType.Constructor,
|
||||
EndpointType.ClassMethod,
|
||||
EndpointType.StaticMethod,
|
||||
],
|
||||
// extensible predicate sourceModel(
|
||||
// string type, string path, string kind
|
||||
// );
|
||||
generateMethodDefinition: (method) => [
|
||||
method.packageName,
|
||||
pythonPath(
|
||||
method.typeName,
|
||||
method.methodName,
|
||||
method.endpointType,
|
||||
method.output,
|
||||
),
|
||||
pythonType(method.packageName, method.typeName, method.endpointType),
|
||||
pythonPath(method.methodName, method.output),
|
||||
method.kind,
|
||||
],
|
||||
readModeledMethod: (row) => {
|
||||
const packageName = row[0] as string;
|
||||
const {
|
||||
packageName,
|
||||
typeName,
|
||||
methodName,
|
||||
endpointType,
|
||||
path: output,
|
||||
} = parsePythonAccessPath(row[1] as string);
|
||||
} = parsePythonTypeAndPath(row[0] as string, row[1] as string);
|
||||
return {
|
||||
type: "source",
|
||||
output,
|
||||
@@ -59,30 +63,31 @@ export const python: ModelsAsDataLanguage = {
|
||||
sink: {
|
||||
extensiblePredicate: sharedExtensiblePredicates.sink,
|
||||
supportedKinds: sharedKinds.sink,
|
||||
supportedEndpointTypes: [EndpointType.Method, EndpointType.Function],
|
||||
supportedEndpointTypes: [
|
||||
EndpointType.Method,
|
||||
EndpointType.Function,
|
||||
EndpointType.Constructor,
|
||||
EndpointType.ClassMethod,
|
||||
EndpointType.StaticMethod,
|
||||
],
|
||||
// extensible predicate sinkModel(
|
||||
// string type, string path, string kind
|
||||
// );
|
||||
generateMethodDefinition: (method) => {
|
||||
return [
|
||||
method.packageName,
|
||||
pythonPath(
|
||||
method.typeName,
|
||||
method.methodName,
|
||||
method.endpointType,
|
||||
method.input,
|
||||
),
|
||||
pythonType(method.packageName, method.typeName, method.endpointType),
|
||||
pythonPath(method.methodName, method.input),
|
||||
method.kind,
|
||||
];
|
||||
},
|
||||
readModeledMethod: (row) => {
|
||||
const packageName = row[0] as string;
|
||||
const {
|
||||
packageName,
|
||||
typeName,
|
||||
methodName,
|
||||
endpointType,
|
||||
path: input,
|
||||
} = parsePythonAccessPath(row[1] as string);
|
||||
} = parsePythonTypeAndPath(row[0] as string, row[1] as string);
|
||||
return {
|
||||
type: "sink",
|
||||
input,
|
||||
@@ -100,25 +105,26 @@ export const python: ModelsAsDataLanguage = {
|
||||
summary: {
|
||||
extensiblePredicate: sharedExtensiblePredicates.summary,
|
||||
supportedKinds: sharedKinds.summary,
|
||||
supportedEndpointTypes: [EndpointType.Method, EndpointType.Function],
|
||||
supportedEndpointTypes: [
|
||||
EndpointType.Method,
|
||||
EndpointType.Function,
|
||||
EndpointType.Constructor,
|
||||
EndpointType.ClassMethod,
|
||||
EndpointType.StaticMethod,
|
||||
],
|
||||
// extensible predicate summaryModel(
|
||||
// string type, string path, string input, string output, string kind
|
||||
// );
|
||||
generateMethodDefinition: (method) => [
|
||||
method.packageName,
|
||||
pythonMethodPath(
|
||||
method.typeName,
|
||||
method.methodName,
|
||||
method.endpointType,
|
||||
),
|
||||
pythonType(method.packageName, method.typeName, method.endpointType),
|
||||
pythonMethodPath(method.methodName),
|
||||
method.input,
|
||||
method.output,
|
||||
method.kind,
|
||||
],
|
||||
readModeledMethod: (row) => {
|
||||
const packageName = row[0] as string;
|
||||
const { typeName, methodName, endpointType, path } =
|
||||
parsePythonAccessPath(row[1] as string);
|
||||
const { packageName, typeName, methodName, endpointType, path } =
|
||||
parsePythonTypeAndPath(row[0] as string, row[1] as string);
|
||||
if (path !== "") {
|
||||
throw new Error("Summary path must be a method");
|
||||
}
|
||||
@@ -144,18 +150,13 @@ export const python: ModelsAsDataLanguage = {
|
||||
// string type, string path, string kind
|
||||
// );
|
||||
generateMethodDefinition: (method) => [
|
||||
method.packageName,
|
||||
pythonMethodPath(
|
||||
method.typeName,
|
||||
method.methodName,
|
||||
method.endpointType,
|
||||
),
|
||||
pythonType(method.packageName, method.typeName, method.endpointType),
|
||||
pythonMethodPath(method.methodName),
|
||||
method.kind,
|
||||
],
|
||||
readModeledMethod: (row) => {
|
||||
const packageName = row[0] as string;
|
||||
const { typeName, methodName, endpointType, path } =
|
||||
parsePythonAccessPath(row[1] as string);
|
||||
const { packageName, typeName, methodName, endpointType, path } =
|
||||
parsePythonTypeAndPath(row[0] as string, row[1] as string);
|
||||
if (path !== "") {
|
||||
throw new Error("Neutral path must be a method");
|
||||
}
|
||||
@@ -172,25 +173,46 @@ export const python: ModelsAsDataLanguage = {
|
||||
};
|
||||
},
|
||||
},
|
||||
type: {
|
||||
extensiblePredicate: "typeModel",
|
||||
// extensible predicate typeModel(string type1, string type2, string path);
|
||||
generateMethodDefinition: (method) => [
|
||||
method.relatedTypeName,
|
||||
pythonType(method.packageName, method.typeName, method.endpointType),
|
||||
pythonPath(method.methodName, method.path),
|
||||
],
|
||||
readModeledMethod: (row) => {
|
||||
const { packageName, typeName, methodName, endpointType, path } =
|
||||
parsePythonTypeAndPath(row[1] as string, row[2] as string);
|
||||
|
||||
return {
|
||||
type: "type",
|
||||
relatedTypeName: row[0] as string,
|
||||
path,
|
||||
signature: pythonMethodSignature(typeName, methodName),
|
||||
endpointType,
|
||||
packageName,
|
||||
typeName,
|
||||
methodName,
|
||||
methodParameters: "",
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
getArgumentOptions: (method) => {
|
||||
// Argument and Parameter are equivalent in Python, but we'll use Argument in the model editor
|
||||
const argumentsList = getArgumentsList(method.methodParameters).map(
|
||||
(argument, index): MethodArgument => {
|
||||
if (
|
||||
method.endpointType === EndpointType.Method &&
|
||||
argument === "self" &&
|
||||
index === 0
|
||||
) {
|
||||
if (hasPythonSelfArgument(method.endpointType) && index === 0) {
|
||||
return {
|
||||
path: "Argument[self]",
|
||||
label: "Argument[self]: self",
|
||||
label: `Argument[self]: ${argument}`,
|
||||
};
|
||||
}
|
||||
|
||||
// If this is a method, self does not count as an argument index, so we
|
||||
// If this endpoint has a self argument, self does not count as an argument index so we
|
||||
// should start at 0 for the second argument
|
||||
if (method.endpointType === EndpointType.Method) {
|
||||
if (hasPythonSelfArgument(method.endpointType)) {
|
||||
index -= 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ export enum EndpointType {
|
||||
Method = "method",
|
||||
Constructor = "constructor",
|
||||
Function = "function",
|
||||
StaticMethod = "staticMethod",
|
||||
ClassMethod = "classMethod",
|
||||
}
|
||||
|
||||
export interface MethodDefinition {
|
||||
|
||||
@@ -17,6 +17,7 @@ export type Query = {
|
||||
* - libraryVersion: the version of the library that contains the external API. This is a string and can be empty if the version cannot be determined.
|
||||
* - type: the modeled kind of the method, either "sink", "source", "summary", or "neutral"
|
||||
* - classification: the classification of the use of the method, either "source", "test", "generated", or "unknown"
|
||||
* - kind: the kind of the endpoint, language-specific, e.g. "method" or "function"
|
||||
*/
|
||||
applicationModeQuery: string;
|
||||
/**
|
||||
@@ -32,6 +33,7 @@ export type Query = {
|
||||
* - supported: whether this method is modeled. This should be a string representation of a boolean to satify the result pattern for a problem query.
|
||||
* - libraryName: the name of the file or library that contains the method. This is a string and usually the basename of a file.
|
||||
* - type: the modeled kind of the method, either "sink", "source", "summary", or "neutral"
|
||||
* - kind: the kind of the endpoint, language-specific, e.g. "method" or "function"
|
||||
*/
|
||||
frameworkModeQuery: string;
|
||||
dependencies?: {
|
||||
@@ -50,6 +52,7 @@ export type ApplicationModeTuple = [
|
||||
string,
|
||||
ModeledMethodType,
|
||||
CallClassification,
|
||||
string | BqrsEntityValue | undefined,
|
||||
];
|
||||
|
||||
export type FrameworkModeTuple = [
|
||||
@@ -61,4 +64,5 @@ export type FrameworkModeTuple = [
|
||||
boolean,
|
||||
string,
|
||||
ModeledMethodType,
|
||||
string | BqrsEntityValue | undefined,
|
||||
];
|
||||
|
||||
@@ -9,20 +9,16 @@ export const SUPPORTED_LANGUAGES: QueryLanguage[] = [
|
||||
QueryLanguage.Java,
|
||||
QueryLanguage.CSharp,
|
||||
QueryLanguage.Ruby,
|
||||
QueryLanguage.Python,
|
||||
];
|
||||
|
||||
export function isSupportedLanguage(
|
||||
language: QueryLanguage,
|
||||
modelConfig: ModelConfig,
|
||||
_modelConfig: ModelConfig,
|
||||
) {
|
||||
if (SUPPORTED_LANGUAGES.includes(language)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (language === QueryLanguage.Python) {
|
||||
// Python is only enabled when the config setting is set
|
||||
return modelConfig.enablePython;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -544,9 +544,16 @@ export async function generateEvalLogSummaries(
|
||||
await cliServer.generateJsonLogSummary(log, jsonSummary);
|
||||
|
||||
if (humanReadableSummary !== undefined) {
|
||||
progress(progressUpdate(3, 3, "Generating summary symbols file"));
|
||||
summarySymbols = outputDir.evalLogSummarySymbolsPath;
|
||||
await generateSummarySymbolsFile(humanReadableSummary, summarySymbols);
|
||||
if (
|
||||
!(await cliServer.cliConstraints.supportsGenerateSummarySymbolMap())
|
||||
) {
|
||||
// We're using an old CLI that cannot generate the summary symbols file while generating the
|
||||
// human-readable log summary. As a fallback, create it by parsing the human-readable
|
||||
// summary.
|
||||
progress(progressUpdate(3, 3, "Generating summary symbols file"));
|
||||
await generateSummarySymbolsFile(humanReadableSummary, summarySymbols);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,18 @@ const ShowPathsLink = styled(VSCodeLink)`
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const Label = styled.span`
|
||||
color: var(--vscode-descriptionForeground);
|
||||
margin-left: 10px;
|
||||
`;
|
||||
|
||||
function getShortestPathLength(codeFlows: CodeFlow[]): number {
|
||||
const allPathLengths = codeFlows
|
||||
.map((codeFlow) => codeFlow.threadFlows.length)
|
||||
.flat();
|
||||
return Math.min(...allPathLengths);
|
||||
}
|
||||
|
||||
export type CodePathsProps = {
|
||||
codeFlows: CodeFlow[];
|
||||
ruleDescription: string;
|
||||
@@ -40,6 +52,7 @@ export const CodePaths = ({
|
||||
return (
|
||||
<>
|
||||
<ShowPathsLink onClick={onShowPathsClick}>Show paths</ShowPathsLink>
|
||||
<Label>(Shortest: {getShortestPathLength(codeFlows)})</Label>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -24,6 +24,12 @@ describe(CodePaths.name, () => {
|
||||
expect(screen.getByText("Show paths")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders shortest path for code flows", () => {
|
||||
render();
|
||||
|
||||
expect(screen.getByText("(Shortest: 1)")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("posts extension message when 'show paths' link clicked", async () => {
|
||||
render();
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@ import type {
|
||||
ToCompareViewMessage,
|
||||
SetComparisonsMessage,
|
||||
SetComparisonQueryInfoMessage,
|
||||
UserSettings,
|
||||
} from "../../common/interface-types";
|
||||
import { DEFAULT_USER_SETTINGS } from "../../common/interface-types";
|
||||
import CompareSelector from "./CompareSelector";
|
||||
import { vscode } from "../vscode-api";
|
||||
import CompareTable from "./CompareTable";
|
||||
@@ -31,6 +33,9 @@ export function Compare(_: Record<string, never>): React.JSX.Element {
|
||||
const [comparison, setComparison] = useState<SetComparisonsMessage | null>(
|
||||
null,
|
||||
);
|
||||
const [userSettings, setUserSettings] = useState<UserSettings>(
|
||||
DEFAULT_USER_SETTINGS,
|
||||
);
|
||||
|
||||
const message = comparison?.message || "Empty comparison";
|
||||
const hasRows =
|
||||
@@ -48,6 +53,9 @@ export function Compare(_: Record<string, never>): React.JSX.Element {
|
||||
case "setComparisons":
|
||||
setComparison(msg);
|
||||
break;
|
||||
case "setUserSettings":
|
||||
setUserSettings(msg.userSettings);
|
||||
break;
|
||||
default:
|
||||
assertNever(msg);
|
||||
}
|
||||
@@ -85,6 +93,7 @@ export function Compare(_: Record<string, never>): React.JSX.Element {
|
||||
<CompareTable
|
||||
queryInfo={queryInfo}
|
||||
comparison={comparison}
|
||||
userSettings={userSettings}
|
||||
></CompareTable>
|
||||
) : (
|
||||
<Message>{message}</Message>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
SetComparisonQueryInfoMessage,
|
||||
SetComparisonsMessage,
|
||||
UserSettings,
|
||||
} from "../../common/interface-types";
|
||||
import { className } from "../results/result-table-utils";
|
||||
import { vscode } from "../vscode-api";
|
||||
@@ -12,6 +13,7 @@ import { InterpretedCompareResultTable } from "./InterpretedCompareResultTable";
|
||||
interface Props {
|
||||
queryInfo: SetComparisonQueryInfoMessage;
|
||||
comparison: SetComparisonsMessage;
|
||||
userSettings: UserSettings;
|
||||
}
|
||||
|
||||
const OpenButton = styled(TextButton)`
|
||||
@@ -29,7 +31,11 @@ const Table = styled.table`
|
||||
}
|
||||
`;
|
||||
|
||||
export default function CompareTable({ queryInfo, comparison }: Props) {
|
||||
export default function CompareTable({
|
||||
queryInfo,
|
||||
comparison,
|
||||
userSettings,
|
||||
}: Props) {
|
||||
const result = comparison.result!;
|
||||
|
||||
async function openQuery(kind: "from" | "to") {
|
||||
@@ -78,6 +84,7 @@ export default function CompareTable({ queryInfo, comparison }: Props) {
|
||||
{result.kind === "interpreted" && (
|
||||
<InterpretedCompareResultTable
|
||||
results={result.from}
|
||||
userSettings={userSettings}
|
||||
databaseUri={queryInfo.databaseUri}
|
||||
sourceLocationPrefix={result.sourceLocationPrefix}
|
||||
/>
|
||||
@@ -96,6 +103,7 @@ export default function CompareTable({ queryInfo, comparison }: Props) {
|
||||
{result.kind === "interpreted" && (
|
||||
<InterpretedCompareResultTable
|
||||
results={result.to}
|
||||
userSettings={userSettings}
|
||||
databaseUri={queryInfo.databaseUri}
|
||||
sourceLocationPrefix={result.sourceLocationPrefix}
|
||||
/>
|
||||
|
||||
@@ -1,27 +1,32 @@
|
||||
import type { Result } from "sarif";
|
||||
import type { Result, Run } from "sarif";
|
||||
import { AlertTable } from "../results/AlertTable";
|
||||
import type { UserSettings } from "../../common/interface-types";
|
||||
|
||||
type Props = {
|
||||
results: Result[];
|
||||
databaseUri: string;
|
||||
sourceLocationPrefix: string;
|
||||
run?: Run;
|
||||
userSettings: UserSettings;
|
||||
};
|
||||
|
||||
export const InterpretedCompareResultTable = ({
|
||||
results,
|
||||
databaseUri,
|
||||
sourceLocationPrefix,
|
||||
userSettings,
|
||||
}: Props) => {
|
||||
return (
|
||||
<AlertTable
|
||||
results={results}
|
||||
userSettings={userSettings}
|
||||
databaseUri={databaseUri}
|
||||
sourceLocationPrefix={sourceLocationPrefix}
|
||||
header={
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={2}></th>
|
||||
<th className={`vscode-codeql__alert-message-cell`} colSpan={3}>
|
||||
<th className={`vscode-codeql__alert-message-cell`} colSpan={4}>
|
||||
Message
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Location, Result } from "sarif";
|
||||
import type { Location, Result, Run } from "sarif";
|
||||
import type {
|
||||
PathNode,
|
||||
Result as ResultKeysResult,
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
import { getPath, getPathNode, getResult, keyToString } from "./result-keys";
|
||||
import { className, jumpToLocation } from "./result-table-utils";
|
||||
import { onNavigation } from "./navigation";
|
||||
import type { NavigateMsg } from "../../common/interface-types";
|
||||
import type { NavigateMsg, UserSettings } from "../../common/interface-types";
|
||||
import { NavigationDirection } from "../../common/interface-types";
|
||||
import { isNoLocation, parseSarifLocation } from "../../common/sarif-utils";
|
||||
import { sendTelemetry } from "../common/telemetry";
|
||||
@@ -21,6 +21,8 @@ type Props = {
|
||||
results: Result[];
|
||||
databaseUri: string;
|
||||
sourceLocationPrefix: string;
|
||||
run?: Run;
|
||||
userSettings: UserSettings;
|
||||
numTruncatedResults?: number;
|
||||
|
||||
header: ReactNode;
|
||||
@@ -31,6 +33,8 @@ export function AlertTable({
|
||||
results,
|
||||
databaseUri,
|
||||
sourceLocationPrefix,
|
||||
run,
|
||||
userSettings,
|
||||
numTruncatedResults,
|
||||
header,
|
||||
noResults,
|
||||
@@ -202,6 +206,8 @@ export function AlertTable({
|
||||
selectedItem={selectedItem}
|
||||
selectedItemRef={selectedItemRef}
|
||||
databaseUri={databaseUri}
|
||||
run={run}
|
||||
userSettings={userSettings}
|
||||
sourceLocationPrefix={sourceLocationPrefix}
|
||||
updateSelectionCallback={updateSelectionCallback}
|
||||
toggleExpanded={toggle}
|
||||
|
||||
@@ -45,7 +45,7 @@ export function AlertTableHeader({
|
||||
<th colSpan={2}></th>
|
||||
<th
|
||||
className={`${sortClass()} vscode-codeql__alert-message-cell`}
|
||||
colSpan={3}
|
||||
colSpan={4}
|
||||
onClick={toggleSortStateForColumn}
|
||||
>
|
||||
Message
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ThreadFlowLocation } from "sarif";
|
||||
import type { ThreadFlowLocation, Run } from "sarif";
|
||||
import type {
|
||||
PathNode,
|
||||
Result as ResultKeysResult,
|
||||
@@ -9,6 +9,8 @@ import { SarifLocation } from "./locations/SarifLocation";
|
||||
import { selectableZebraStripe } from "./result-table-utils";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { VerticalRule } from "../common/VerticalRule";
|
||||
import type { UserSettings } from "../../common/interface-types";
|
||||
import { TaxaLocations } from "./locations/TaxaLocations";
|
||||
|
||||
interface Props {
|
||||
step: ThreadFlowLocation;
|
||||
@@ -19,6 +21,8 @@ interface Props {
|
||||
selectedItemRef: React.RefObject<HTMLTableRowElement>;
|
||||
databaseUri: string;
|
||||
sourceLocationPrefix: string;
|
||||
run?: Run;
|
||||
userSettings: UserSettings;
|
||||
updateSelectionCallback: (
|
||||
resultKey: PathNode | ResultKeysResult | undefined,
|
||||
) => void;
|
||||
@@ -34,6 +38,8 @@ export function AlertTablePathNodeRow(props: Props) {
|
||||
selectedItemRef,
|
||||
databaseUri,
|
||||
sourceLocationPrefix,
|
||||
run,
|
||||
userSettings,
|
||||
updateSelectionCallback,
|
||||
} = props;
|
||||
|
||||
@@ -86,6 +92,23 @@ export function AlertTablePathNodeRow(props: Props) {
|
||||
"[no location]"
|
||||
)}
|
||||
</td>
|
||||
<td
|
||||
{...selectableZebraStripe(
|
||||
isSelected,
|
||||
zebraIndex,
|
||||
"vscode-codeql__taxa-cell",
|
||||
)}
|
||||
>
|
||||
{userSettings.shouldShowProvenance ? (
|
||||
<div className="vscode-codeql__taxa-cell-contents">
|
||||
<TaxaLocations
|
||||
taxa={step.taxa}
|
||||
run={run}
|
||||
onClick={handleSarifLocationClicked}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</td>
|
||||
<td
|
||||
{...selectableZebraStripe(
|
||||
isSelected,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ThreadFlow } from "sarif";
|
||||
import type { Run, ThreadFlow } from "sarif";
|
||||
import type {
|
||||
PathNode,
|
||||
Result as ResultKeysResult,
|
||||
@@ -10,6 +10,7 @@ import { AlertTablePathNodeRow } from "./AlertTablePathNodeRow";
|
||||
import { AlertTableDropdownIndicatorCell } from "./AlertTableDropdownIndicatorCell";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { VerticalRule } from "../common/VerticalRule";
|
||||
import type { UserSettings } from "../../common/interface-types";
|
||||
|
||||
interface Props {
|
||||
path: ThreadFlow;
|
||||
@@ -20,6 +21,8 @@ interface Props {
|
||||
selectedItemRef: React.RefObject<HTMLTableRowElement>;
|
||||
databaseUri: string;
|
||||
sourceLocationPrefix: string;
|
||||
run?: Run;
|
||||
userSettings: UserSettings;
|
||||
updateSelectionCallback: (
|
||||
resultKey: PathNode | ResultKeysResult | undefined,
|
||||
) => void;
|
||||
@@ -61,7 +64,7 @@ export function AlertTablePathRow(props: Props) {
|
||||
expanded={currentPathExpanded}
|
||||
onClick={handleDropdownClick}
|
||||
/>
|
||||
<td className="vscode-codeql__text-center" colSpan={3}>
|
||||
<td className="vscode-codeql__text-center" colSpan={4}>
|
||||
Path
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Result } from "sarif";
|
||||
import type { Result, Run } from "sarif";
|
||||
import type {
|
||||
PathNode,
|
||||
Result as ResultKeysResult,
|
||||
@@ -12,6 +12,7 @@ import { useCallback, useMemo } from "react";
|
||||
import { SarifLocation } from "./locations/SarifLocation";
|
||||
import { SarifMessageWithLocations } from "./locations/SarifMessageWithLocations";
|
||||
import { AlertTablePathRow } from "./AlertTablePathRow";
|
||||
import type { UserSettings } from "../../common/interface-types";
|
||||
|
||||
interface Props {
|
||||
result: Result;
|
||||
@@ -21,6 +22,8 @@ interface Props {
|
||||
selectedItemRef: React.RefObject<HTMLTableRowElement>;
|
||||
databaseUri: string;
|
||||
sourceLocationPrefix: string;
|
||||
run?: Run;
|
||||
userSettings: UserSettings;
|
||||
updateSelectionCallback: (
|
||||
resultKey: PathNode | ResultKeysResult | undefined,
|
||||
) => void;
|
||||
@@ -90,7 +93,7 @@ export function AlertTableResultRow(props: Props) {
|
||||
{result.codeFlows === undefined ? (
|
||||
<>
|
||||
<td className="vscode-codeql__icon-cell">{info}</td>
|
||||
<td colSpan={3}>{msg}</td>
|
||||
<td colSpan={4}>{msg}</td>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
@@ -99,7 +102,7 @@ export function AlertTableResultRow(props: Props) {
|
||||
onClick={handleDropdownClick}
|
||||
/>
|
||||
<td className="vscode-codeql__icon-cell">{listUnordered}</td>
|
||||
<td colSpan={2}>{msg}</td>
|
||||
<td colSpan={3}>{msg}</td>
|
||||
</>
|
||||
)}
|
||||
<td className="vscode-codeql__location-cell">
|
||||
|
||||
@@ -6,7 +6,7 @@ import { AlertTableNoResults } from "./AlertTableNoResults";
|
||||
import { AlertTableHeader } from "./AlertTableHeader";
|
||||
|
||||
export function ResultTable(props: ResultTableProps) {
|
||||
const { resultSet } = props;
|
||||
const { resultSet, userSettings } = props;
|
||||
switch (resultSet.t) {
|
||||
case "RawResultSet":
|
||||
return <RawTable {...props} resultSet={resultSet.resultSet} />;
|
||||
@@ -21,6 +21,8 @@ export function ResultTable(props: ResultTableProps) {
|
||||
sourceLocationPrefix={
|
||||
resultSet.interpretation.sourceLocationPrefix
|
||||
}
|
||||
run={data.runs[0]}
|
||||
userSettings={userSettings}
|
||||
numTruncatedResults={resultSet.interpretation.numTruncatedResults}
|
||||
header={<AlertTableHeader sortState={data.sortState} />}
|
||||
noResults={
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
ResultSet,
|
||||
ParsedResultSets,
|
||||
IntoResultsViewMsg,
|
||||
UserSettings,
|
||||
} from "../../common/interface-types";
|
||||
import {
|
||||
ALERTS_TABLE_NAME,
|
||||
@@ -33,6 +34,7 @@ interface ResultTablesProps {
|
||||
rawResultSets: readonly ResultSet[];
|
||||
interpretation: Interpretation | undefined;
|
||||
database: DatabaseInfo;
|
||||
userSettings: UserSettings;
|
||||
metadata?: QueryMetadata;
|
||||
resultsPath: string;
|
||||
origResultsPaths: ResultsPaths;
|
||||
@@ -94,6 +96,7 @@ export function ResultTables(props: ResultTablesProps) {
|
||||
interpretation,
|
||||
database,
|
||||
resultsPath,
|
||||
userSettings,
|
||||
metadata,
|
||||
origResultsPaths,
|
||||
isLoadingNewResults,
|
||||
@@ -242,6 +245,7 @@ export function ResultTables(props: ResultTablesProps) {
|
||||
<ResultTable
|
||||
key={resultSetName}
|
||||
resultSet={resultSet}
|
||||
userSettings={userSettings}
|
||||
databaseUri={database.databaseUri}
|
||||
resultsPath={resultsPath}
|
||||
sortState={sortStates.get(resultSetName)}
|
||||
|
||||
@@ -9,9 +9,11 @@ import type {
|
||||
ResultsPaths,
|
||||
ParsedResultSets,
|
||||
ResultSet,
|
||||
UserSettings,
|
||||
} from "../../common/interface-types";
|
||||
import {
|
||||
ALERTS_TABLE_NAME,
|
||||
DEFAULT_USER_SETTINGS,
|
||||
GRAPH_TABLE_NAME,
|
||||
} from "../../common/interface-types";
|
||||
import { ResultTables } from "./ResultTables";
|
||||
@@ -77,6 +79,10 @@ export function ResultsApp() {
|
||||
isExpectingResultsUpdate: true,
|
||||
});
|
||||
|
||||
const [userSettings, setUserSettings] = useState<UserSettings>(
|
||||
DEFAULT_USER_SETTINGS,
|
||||
);
|
||||
|
||||
const updateStateWithNewResultsInfo = useCallback(
|
||||
(resultsInfo: ResultsInfo): void => {
|
||||
let results: Results | null = null;
|
||||
@@ -110,6 +116,10 @@ export function ResultsApp() {
|
||||
const handleMessage = useCallback(
|
||||
(msg: IntoResultsViewMsg): void => {
|
||||
switch (msg.t) {
|
||||
case "setUserSettings":
|
||||
setUserSettings(msg.userSettings);
|
||||
break;
|
||||
|
||||
case "setState":
|
||||
updateStateWithNewResultsInfo({
|
||||
resultsPath: msg.resultsPath,
|
||||
@@ -217,6 +227,7 @@ export function ResultsApp() {
|
||||
? displayedResults.resultsInfo.interpretation
|
||||
: undefined
|
||||
}
|
||||
userSettings={userSettings}
|
||||
database={displayedResults.results.database}
|
||||
origResultsPaths={displayedResults.resultsInfo.origResultsPaths}
|
||||
resultsPath={displayedResults.resultsInfo.resultsPath}
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { UrlValueResolvable } from "../../../common/raw-result-types";
|
||||
interface Props {
|
||||
loc: UrlValueResolvable;
|
||||
label: string;
|
||||
databaseUri: string;
|
||||
databaseUri: string | undefined;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,17 +26,13 @@ export function Location({
|
||||
const displayLabel = useMemo(() => convertNonPrintableChars(label), [label]);
|
||||
|
||||
if (loc === undefined) {
|
||||
return <NonClickableLocation msg={displayLabel} />;
|
||||
return <NonClickableLocation msg={displayLabel} locationHint={title} />;
|
||||
}
|
||||
|
||||
if (loc.type === "string") {
|
||||
return <a href={loc.value}>{loc.value}</a>;
|
||||
}
|
||||
|
||||
if (databaseUri === undefined) {
|
||||
return <NonClickableLocation msg={displayLabel} locationHint={title} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ClickableLocation
|
||||
loc={loc}
|
||||
|
||||
@@ -8,7 +8,7 @@ interface Props {
|
||||
text?: string;
|
||||
loc?: SarifLogLocation;
|
||||
sourceLocationPrefix: string;
|
||||
databaseUri: string;
|
||||
databaseUri: string | undefined;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
import type {
|
||||
Location as SarifLogLocation,
|
||||
ArtifactLocation,
|
||||
PhysicalLocation,
|
||||
ReportingDescriptorReference,
|
||||
Run,
|
||||
ToolComponent,
|
||||
} from "sarif";
|
||||
import { SarifLocation } from "./SarifLocation";
|
||||
|
||||
/** The definition of a taxon for a data extension model row. */
|
||||
interface ModelTaxon {
|
||||
kind: "model";
|
||||
location: SarifLogLocation;
|
||||
}
|
||||
|
||||
/** A taxon for a built-in model, such as `AdditionalFlowStep`. */
|
||||
interface BuiltInTaxon {
|
||||
kind: "string";
|
||||
text: string;
|
||||
}
|
||||
|
||||
type TaxonDefinition = ModelTaxon | BuiltInTaxon;
|
||||
|
||||
/** Resolve an `ArtifactLocation` that might contain a relative reference instead of an absolute
|
||||
* URI.
|
||||
*/
|
||||
function resolveArtifactLocation(
|
||||
location: ArtifactLocation,
|
||||
baseUri: URL,
|
||||
): ArtifactLocation {
|
||||
if (location.uri === undefined) {
|
||||
// No URI at all. Just return the original location.
|
||||
return location;
|
||||
}
|
||||
return {
|
||||
...location,
|
||||
uri: new URL(location.uri, baseUri).toString(),
|
||||
};
|
||||
}
|
||||
|
||||
/** Get the URI of the pack's local root directory, if available. */
|
||||
function getLocalPackUri(extension: ToolComponent): URL | undefined {
|
||||
if (extension.locations === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const localPackLocation = extension.locations.find(
|
||||
(loc) =>
|
||||
loc.properties !== undefined &&
|
||||
loc.properties.tags !== undefined &&
|
||||
loc.properties.tags.includes("CodeQL/LocalPackRoot"),
|
||||
);
|
||||
if (localPackLocation === undefined || localPackLocation.uri === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return new URL(localPackLocation.uri);
|
||||
}
|
||||
|
||||
/** Resolve a `ReportingDescriptorReference` to the built-in taxon it refers to, or `undefined` if
|
||||
* it is not a built-in taxon.
|
||||
*/
|
||||
function resolveBuiltInTaxon(
|
||||
taxonRef: ReportingDescriptorReference,
|
||||
): BuiltInTaxon | undefined {
|
||||
if (
|
||||
taxonRef.id !== undefined &&
|
||||
taxonRef.index === undefined &&
|
||||
taxonRef.toolComponent === undefined
|
||||
) {
|
||||
return {
|
||||
kind: "string",
|
||||
text: taxonRef.id,
|
||||
};
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a `ReportingDescriptorReference` to the MaD taxon definition it refers to, or
|
||||
* `undefined` if it does not refer to a MaD model.
|
||||
*/
|
||||
function resolveModelTaxon(
|
||||
taxonRef: ReportingDescriptorReference,
|
||||
run: Run,
|
||||
): ModelTaxon | undefined {
|
||||
const extensions = run.tool.extensions;
|
||||
if (extensions === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const extensionIndex = taxonRef.toolComponent?.index;
|
||||
if (
|
||||
extensionIndex === undefined ||
|
||||
extensionIndex < 0 ||
|
||||
extensionIndex >= extensions.length
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const extension = extensions[extensionIndex];
|
||||
if (extension.taxa === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const localPackUri = getLocalPackUri(extension);
|
||||
if (localPackUri === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const taxonIndex = taxonRef.index;
|
||||
if (
|
||||
taxonIndex === undefined ||
|
||||
taxonIndex < 0 ||
|
||||
taxonIndex >= extension.taxa.length
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const taxonDef = extension.taxa[taxonIndex];
|
||||
if (taxonDef.properties === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const location: PhysicalLocation =
|
||||
taxonDef.properties["CodeQL/DataExtensionLocation"];
|
||||
if (location === undefined || location.artifactLocation === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
kind: "model",
|
||||
location: {
|
||||
physicalLocation: {
|
||||
...location,
|
||||
artifactLocation: resolveArtifactLocation(
|
||||
location.artifactLocation,
|
||||
localPackUri,
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Resolve a `ReportingDescriptorReference` to the taxon definition it refers to. */
|
||||
function resolveTaxonDefinition(
|
||||
run: Run,
|
||||
taxonRef: ReportingDescriptorReference,
|
||||
): TaxonDefinition | undefined {
|
||||
return resolveModelTaxon(taxonRef, run) ?? resolveBuiltInTaxon(taxonRef);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
taxa: ReportingDescriptorReference[] | undefined;
|
||||
run: Run | undefined;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
/** Generate the React elements for each taxon. */
|
||||
export function TaxaLocations({
|
||||
taxa,
|
||||
run,
|
||||
onClick,
|
||||
}: Props): React.JSX.Element[] {
|
||||
if (taxa === undefined || taxa.length === 0 || run === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return taxa.flatMap((taxonRef, index) => {
|
||||
if (taxonRef.properties === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const role = taxonRef.properties["CodeQL/DataflowRole"];
|
||||
if (typeof role !== "string") {
|
||||
return [];
|
||||
}
|
||||
|
||||
const taxonDef = resolveTaxonDefinition(run, taxonRef);
|
||||
if (taxonDef === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={index}>
|
||||
{`(${role}) `}
|
||||
<SarifLocation
|
||||
loc={taxonDef.kind === "model" ? taxonDef.location : undefined}
|
||||
databaseUri={undefined}
|
||||
text={taxonDef.kind === "string" ? taxonDef.text : undefined}
|
||||
sourceLocationPrefix=""
|
||||
onClick={onClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import type {
|
||||
QueryMetadata,
|
||||
RawResultsSortState,
|
||||
ResultSet,
|
||||
UserSettings,
|
||||
} from "../../common/interface-types";
|
||||
import { SortDirection } from "../../common/interface-types";
|
||||
import { assertNever } from "../../common/helpers-pure";
|
||||
@@ -11,6 +12,7 @@ import type { UrlValueResolvable } from "../../common/raw-result-types";
|
||||
export interface ResultTableProps {
|
||||
resultSet: ResultSet;
|
||||
databaseUri: string;
|
||||
userSettings: UserSettings;
|
||||
metadata?: QueryMetadata;
|
||||
resultsPath: string | undefined;
|
||||
sortState?: RawResultsSortState;
|
||||
@@ -41,7 +43,7 @@ export const selectedRowClassName = "vscode-codeql__result-table-row--selected";
|
||||
|
||||
export function jumpToLocation(
|
||||
loc: UrlValueResolvable,
|
||||
databaseUri: string,
|
||||
databaseUri: string | undefined,
|
||||
): void {
|
||||
vscode.postMessage({
|
||||
t: "viewSourceFile",
|
||||
|
||||
@@ -144,3 +144,12 @@ td.vscode-codeql__path-index-cell {
|
||||
.vscode-codeql__location-cell {
|
||||
text-align: right !important;
|
||||
}
|
||||
|
||||
.vscode-codeql__taxa-cell {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
.vscode-codeql__taxa-cell-contents {
|
||||
background-color: transparent;
|
||||
display: grid;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[
|
||||
"v2.17.5",
|
||||
"v2.18.1",
|
||||
"v2.17.6",
|
||||
"v2.16.6",
|
||||
"v2.15.5",
|
||||
"v2.14.6",
|
||||
"nightly"
|
||||
]
|
||||
|
||||
@@ -1,16 +1,59 @@
|
||||
import {
|
||||
parsePythonAccessPath,
|
||||
parsePythonType,
|
||||
pythonEndpointType,
|
||||
pythonPath,
|
||||
} from "../../../../../src/model-editor/languages/python/access-paths";
|
||||
import { EndpointType } from "../../../../../src/model-editor/method";
|
||||
|
||||
describe("parsePythonType", () => {
|
||||
it("parses a type with a package", () => {
|
||||
expect(parsePythonType("requests.utils")).toEqual({
|
||||
packageName: "requests",
|
||||
typeName: "utils",
|
||||
});
|
||||
});
|
||||
|
||||
it("parses a nested type with a package", () => {
|
||||
expect(parsePythonType("requests.adapters.HTTPAdapter")).toEqual({
|
||||
packageName: "requests",
|
||||
typeName: "adapters.HTTPAdapter",
|
||||
});
|
||||
});
|
||||
|
||||
it("parses a package without a type", () => {
|
||||
expect(parsePythonType("requests")).toEqual({
|
||||
packageName: "requests",
|
||||
typeName: "",
|
||||
});
|
||||
});
|
||||
|
||||
it("parses an empty string", () => {
|
||||
expect(parsePythonType("")).toEqual({
|
||||
packageName: "",
|
||||
typeName: "",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const testCases: Array<{
|
||||
path: string;
|
||||
shortTypeName: string;
|
||||
method: ReturnType<typeof parsePythonAccessPath>;
|
||||
}> = [
|
||||
{
|
||||
path: "Member[CommonTokens].Member[Class].Instance.Member[foo]",
|
||||
shortTypeName: "",
|
||||
method: {
|
||||
typeName: "CommonTokens.Class",
|
||||
methodName: "foo",
|
||||
endpointType: EndpointType.Method,
|
||||
path: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "Member[foo]",
|
||||
shortTypeName: "CommonTokens.Class",
|
||||
method: {
|
||||
typeName: "CommonTokens.Class",
|
||||
methodName: "foo",
|
||||
@@ -20,6 +63,17 @@ const testCases: Array<{
|
||||
},
|
||||
{
|
||||
path: "Member[CommonTokens].Member[Class].Instance.Member[foo].Parameter[self]",
|
||||
shortTypeName: "",
|
||||
method: {
|
||||
typeName: "CommonTokens.Class",
|
||||
methodName: "foo",
|
||||
endpointType: EndpointType.Method,
|
||||
path: "Parameter[self]",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "Member[foo].Parameter[self]",
|
||||
shortTypeName: "CommonTokens.Class",
|
||||
method: {
|
||||
typeName: "CommonTokens.Class",
|
||||
methodName: "foo",
|
||||
@@ -29,6 +83,7 @@ const testCases: Array<{
|
||||
},
|
||||
{
|
||||
path: "Member[getSource].ReturnValue",
|
||||
shortTypeName: "",
|
||||
method: {
|
||||
typeName: "",
|
||||
methodName: "getSource",
|
||||
@@ -38,6 +93,17 @@ const testCases: Array<{
|
||||
},
|
||||
{
|
||||
path: "Member[CommonTokens].Member[makePromise].ReturnValue.Awaited",
|
||||
shortTypeName: "",
|
||||
method: {
|
||||
typeName: "CommonTokens",
|
||||
methodName: "makePromise",
|
||||
endpointType: EndpointType.Function,
|
||||
path: "ReturnValue.Awaited",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "Member[makePromise].ReturnValue.Awaited",
|
||||
shortTypeName: "CommonTokens!",
|
||||
method: {
|
||||
typeName: "CommonTokens",
|
||||
methodName: "makePromise",
|
||||
@@ -47,6 +113,17 @@ const testCases: Array<{
|
||||
},
|
||||
{
|
||||
path: "Member[ArgPos].Member[anyParam].Argument[any]",
|
||||
shortTypeName: "",
|
||||
method: {
|
||||
typeName: "ArgPos",
|
||||
methodName: "anyParam",
|
||||
endpointType: EndpointType.Function,
|
||||
path: "Argument[any]",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "Member[anyParam].Argument[any]",
|
||||
shortTypeName: "ArgPos!",
|
||||
method: {
|
||||
typeName: "ArgPos",
|
||||
methodName: "anyParam",
|
||||
@@ -56,6 +133,17 @@ const testCases: Array<{
|
||||
},
|
||||
{
|
||||
path: "Member[ArgPos].Instance.Member[self_thing].Argument[self]",
|
||||
shortTypeName: "",
|
||||
method: {
|
||||
typeName: "ArgPos",
|
||||
methodName: "self_thing",
|
||||
endpointType: EndpointType.Method,
|
||||
path: "Argument[self]",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "Member[self_thing].Argument[self]",
|
||||
shortTypeName: "ArgPos",
|
||||
method: {
|
||||
typeName: "ArgPos",
|
||||
methodName: "self_thing",
|
||||
@@ -66,44 +154,138 @@ const testCases: Array<{
|
||||
];
|
||||
|
||||
describe("parsePythonAccessPath", () => {
|
||||
it.each(testCases)("parses $path", ({ path, method }) => {
|
||||
expect(parsePythonAccessPath(path)).toEqual(method);
|
||||
});
|
||||
it.each(testCases)(
|
||||
"parses $path with $shortTypeName",
|
||||
({ path, shortTypeName, method }) => {
|
||||
expect(parsePythonAccessPath(path, shortTypeName)).toEqual(method);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("pythonPath", () => {
|
||||
it.each(testCases)("constructs $path", ({ path, method }) => {
|
||||
expect(
|
||||
pythonPath(
|
||||
method.typeName,
|
||||
method.methodName,
|
||||
method.endpointType,
|
||||
method.path,
|
||||
),
|
||||
).toEqual(path);
|
||||
it("returns empty for an empty method name", () => {
|
||||
expect(pythonPath("", "ReturnValue")).toEqual("ReturnValue");
|
||||
});
|
||||
|
||||
it("returns empty for an empty path", () => {
|
||||
expect(pythonPath("foo", "")).toEqual("Member[foo]");
|
||||
});
|
||||
|
||||
it("returns correctly for a full method name and path", () => {
|
||||
expect(pythonPath("foo", "ReturnValue")).toEqual("Member[foo].ReturnValue");
|
||||
});
|
||||
});
|
||||
|
||||
describe("pythonEndpointType", () => {
|
||||
it("returns method for a method", () => {
|
||||
expect(
|
||||
pythonEndpointType({
|
||||
packageName: "testlib",
|
||||
typeName: "CommonTokens",
|
||||
methodName: "foo",
|
||||
methodParameters: "(self,a)",
|
||||
}),
|
||||
pythonEndpointType(
|
||||
{
|
||||
packageName: "testlib",
|
||||
typeName: "CommonTokens",
|
||||
methodName: "foo",
|
||||
methodParameters: "(self,a)",
|
||||
},
|
||||
"InstanceMethod",
|
||||
),
|
||||
).toEqual(EndpointType.Method);
|
||||
});
|
||||
|
||||
it("returns class method for a class method", () => {
|
||||
expect(
|
||||
pythonEndpointType(
|
||||
{
|
||||
packageName: "testlib",
|
||||
typeName: "CommonTokens",
|
||||
methodName: "foo",
|
||||
methodParameters: "(cls,a)",
|
||||
},
|
||||
"ClassMethod",
|
||||
),
|
||||
).toEqual(EndpointType.ClassMethod);
|
||||
});
|
||||
|
||||
it("returns static method for a static method", () => {
|
||||
expect(
|
||||
pythonEndpointType(
|
||||
{
|
||||
packageName: "testlib",
|
||||
typeName: "CommonTokens",
|
||||
methodName: "foo",
|
||||
methodParameters: "(a)",
|
||||
},
|
||||
"StaticMethod",
|
||||
),
|
||||
).toEqual(EndpointType.StaticMethod);
|
||||
});
|
||||
|
||||
it("returns function for a function", () => {
|
||||
expect(
|
||||
pythonEndpointType({
|
||||
packageName: "testlib",
|
||||
typeName: "CommonTokens",
|
||||
methodName: "foo",
|
||||
methodParameters: "(a)",
|
||||
}),
|
||||
pythonEndpointType(
|
||||
{
|
||||
packageName: "testlib",
|
||||
typeName: "",
|
||||
methodName: "foo",
|
||||
methodParameters: "(a)",
|
||||
},
|
||||
"Function",
|
||||
),
|
||||
).toEqual(EndpointType.Function);
|
||||
});
|
||||
|
||||
it("returns constructor for an init method", () => {
|
||||
expect(
|
||||
pythonEndpointType(
|
||||
{
|
||||
packageName: "testlib",
|
||||
typeName: "CommonTokens",
|
||||
methodName: "foo",
|
||||
methodParameters: "(a)",
|
||||
},
|
||||
"InitMethod",
|
||||
),
|
||||
).toEqual(EndpointType.Constructor);
|
||||
});
|
||||
|
||||
it("returns class for a class", () => {
|
||||
expect(
|
||||
pythonEndpointType(
|
||||
{
|
||||
packageName: "testlib",
|
||||
typeName: "CommonTokens",
|
||||
methodName: "",
|
||||
methodParameters: "",
|
||||
},
|
||||
"Class",
|
||||
),
|
||||
).toEqual(EndpointType.Class);
|
||||
});
|
||||
|
||||
it("returns method for a method without endpoint kind", () => {
|
||||
expect(
|
||||
pythonEndpointType(
|
||||
{
|
||||
packageName: "testlib",
|
||||
typeName: "CommonTokens",
|
||||
methodName: "foo",
|
||||
methodParameters: "(self,a)",
|
||||
},
|
||||
undefined,
|
||||
),
|
||||
).toEqual(EndpointType.Method);
|
||||
});
|
||||
|
||||
it("returns function for a function without endpoint kind", () => {
|
||||
expect(
|
||||
pythonEndpointType(
|
||||
{
|
||||
packageName: "testlib",
|
||||
typeName: "CommonTokens",
|
||||
methodName: "foo",
|
||||
methodParameters: "(a)",
|
||||
},
|
||||
undefined,
|
||||
),
|
||||
).toEqual(EndpointType.Function);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user