Compare commits
167 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ed6b011a5 | ||
|
|
912254fd3c | ||
|
|
7eab911cc5 | ||
|
|
80ae9a4b36 | ||
|
|
868fae093d | ||
|
|
1289ab509c | ||
|
|
5f489212d4 | ||
|
|
fea45ea04d | ||
|
|
a39e55590a | ||
|
|
6e4641f2c1 | ||
|
|
553e5cb4a1 | ||
|
|
4c0f68f193 | ||
|
|
1ee9cdaadd | ||
|
|
098437b463 | ||
|
|
558a70e3c8 | ||
|
|
7c10447bb5 | ||
|
|
9fd6cb8c1f | ||
|
|
f4da522953 | ||
|
|
6dfe1736f8 | ||
|
|
b46e0ab175 | ||
|
|
34fa629054 | ||
|
|
5107086a93 | ||
|
|
58c5c0e5f5 | ||
|
|
5427b5718f | ||
|
|
0cc399507f | ||
|
|
bb9299e0e2 | ||
|
|
e8afa54584 | ||
|
|
d94443e025 | ||
|
|
0e5cb1a3e8 | ||
|
|
59958a5b32 | ||
|
|
3d9b2da514 | ||
|
|
3b8cea8df4 | ||
|
|
6adf683c87 | ||
|
|
37f1c62ee6 | ||
|
|
c1107d7423 | ||
|
|
72fa1c5583 | ||
|
|
5f65498e0a | ||
|
|
6e21706c15 | ||
|
|
4dcca4e97c | ||
|
|
84492d2fb9 | ||
|
|
a2c9ac792b | ||
|
|
18704558d3 | ||
|
|
ca16dca7ed | ||
|
|
f05d5d9766 | ||
|
|
aacc243bae | ||
|
|
396dc3e915 | ||
|
|
d3b2d0fce8 | ||
|
|
4d4cd4c2d6 | ||
|
|
72512da3b5 | ||
|
|
c2ed98eb85 | ||
|
|
bebe130fb0 | ||
|
|
db065584fa | ||
|
|
844f25ed98 | ||
|
|
546f668301 | ||
|
|
a79753d0a5 | ||
|
|
32c44cdfe3 | ||
|
|
de5dbea69f | ||
|
|
3f896751f3 | ||
|
|
41f5beb619 | ||
|
|
5e5535653b | ||
|
|
af50d90bcb | ||
|
|
c5a4c53a1a | ||
|
|
016940f2ce | ||
|
|
e877695a14 | ||
|
|
e2256e28ba | ||
|
|
5c08083336 | ||
|
|
07b8732a31 | ||
|
|
3e49d05ef9 | ||
|
|
83cc9835e8 | ||
|
|
c5af8bdcd7 | ||
|
|
55b21c2add | ||
|
|
b87fe94a92 | ||
|
|
493de4c190 | ||
|
|
8f99ed2478 | ||
|
|
cdcbdc60fb | ||
|
|
e1bbbd6e9c | ||
|
|
84de8ad252 | ||
|
|
57bcfbbe29 | ||
|
|
32656c1cb8 | ||
|
|
5572cece83 | ||
|
|
08675e6713 | ||
|
|
abee109dbd | ||
|
|
ef27730e5e | ||
|
|
10c6708db5 | ||
|
|
a618aed415 | ||
|
|
8e8e0faa9e | ||
|
|
41ce5086e7 | ||
|
|
a79b71cff6 | ||
|
|
f0318b0c84 | ||
|
|
814acfa74a | ||
|
|
d73276c136 | ||
|
|
44b58280e8 | ||
|
|
49a05c412c | ||
|
|
f57bbc2b52 | ||
|
|
e620120144 | ||
|
|
6fbe95a334 | ||
|
|
cb4dcc81ea | ||
|
|
3126c8d1a8 | ||
|
|
0d7814c778 | ||
|
|
f70ea71885 | ||
|
|
04df20a732 | ||
|
|
c7b556e748 | ||
|
|
8314a5486d | ||
|
|
e80ef7c1dc | ||
|
|
f1a928994a | ||
|
|
0f594704d5 | ||
|
|
3064415068 | ||
|
|
f03ef66596 | ||
|
|
0617e3ec7f | ||
|
|
dacaf4e394 | ||
|
|
e6566b910a | ||
|
|
778f839e8e | ||
|
|
52711c5cc1 | ||
|
|
d8687b5985 | ||
|
|
19ad237427 | ||
|
|
bb246144c2 | ||
|
|
fa01b33dfa | ||
|
|
00780442dd | ||
|
|
5b170d02eb | ||
|
|
db4dc89e42 | ||
|
|
b5b606d486 | ||
|
|
f2c7c41117 | ||
|
|
152e194655 | ||
|
|
f12ba96389 | ||
|
|
add3296071 | ||
|
|
a90b85c2a6 | ||
|
|
3568d4a780 | ||
|
|
d3a5a5e669 | ||
|
|
fc77a52c46 | ||
|
|
5617331598 | ||
|
|
76a7a266ff | ||
|
|
b6c60b26cd | ||
|
|
30d8303320 | ||
|
|
5c12a4b205 | ||
|
|
b830781e48 | ||
|
|
8329dedd7f | ||
|
|
70e04a1c99 | ||
|
|
768b95d3a9 | ||
|
|
8e4ee5df3d | ||
|
|
baa2a7fed3 | ||
|
|
1f16294d7e | ||
|
|
679266c0b7 | ||
|
|
f1e96f7812 | ||
|
|
f8e6ccea23 | ||
|
|
8f46052459 | ||
|
|
b210d83210 | ||
|
|
eaf8d68dd1 | ||
|
|
c4ebee8e8d | ||
|
|
2962306094 | ||
|
|
a4c0365a95 | ||
|
|
2abc4d542b | ||
|
|
700b9bf348 | ||
|
|
9924f87473 | ||
|
|
f19b600287 | ||
|
|
14200a5011 | ||
|
|
fffb692ca8 | ||
|
|
de7d65fc8b | ||
|
|
e73421dabb | ||
|
|
421fe11664 | ||
|
|
1dcd048268 | ||
|
|
12511922ad | ||
|
|
1782239c7c | ||
|
|
752cf8ab16 | ||
|
|
c512a11e7e | ||
|
|
ba27230e3c | ||
|
|
52f7cac0a9 | ||
|
|
2db42e3eb0 |
265
.github/codeql/queries/unique-command-use.ql
vendored
265
.github/codeql/queries/unique-command-use.ql
vendored
@@ -15,134 +15,145 @@
|
||||
* that should be changed to fix the alert.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* The name of a VS Code command.
|
||||
*/
|
||||
class CommandName extends string {
|
||||
CommandName() { exists(CommandUsage e | e.getCommandName() = this) }
|
||||
|
||||
/**
|
||||
* In how many ways is this command used. Will always be at least 1.
|
||||
*/
|
||||
int getNumberOfUsages() { result = count(this.getAUse()) }
|
||||
/**
|
||||
* The name of a VS Code command.
|
||||
*/
|
||||
class CommandName extends string {
|
||||
CommandName() { exists(CommandUsage e | e.getCommandName() = this) }
|
||||
|
||||
/**
|
||||
* Get a usage of this command.
|
||||
*/
|
||||
CommandUsage getAUse() { result.getCommandName() = this }
|
||||
|
||||
/**
|
||||
* Get the canonical first usage of this command, to use for the location
|
||||
* of the alert. The implementation of this ordering of usages is arbitrary
|
||||
* and the usage given may not be the one that should be changed when fixing
|
||||
* the alert.
|
||||
*/
|
||||
CommandUsage getFirstUsage() {
|
||||
result =
|
||||
max(CommandUsage use |
|
||||
use = this.getAUse()
|
||||
|
|
||||
use
|
||||
order by
|
||||
use.getFile().getRelativePath(), use.getLocation().getStartLine(),
|
||||
use.getLocation().getStartColumn()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single usage of a command, either from within code or
|
||||
* from the command's definition in package.json
|
||||
*/
|
||||
abstract class CommandUsage extends Locatable {
|
||||
abstract string getCommandName();
|
||||
}
|
||||
|
||||
/**
|
||||
* A usage of a command from the typescript code, by calling `executeCommand`.
|
||||
*/
|
||||
class CommandUsageCallExpr extends CommandUsage, CallExpr {
|
||||
CommandUsageCallExpr() {
|
||||
this.getCalleeName() = "executeCommand" and
|
||||
this.getArgument(0).(StringLiteral).getValue().matches("%codeQL%") and
|
||||
not this.getFile().getRelativePath().matches("extensions/ql-vscode/test/%")
|
||||
}
|
||||
|
||||
override string getCommandName() { result = this.getArgument(0).(StringLiteral).getValue() }
|
||||
}
|
||||
/**
|
||||
* In how many ways is this command used. Will always be at least 1.
|
||||
*/
|
||||
int getNumberOfUsages() { result = count(this.getAUse()) }
|
||||
|
||||
/**
|
||||
* A usage of a command from the typescript code, by calling `CommandManager.execute`.
|
||||
*/
|
||||
class CommandUsageCommandManagerMethodCallExpr extends CommandUsage, MethodCallExpr {
|
||||
CommandUsageCommandManagerMethodCallExpr() {
|
||||
this.getCalleeName() = "execute" and
|
||||
this.getReceiver().getType().unfold().(TypeReference).getTypeName().getName() = "CommandManager" and
|
||||
this.getArgument(0).(StringLiteral).getValue().matches("%codeQL%") and
|
||||
not this.getFile().getRelativePath().matches("extensions/ql-vscode/test/%")
|
||||
}
|
||||
/**
|
||||
* Get a usage of this command.
|
||||
*/
|
||||
CommandUsage getAUse() { result.getCommandName() = this }
|
||||
|
||||
override string getCommandName() { result = this.getArgument(0).(StringLiteral).getValue() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A usage of a command from any menu that isn't the command palette.
|
||||
* This means a user could invoke the command by clicking on a button in
|
||||
* something like a menu or a dropdown.
|
||||
*/
|
||||
class CommandUsagePackageJsonMenuItem extends CommandUsage, JsonObject {
|
||||
CommandUsagePackageJsonMenuItem() {
|
||||
exists(this.getPropValue("command")) and
|
||||
exists(PackageJson packageJson, string menuName |
|
||||
packageJson
|
||||
.getPropValue("contributes")
|
||||
.getPropValue("menus")
|
||||
.getPropValue(menuName)
|
||||
.getElementValue(_) = this and
|
||||
menuName != "commandPalette"
|
||||
)
|
||||
}
|
||||
|
||||
override string getCommandName() { result = this.getPropValue("command").getStringValue() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the given command disabled for use in the command palette by
|
||||
* a block with a `"when": "false"` field.
|
||||
*/
|
||||
predicate isDisabledInCommandPalette(string commandName) {
|
||||
exists(PackageJson packageJson, JsonObject commandPaletteObject |
|
||||
packageJson
|
||||
.getPropValue("contributes")
|
||||
.getPropValue("menus")
|
||||
.getPropValue("commandPalette")
|
||||
.getElementValue(_) = commandPaletteObject and
|
||||
commandPaletteObject.getPropValue("command").getStringValue() = commandName and
|
||||
commandPaletteObject.getPropValue("when").getStringValue() = "false"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a command being usable from the command palette.
|
||||
* This means that a user could choose to manually invoke the command.
|
||||
*/
|
||||
class CommandUsagePackageJsonCommandPalette extends CommandUsage, JsonObject {
|
||||
CommandUsagePackageJsonCommandPalette() {
|
||||
this.getFile().getBaseName() = "package.json" and
|
||||
exists(this.getPropValue("command")) and
|
||||
exists(PackageJson packageJson |
|
||||
packageJson.getPropValue("contributes").getPropValue("commands").getElementValue(_) = this
|
||||
) and
|
||||
not isDisabledInCommandPalette(this.getPropValue("command").getStringValue())
|
||||
}
|
||||
|
||||
override string getCommandName() { result = this.getPropValue("command").getStringValue() }
|
||||
}
|
||||
|
||||
from CommandName c
|
||||
where c.getNumberOfUsages() > 1
|
||||
select c.getFirstUsage(),
|
||||
"The " + c + " command is used from " + c.getNumberOfUsages() + " locations"
|
||||
|
||||
/**
|
||||
* Get the canonical first usage of this command, to use for the location
|
||||
* of the alert. The implementation of this ordering of usages is arbitrary
|
||||
* and the usage given may not be the one that should be changed when fixing
|
||||
* the alert.
|
||||
*/
|
||||
CommandUsage getFirstUsage() {
|
||||
result =
|
||||
max(CommandUsage use |
|
||||
use = this.getAUse()
|
||||
|
|
||||
use
|
||||
order by
|
||||
use.getFile().getRelativePath(), use.getLocation().getStartLine(),
|
||||
use.getLocation().getStartColumn()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches one of the members of `BuiltInVsCodeCommands` from `extensions/ql-vscode/src/common/commands.ts`.
|
||||
*/
|
||||
class BuiltInVSCodeCommand extends string {
|
||||
BuiltInVSCodeCommand() {
|
||||
exists(TypeAliasDeclaration tad |
|
||||
tad.getIdentifier().getName() = "BuiltInVsCodeCommands" and
|
||||
tad.getDefinition().(InterfaceTypeExpr).getAMember().getName() = this
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single usage of a command, either from within code or
|
||||
* from the command's definition in package.json
|
||||
*/
|
||||
abstract class CommandUsage extends Locatable {
|
||||
abstract string getCommandName();
|
||||
}
|
||||
|
||||
/**
|
||||
* A usage of a command from the typescript code, by calling `executeCommand`.
|
||||
*/
|
||||
class CommandUsageCallExpr extends CommandUsage, CallExpr {
|
||||
CommandUsageCallExpr() {
|
||||
this.getCalleeName() = "executeCommand" and
|
||||
this.getArgument(0).(StringLiteral).getValue().matches("%codeQL%") and
|
||||
not this.getFile().getRelativePath().matches("extensions/ql-vscode/test/%")
|
||||
}
|
||||
|
||||
override string getCommandName() { result = this.getArgument(0).(StringLiteral).getValue() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A usage of a command from the typescript code, by calling `CommandManager.execute`.
|
||||
*/
|
||||
class CommandUsageCommandManagerMethodCallExpr extends CommandUsage, MethodCallExpr {
|
||||
CommandUsageCommandManagerMethodCallExpr() {
|
||||
this.getCalleeName() = "execute" and
|
||||
this.getReceiver().getType().unfold().(TypeReference).getTypeName().getName() = "CommandManager" and
|
||||
this.getArgument(0).(StringLiteral).getValue().matches("%codeQL%") and
|
||||
not this.getFile().getRelativePath().matches("extensions/ql-vscode/test/%")
|
||||
}
|
||||
|
||||
override string getCommandName() { result = this.getArgument(0).(StringLiteral).getValue() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A usage of a command from any menu that isn't the command palette.
|
||||
* This means a user could invoke the command by clicking on a button in
|
||||
* something like a menu or a dropdown.
|
||||
*/
|
||||
class CommandUsagePackageJsonMenuItem extends CommandUsage, JsonObject {
|
||||
CommandUsagePackageJsonMenuItem() {
|
||||
exists(this.getPropValue("command")) and
|
||||
exists(PackageJson packageJson, string menuName |
|
||||
packageJson
|
||||
.getPropValue("contributes")
|
||||
.getPropValue("menus")
|
||||
.getPropValue(menuName)
|
||||
.getElementValue(_) = this and
|
||||
menuName != "commandPalette"
|
||||
)
|
||||
}
|
||||
|
||||
override string getCommandName() { result = this.getPropValue("command").getStringValue() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the given command disabled for use in the command palette by
|
||||
* a block with a `"when": "false"` field.
|
||||
*/
|
||||
predicate isDisabledInCommandPalette(string commandName) {
|
||||
exists(PackageJson packageJson, JsonObject commandPaletteObject |
|
||||
packageJson
|
||||
.getPropValue("contributes")
|
||||
.getPropValue("menus")
|
||||
.getPropValue("commandPalette")
|
||||
.getElementValue(_) = commandPaletteObject and
|
||||
commandPaletteObject.getPropValue("command").getStringValue() = commandName and
|
||||
commandPaletteObject.getPropValue("when").getStringValue() = "false"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a command being usable from the command palette.
|
||||
* This means that a user could choose to manually invoke the command.
|
||||
*/
|
||||
class CommandUsagePackageJsonCommandPalette extends CommandUsage, JsonObject {
|
||||
CommandUsagePackageJsonCommandPalette() {
|
||||
this.getFile().getBaseName() = "package.json" and
|
||||
exists(this.getPropValue("command")) and
|
||||
exists(PackageJson packageJson |
|
||||
packageJson.getPropValue("contributes").getPropValue("commands").getElementValue(_) = this
|
||||
) and
|
||||
not isDisabledInCommandPalette(this.getPropValue("command").getStringValue())
|
||||
}
|
||||
|
||||
override string getCommandName() { result = this.getPropValue("command").getStringValue() }
|
||||
}
|
||||
|
||||
from CommandName c
|
||||
where c.getNumberOfUsages() > 1 and not c instanceof BuiltInVSCodeCommand
|
||||
select c.getFirstUsage(),
|
||||
"The " + c + " command is used from " + c.getNumberOfUsages() + " locations"
|
||||
|
||||
5
.github/workflows/main.yml
vendored
5
.github/workflows/main.yml
vendored
@@ -110,6 +110,11 @@ jobs:
|
||||
run: |
|
||||
npm run lint:scenarios
|
||||
|
||||
- name: Find deadcode
|
||||
working-directory: extensions/ql-vscode
|
||||
run: |
|
||||
npm run find-deadcode
|
||||
|
||||
unit-test:
|
||||
name: Unit Test
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
@@ -44,21 +44,21 @@ choose to go through some of the Optional Test Cases.
|
||||
|
||||
#### Test case 2: Running a problem query and viewing results
|
||||
|
||||
1. Open the [javascript UnsafeJQueryPlugin query](https://github.com/github/codeql/blob/main/javascript/ql/src/Security/CWE-079/UnsafeJQueryPlugin.ql).
|
||||
1. Open the [javascript ReDoS query](https://github.com/github/codeql/blob/main/javascript/ql/src/Performance/ReDoS.ql).
|
||||
2. Select the `babel/babel` database (or download it if you don't have one already)
|
||||
3. Run a local query.
|
||||
4. Once the query completes:
|
||||
- Check that the result messages are rendered
|
||||
- Check that alert locations can be clicked on
|
||||
|
||||
#### Test case 3: Running a non-probem query and viewing results
|
||||
#### Test case 3: Running a non-problem query and viewing results
|
||||
|
||||
1. Open the [cpp FunLinesOfCode query](https://github.com/github/codeql/blob/main/cpp/ql/src/Metrics/Functions/FunLinesOfCode.ql).
|
||||
2. Select the `google/brotli` database (or download it if you don't have one already)
|
||||
3. Run a local query.
|
||||
4. Once the query completes:
|
||||
- Check that the results table is rendered
|
||||
- Check that alert locations can be clicked on
|
||||
- Check that result locations can be clicked on
|
||||
|
||||
#### Test case 3: Can use AST viewer
|
||||
|
||||
@@ -318,7 +318,6 @@ This requires running a MRVA query and seeing the results view.
|
||||
1. Alphabetically
|
||||
2. By number of results
|
||||
3. By popularity
|
||||
4. By most recent commit
|
||||
9. Can filter repos
|
||||
10. Shows correct statistics
|
||||
1. Total number of results
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { StorybookConfig } from "@storybook/core-common";
|
||||
import type { StorybookConfig } from "@storybook/react-webpack5";
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
|
||||
@@ -8,13 +8,13 @@ const config: StorybookConfig = {
|
||||
"@storybook/addon-interactions",
|
||||
"./vscode-theme-addon/preset.ts",
|
||||
],
|
||||
framework: "@storybook/react",
|
||||
core: {
|
||||
builder: "@storybook/builder-webpack5",
|
||||
framework: {
|
||||
name: "@storybook/react-webpack5",
|
||||
options: {},
|
||||
},
|
||||
features: {
|
||||
babelModeV7: true,
|
||||
docs: {
|
||||
autodocs: "tag",
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
export default config;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { addons } from "@storybook/addons";
|
||||
import { addons } from "@storybook/manager-api";
|
||||
import { themes } from "@storybook/theming";
|
||||
|
||||
addons.setConfig({
|
||||
|
||||
@@ -1,31 +1,36 @@
|
||||
import { Preview } from "@storybook/react";
|
||||
import { themes } from "@storybook/theming";
|
||||
import { action } from "@storybook/addon-actions";
|
||||
|
||||
// Allow all stories/components to use Codicons
|
||||
import "@vscode/codicons/dist/codicon.css";
|
||||
|
||||
// https://storybook.js.org/docs/react/configure/overview#configure-story-rendering
|
||||
export const parameters = {
|
||||
// All props starting with `on` will automatically receive an action as a prop
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
// All props matching these names will automatically get the correct control
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/,
|
||||
},
|
||||
},
|
||||
// Use a dark theme to be aligned with VSCode
|
||||
docs: {
|
||||
theme: themes.dark,
|
||||
},
|
||||
backgrounds: {
|
||||
// The background is injected by our theme CSS files
|
||||
disable: true,
|
||||
},
|
||||
};
|
||||
|
||||
(window as any).acquireVsCodeApi = () => ({
|
||||
postMessage: action("post-vscode-message"),
|
||||
setState: action("set-vscode-state"),
|
||||
});
|
||||
|
||||
// https://storybook.js.org/docs/react/configure/overview#configure-story-rendering
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
// All props starting with `on` will automatically receive an action as a prop
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
// All props matching these names will automatically get the correct control
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/,
|
||||
},
|
||||
},
|
||||
// Use a dark theme to be aligned with VSCode
|
||||
docs: {
|
||||
theme: themes.dark,
|
||||
},
|
||||
backgrounds: {
|
||||
// The background is injected by our theme CSS files
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default preview;
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import * as React from "react";
|
||||
import { FunctionComponent, useCallback } from "react";
|
||||
|
||||
import { useGlobals } from "@storybook/api";
|
||||
import { useGlobals } from "@storybook/manager-api";
|
||||
import {
|
||||
IconButton,
|
||||
Icons,
|
||||
WithTooltip,
|
||||
TooltipLinkList,
|
||||
Link,
|
||||
WithHideFn,
|
||||
WithTooltip,
|
||||
} from "@storybook/components";
|
||||
|
||||
import { themeNames, VSCodeTheme } from "./theme";
|
||||
@@ -26,7 +24,7 @@ export const ThemeSelector: FunctionComponent = () => {
|
||||
);
|
||||
|
||||
const createLinks = useCallback(
|
||||
(onHide: () => void): Link[] =>
|
||||
(onHide: () => void) =>
|
||||
Object.values(VSCodeTheme).map((theme) => ({
|
||||
id: theme,
|
||||
onClick() {
|
||||
@@ -44,8 +42,8 @@ export const ThemeSelector: FunctionComponent = () => {
|
||||
<WithTooltip
|
||||
placement="top"
|
||||
trigger="click"
|
||||
closeOnClick
|
||||
tooltip={({ onHide }: WithHideFn) => (
|
||||
closeOnOutsideClick
|
||||
tooltip={({ onHide }: { onHide: () => void }) => (
|
||||
<TooltipLinkList links={createLinks(onHide)} />
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { addons, types } from "@storybook/addons";
|
||||
import { addons, types } from "@storybook/manager-api";
|
||||
import { ThemeSelector } from "./ThemeSelector";
|
||||
|
||||
const ADDON_ID = "vscode-theme-addon";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export function config(entry = []) {
|
||||
export function previewAnnotations(entry = []) {
|
||||
return [...entry, require.resolve("./preview.ts")];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useEffect, useGlobals } from "@storybook/addons";
|
||||
import { useEffect } from "react";
|
||||
import type {
|
||||
AnyFramework,
|
||||
PartialStoryFn as StoryFunction,
|
||||
StoryContext,
|
||||
} from "@storybook/csf";
|
||||
@@ -34,11 +33,8 @@ const themeFiles: { [key in VSCodeTheme]: string } = {
|
||||
.default,
|
||||
};
|
||||
|
||||
export const withTheme = (
|
||||
StoryFn: StoryFunction<AnyFramework>,
|
||||
context: StoryContext<AnyFramework>,
|
||||
) => {
|
||||
const [{ vscodeTheme }] = useGlobals();
|
||||
export const withTheme = (StoryFn: StoryFunction, context: StoryContext) => {
|
||||
const { vscodeTheme } = context.globals;
|
||||
|
||||
useEffect(() => {
|
||||
const styleSelectorId =
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# CodeQL for Visual Studio Code: Changelog
|
||||
|
||||
## 1.8.9 - 3 August 2023
|
||||
|
||||
- Remove "last updated" information and sorting from variant analysis results view. [#2637](https://github.com/github/vscode-codeql/pull/2637)
|
||||
- Links to code on GitHub now include column numbers as well as line numbers. [#2406](https://github.com/github/vscode-codeql/pull/2406)
|
||||
- No longer highlight trailing commas for jump to definition. [#2615](https://github.com/github/vscode-codeql/pull/2615)
|
||||
|
||||
## 1.8.8 - 17 July 2023
|
||||
|
||||
- Remove support for CodeQL CLI versions older than 2.9.4. [#2610](https://github.com/github/vscode-codeql/pull/2610)
|
||||
|
||||
@@ -15,9 +15,6 @@ export const config: webpack.Configuration = {
|
||||
devtool: isDevBuild ? "inline-source-map" : "source-map",
|
||||
resolve: {
|
||||
extensions: [".js", ".ts", ".tsx", ".json"],
|
||||
fallback: {
|
||||
path: require.resolve("path-browserify"),
|
||||
},
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
|
||||
@@ -30,5 +30,5 @@
|
||||
"end": "^\\s*//\\s*#?endregion\\b"
|
||||
}
|
||||
},
|
||||
"wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\.\\<\\>\\/\\?\\s]+)"
|
||||
"wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\.\\<\\>\\/\\?\\s\\,]+)"
|
||||
}
|
||||
|
||||
45825
extensions/ql-vscode/package-lock.json
generated
45825
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.8.8",
|
||||
"version": "1.8.9",
|
||||
"publisher": "GitHub",
|
||||
"license": "MIT",
|
||||
"icon": "media/VS-marketplace-CodeQL-icon.png",
|
||||
@@ -350,13 +350,11 @@
|
||||
"enum": [
|
||||
"alphabetically",
|
||||
"popularity",
|
||||
"mostRecentCommit",
|
||||
"numberOfResults"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Sort repositories alphabetically in the results view.",
|
||||
"Sort repositories by popularity in the results view.",
|
||||
"Sort repositories by most recent commit in the results view.",
|
||||
"Sort repositories by number of results in the results view."
|
||||
],
|
||||
"description": "The default sorting order for repositories in the variant analysis results view."
|
||||
@@ -1077,17 +1075,17 @@
|
||||
{
|
||||
"command": "codeQLQueryHistory.showEvalLog",
|
||||
"group": "4_queryHistory@1",
|
||||
"when": "codeql.supportsEvalLog && viewItem == rawResultsItem || codeql.supportsEvalLog && viewItem == interpretedResultsItem || codeql.supportsEvalLog && viewItem == cancelledResultsItem"
|
||||
"when": "viewItem == rawResultsItem || viewItem == interpretedResultsItem || viewItem == cancelledResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showEvalLogSummary",
|
||||
"group": "4_queryHistory@2",
|
||||
"when": "codeql.supportsEvalLog && viewItem == rawResultsItem || codeql.supportsEvalLog && viewItem == interpretedResultsItem || codeql.supportsEvalLog && viewItem == cancelledResultsItem"
|
||||
"when": "viewItem == rawResultsItem || viewItem == interpretedResultsItem || viewItem == cancelledResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showEvalLogViewer",
|
||||
"group": "4_queryHistory@3",
|
||||
"when": "config.codeQL.canary && codeql.supportsEvalLog && viewItem == rawResultsItem || config.codeQL.canary && codeql.supportsEvalLog && viewItem == interpretedResultsItem || config.codeQL.canary && codeql.supportsEvalLog && viewItem == cancelledResultsItem"
|
||||
"when": "config.codeQL.canary && viewItem == rawResultsItem || config.codeQL.canary && viewItem == interpretedResultsItem || config.codeQL.canary && viewItem == cancelledResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showQueryText",
|
||||
@@ -1728,13 +1726,15 @@
|
||||
"test:vscode-integration:no-workspace": "jest --projects test/vscode-tests/no-workspace",
|
||||
"test:vscode-integration:minimal-workspace": "jest --projects test/vscode-tests/minimal-workspace",
|
||||
"test:cli-integration": "jest --projects test/vscode-tests/cli-integration --verbose",
|
||||
"clean-test-dir": "find . -type d -name .vscode-test -exec rm -r {} +",
|
||||
"update-vscode": "node ./node_modules/vscode/bin/install",
|
||||
"format": "prettier --write **/*.{ts,tsx} && eslint . --ext .ts,.tsx --fix",
|
||||
"lint": "eslint . --ext .js,.ts,.tsx --max-warnings=0",
|
||||
"lint:markdown": "markdownlint-cli2 \"../../**/*.{md,mdx}\" \"!**/node_modules/**\" \"!**/.vscode-test/**\" \"!**/build/cli/v*/**\"",
|
||||
"find-deadcode": "ts-node scripts/find-deadcode.ts",
|
||||
"format-staged": "lint-staged",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"build-storybook": "build-storybook",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build",
|
||||
"lint:scenarios": "ts-node scripts/lint-scenarios.ts",
|
||||
"check-types": "find . -type f -name \"tsconfig.json\" -not -path \"./node_modules/*\" | sed -r 's|/[^/]+$||' | sort | uniq | xargs -I {} sh -c \"echo Checking types in {} && cd {} && npx tsc --noEmit\"",
|
||||
"postinstall": "patch-package",
|
||||
@@ -1750,59 +1750,63 @@
|
||||
"ajv": "^8.11.0",
|
||||
"child-process-promise": "^2.2.1",
|
||||
"chokidar": "^3.5.3",
|
||||
"classnames": "~2.2.6",
|
||||
"classnames": "^2.2.6",
|
||||
"d3": "^7.6.1",
|
||||
"d3-graphviz": "^5.0.2",
|
||||
"fs-extra": "^11.1.1",
|
||||
"immutable": "^4.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"minimatch": "^9.0.0",
|
||||
"minimist": "~1.2.6",
|
||||
"minimist": "^1.2.6",
|
||||
"msw": "^1.2.0",
|
||||
"nanoid": "^3.2.0",
|
||||
"node-fetch": "~2.6.7",
|
||||
"node-fetch": "^2.6.7",
|
||||
"p-queue": "^6.0.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"semver": "~7.5.2",
|
||||
"semver": "^7.5.2",
|
||||
"source-map": "^0.7.4",
|
||||
"source-map-support": "^0.5.21",
|
||||
"stream": "^0.0.2",
|
||||
"stream-chain": "~2.2.4",
|
||||
"stream-json": "~1.7.3",
|
||||
"stream-chain": "^2.2.4",
|
||||
"stream-json": "^1.7.3",
|
||||
"styled-components": "^5.3.3",
|
||||
"tmp": "^0.1.0",
|
||||
"tmp-promise": "~3.0.2",
|
||||
"tree-kill": "~1.2.2",
|
||||
"unzipper": "~0.10.5",
|
||||
"tmp-promise": "^3.0.2",
|
||||
"tree-kill": "^1.2.2",
|
||||
"unzipper": "^0.10.5",
|
||||
"vscode-extension-telemetry": "^0.1.6",
|
||||
"vscode-jsonrpc": "^8.0.2",
|
||||
"vscode-languageclient": "^8.0.2",
|
||||
"vscode-test-adapter-api": "~1.7.0",
|
||||
"vscode-test-adapter-util": "~0.7.0",
|
||||
"zip-a-folder": "~2.0.0"
|
||||
"vscode-test-adapter-api": "^1.7.0",
|
||||
"vscode-test-adapter-util": "^0.7.0",
|
||||
"zip-a-folder": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.13",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.18.6",
|
||||
"@babel/preset-env": "^7.21.4",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@babel/preset-typescript": "^7.21.4",
|
||||
"@faker-js/faker": "^8.0.2",
|
||||
"@github/markdownlint-github": "^0.3.0",
|
||||
"@octokit/plugin-throttling": "^5.0.1",
|
||||
"@storybook/addon-actions": "^6.5.17-alpha.0",
|
||||
"@storybook/addon-essentials": "^6.5.17-alpha.0",
|
||||
"@storybook/addon-interactions": "^6.5.17-alpha.0",
|
||||
"@storybook/addon-links": "^6.5.17-alpha.0",
|
||||
"@storybook/builder-webpack5": "^6.5.17-alpha.0",
|
||||
"@storybook/manager-webpack5": "^6.5.17-alpha.0",
|
||||
"@storybook/react": "^6.5.17-alpha.0",
|
||||
"@storybook/testing-library": "^0.0.13",
|
||||
"@storybook/addon-actions": "^7.1.0",
|
||||
"@storybook/addon-essentials": "^7.1.0",
|
||||
"@storybook/addon-interactions": "^7.1.0",
|
||||
"@storybook/addon-links": "^7.1.0",
|
||||
"@storybook/components": "^7.1.0",
|
||||
"@storybook/csf": "^0.1.1",
|
||||
"@storybook/manager-api": "^7.1.0",
|
||||
"@storybook/react": "^7.1.0",
|
||||
"@storybook/react-webpack5": "^7.1.0",
|
||||
"@storybook/theming": "^7.1.0",
|
||||
"@testing-library/dom": "^9.3.0",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@types/child-process-promise": "^2.2.1",
|
||||
"@types/classnames": "~2.2.9",
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@types/d3": "^7.4.0",
|
||||
"@types/d3-graphviz": "^2.6.6",
|
||||
"@types/del": "^4.0.0",
|
||||
@@ -1813,21 +1817,21 @@
|
||||
"@types/gulp-sourcemaps": "0.0.32",
|
||||
"@types/jest": "^29.0.2",
|
||||
"@types/js-yaml": "^3.12.5",
|
||||
"@types/jszip": "~3.1.6",
|
||||
"@types/jszip": "^3.1.6",
|
||||
"@types/nanoid": "^3.0.0",
|
||||
"@types/node": "^16.11.25",
|
||||
"@types/node-fetch": "~2.5.2",
|
||||
"@types/node-fetch": "^2.5.2",
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/sarif": "~2.1.2",
|
||||
"@types/semver": "~7.2.0",
|
||||
"@types/stream-chain": "~2.0.1",
|
||||
"@types/stream-json": "~1.7.1",
|
||||
"@types/sarif": "^2.1.2",
|
||||
"@types/semver": "^7.2.0",
|
||||
"@types/stream-chain": "^2.0.1",
|
||||
"@types/stream-json": "^1.7.1",
|
||||
"@types/styled-components": "^5.1.11",
|
||||
"@types/tar-stream": "^2.2.2",
|
||||
"@types/through2": "^2.0.36",
|
||||
"@types/tmp": "^0.1.0",
|
||||
"@types/unzipper": "~0.10.1",
|
||||
"@types/unzipper": "^0.10.1",
|
||||
"@types/vscode": "^1.67.0",
|
||||
"@types/webpack": "^5.28.0",
|
||||
"@types/webpack-env": "^1.18.0",
|
||||
@@ -1837,8 +1841,9 @@
|
||||
"@vscode/vsce": "^2.19.0",
|
||||
"ansi-colors": "^4.1.1",
|
||||
"applicationinsights": "^2.3.5",
|
||||
"cosmiconfig": "^7.1.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "~6.8.1",
|
||||
"css-loader": "^6.8.1",
|
||||
"del": "^6.0.0",
|
||||
"esbuild": "^0.15.15",
|
||||
"eslint": "^8.23.1",
|
||||
@@ -1861,13 +1866,14 @@
|
||||
"jest": "^29.0.3",
|
||||
"jest-environment-jsdom": "^29.0.3",
|
||||
"jest-runner-vscode": "^3.0.1",
|
||||
"lint-staged": "~13.2.0",
|
||||
"lint-staged": "^13.2.0",
|
||||
"markdownlint-cli2": "^0.6.0",
|
||||
"markdownlint-cli2-formatter-pretty": "^0.0.4",
|
||||
"mini-css-extract-plugin": "^2.6.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"patch-package": "^7.0.0",
|
||||
"prettier": "^3.0.0",
|
||||
"storybook": "^7.1.0",
|
||||
"tar-stream": "^3.0.0",
|
||||
"through2": "^4.0.2",
|
||||
"ts-jest": "^29.0.1",
|
||||
@@ -1875,6 +1881,7 @@
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.7.0",
|
||||
"ts-protoc-gen": "^0.9.0",
|
||||
"ts-unused-exports": "^9.0.5",
|
||||
"typescript": "^5.0.2",
|
||||
"webpack": "^5.76.0",
|
||||
"webpack-cli": "^5.0.1"
|
||||
|
||||
47
extensions/ql-vscode/scripts/find-deadcode.ts
Normal file
47
extensions/ql-vscode/scripts/find-deadcode.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { basename, join, relative, resolve } from "path";
|
||||
import analyzeTsConfig from "ts-unused-exports";
|
||||
import { containsPath, pathsEqual } from "../src/common/files";
|
||||
import { exit } from "process";
|
||||
|
||||
function ignoreFile(file: string): boolean {
|
||||
return (
|
||||
containsPath("gulpfile.ts", file) ||
|
||||
containsPath(join("src", "stories"), file) ||
|
||||
pathsEqual(
|
||||
join("test", "vscode-tests", "jest-runner-installed-extensions.ts"),
|
||||
file,
|
||||
) ||
|
||||
basename(file) === "jest.config.ts" ||
|
||||
basename(file) === "index.tsx" ||
|
||||
basename(file) === "index.ts"
|
||||
);
|
||||
}
|
||||
|
||||
function main() {
|
||||
const repositoryRoot = resolve(join(__dirname, ".."));
|
||||
|
||||
const result = analyzeTsConfig("tsconfig.deadcode.json");
|
||||
let foundUnusedExports = false;
|
||||
|
||||
for (const [filepath, exportNameAndLocations] of Object.entries(result)) {
|
||||
const relativeFilepath = relative(repositoryRoot, filepath);
|
||||
|
||||
if (ignoreFile(relativeFilepath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foundUnusedExports = true;
|
||||
|
||||
console.log(relativeFilepath);
|
||||
for (const exportNameAndLocation of exportNameAndLocations) {
|
||||
console.log(` ${exportNameAndLocation.exportName}`);
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
if (foundUnusedExports) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1428,21 +1428,13 @@ export class CodeQLCliServer implements Disposable {
|
||||
|
||||
async packPacklist(dir: string, includeQueries: boolean): Promise<string[]> {
|
||||
const args = includeQueries ? [dir] : ["--no-include-queries", dir];
|
||||
// since 2.7.1, packlist returns an object with a "paths" property that is a list of packs.
|
||||
// previous versions return a list of packs.
|
||||
const results: { paths: string[] } | string[] =
|
||||
await this.runJsonCodeQlCliCommand(
|
||||
["pack", "packlist"],
|
||||
args,
|
||||
"Generating the pack list",
|
||||
);
|
||||
const results: { paths: string[] } = await this.runJsonCodeQlCliCommand(
|
||||
["pack", "packlist"],
|
||||
args,
|
||||
"Generating the pack list",
|
||||
);
|
||||
|
||||
// Once we no longer need to support 2.7.0 or earlier, we can remove this and assume all versions return an object.
|
||||
if ("paths" in results) {
|
||||
return results.paths;
|
||||
} else {
|
||||
return results;
|
||||
}
|
||||
return results.paths;
|
||||
}
|
||||
|
||||
async packResolveDependencies(
|
||||
@@ -1476,13 +1468,6 @@ export class CodeQLCliServer implements Disposable {
|
||||
);
|
||||
|
||||
// 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.supportsEvalLog",
|
||||
newVersion.compare(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_PER_QUERY_EVAL_LOG,
|
||||
) >= 0,
|
||||
);
|
||||
await this.app.commands.execute(
|
||||
"setContext",
|
||||
"codeql.supportsQuickEvalCount",
|
||||
@@ -1807,21 +1792,9 @@ export class CliVersionConstraint {
|
||||
);
|
||||
|
||||
/**
|
||||
* CLI version where the `--evaluator-log` and related options to the query server were introduced,
|
||||
* on a per-query server basis.
|
||||
* CLI version where the `resolve extensions` subcommand exists.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_STRUCTURED_EVAL_LOG = new SemVer("2.8.2");
|
||||
|
||||
/**
|
||||
* CLI version that supports rotating structured logs to produce one per query.
|
||||
*
|
||||
* Note that 2.8.4 supports generating the evaluation logs and summaries,
|
||||
* but 2.9.0 includes a new option to produce the end-of-query summary logs to
|
||||
* the query server console. For simplicity we gate all features behind 2.9.0,
|
||||
* but if a user is tied to the 2.8 release, we can enable evaluator logs
|
||||
* and summaries for them.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_PER_QUERY_EVAL_LOG = new SemVer("2.9.0");
|
||||
public static CLI_VERSION_WITH_RESOLVE_EXTENSIONS = new SemVer("2.10.2");
|
||||
|
||||
/**
|
||||
* CLI version that supports the `--sourcemap` option for log generation.
|
||||
@@ -1882,15 +1855,9 @@ export class CliVersionConstraint {
|
||||
);
|
||||
}
|
||||
|
||||
async supportsStructuredEvalLog() {
|
||||
async supportsResolveExtensions() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_STRUCTURED_EVAL_LOG,
|
||||
);
|
||||
}
|
||||
|
||||
async supportsPerQueryEvalLog() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_PER_QUERY_EVAL_LOG,
|
||||
CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_EXTENSIONS,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -718,7 +718,7 @@ export enum DistributionKind {
|
||||
PathEnvironmentVariable,
|
||||
}
|
||||
|
||||
export interface Distribution {
|
||||
interface Distribution {
|
||||
codeQlPath: string;
|
||||
kind: DistributionKind;
|
||||
}
|
||||
@@ -776,22 +776,22 @@ type DistributionUpdateCheckResult =
|
||||
| InvalidLocationResult
|
||||
| UpdateAvailableResult;
|
||||
|
||||
export interface AlreadyCheckedRecentlyResult {
|
||||
interface AlreadyCheckedRecentlyResult {
|
||||
kind: DistributionUpdateCheckResultKind.AlreadyCheckedRecentlyResult;
|
||||
}
|
||||
|
||||
export interface AlreadyUpToDateResult {
|
||||
interface AlreadyUpToDateResult {
|
||||
kind: DistributionUpdateCheckResultKind.AlreadyUpToDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* The distribution could not be installed or updated because it is not managed by the extension.
|
||||
*/
|
||||
export interface InvalidLocationResult {
|
||||
interface InvalidLocationResult {
|
||||
kind: DistributionUpdateCheckResultKind.InvalidLocation;
|
||||
}
|
||||
|
||||
export interface UpdateAvailableResult {
|
||||
interface UpdateAvailableResult {
|
||||
kind: DistributionUpdateCheckResultKind.UpdateAvailable;
|
||||
updatedRelease: Release;
|
||||
}
|
||||
@@ -862,7 +862,7 @@ function warnDeprecatedLauncher() {
|
||||
/**
|
||||
* A release on GitHub.
|
||||
*/
|
||||
export interface Release {
|
||||
interface Release {
|
||||
assets: ReleaseAsset[];
|
||||
|
||||
/**
|
||||
@@ -884,7 +884,7 @@ export interface Release {
|
||||
/**
|
||||
* An asset corresponding to a release on GitHub.
|
||||
*/
|
||||
export interface ReleaseAsset {
|
||||
interface ReleaseAsset {
|
||||
/**
|
||||
* The id associated with the asset on GitHub.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
export const PAGE_SIZE = 1000;
|
||||
|
||||
/**
|
||||
* The single-character codes used in the bqrs format for the the kind
|
||||
* of a result column. This namespace is intentionally not an enum, see
|
||||
@@ -15,7 +13,7 @@ export namespace ColumnKindCode {
|
||||
export const ENTITY = "e";
|
||||
}
|
||||
|
||||
export type ColumnKind =
|
||||
type ColumnKind =
|
||||
| typeof ColumnKindCode.FLOAT
|
||||
| typeof ColumnKindCode.INTEGER
|
||||
| typeof ColumnKindCode.STRING
|
||||
@@ -46,7 +44,7 @@ export function getResultSetSchema(
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
export interface PaginationInfo {
|
||||
interface PaginationInfo {
|
||||
"step-size": number;
|
||||
offsets: number[];
|
||||
}
|
||||
|
||||
@@ -142,5 +142,7 @@ export function tryGetRemoteLocation(
|
||||
fileLink,
|
||||
resolvableLocation.startLine,
|
||||
resolvableLocation.endLine,
|
||||
resolvableLocation.startColumn,
|
||||
resolvableLocation.endColumn,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { AstItem } from "../language-support";
|
||||
import type { DbTreeViewItem } from "../databases/ui/db-tree-view-item";
|
||||
import type { DatabaseItem } from "../databases/local-databases";
|
||||
import type { QueryHistoryInfo } from "../query-history/query-history-info";
|
||||
import type { RepositoriesFilterSortStateWithIds } from "../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
import type { TestTreeNode } from "../query-testing/test-tree-node";
|
||||
import type {
|
||||
VariantAnalysis,
|
||||
@@ -56,7 +55,7 @@ export type ExplorerSelectionCommandFunction<Item> = (
|
||||
|
||||
// Builtin commands where the implementation is provided by VS Code and not by this extension.
|
||||
// See https://code.visualstudio.com/api/references/commands
|
||||
export type BuiltInVsCodeCommands = {
|
||||
type BuiltInVsCodeCommands = {
|
||||
// The codeQLDatabases.focus command is provided by VS Code because we've registered the custom view
|
||||
"codeQLDatabases.focus": () => Promise<void>;
|
||||
"markdown.showPreviewToSide": (uri: Uri) => Promise<void>;
|
||||
@@ -244,10 +243,6 @@ export type VariantAnalysisCommands = {
|
||||
scannedRepo: VariantAnalysisScannedRepository,
|
||||
variantAnalysisSummary: VariantAnalysis,
|
||||
) => Promise<void>;
|
||||
"codeQL.copyVariantAnalysisRepoList": (
|
||||
variantAnalysisId: number,
|
||||
filterSort?: RepositoriesFilterSortStateWithIds,
|
||||
) => Promise<void>;
|
||||
"codeQL.loadVariantAnalysisRepoResults": (
|
||||
variantAnalysisId: number,
|
||||
repositoryFullName: string,
|
||||
|
||||
@@ -76,11 +76,9 @@ export type GraphInterpretationData = {
|
||||
dot: string[];
|
||||
};
|
||||
|
||||
export type InterpretationData =
|
||||
| SarifInterpretationData
|
||||
| GraphInterpretationData;
|
||||
type InterpretationData = SarifInterpretationData | GraphInterpretationData;
|
||||
|
||||
export interface InterpretationT<T> {
|
||||
interface InterpretationT<T> {
|
||||
sourceLocationPrefix: string;
|
||||
numTruncatedResults: number;
|
||||
numTotalResults: number;
|
||||
@@ -106,7 +104,7 @@ export type SortedResultsMap = { [resultSet: string]: SortedResultSetInfo };
|
||||
*
|
||||
* As a result of receiving this message, listeners might want to display a loading indicator.
|
||||
*/
|
||||
export interface ResultsUpdatingMsg {
|
||||
interface ResultsUpdatingMsg {
|
||||
t: "resultsUpdating";
|
||||
}
|
||||
|
||||
@@ -114,7 +112,7 @@ export interface ResultsUpdatingMsg {
|
||||
* Message to set the initial state of the results view with a new
|
||||
* query.
|
||||
*/
|
||||
export interface SetStateMsg {
|
||||
interface SetStateMsg {
|
||||
t: "setState";
|
||||
resultsPath: string;
|
||||
origResultsPaths: ResultsPaths;
|
||||
@@ -143,7 +141,7 @@ export interface SetStateMsg {
|
||||
* Message indicating that the results view should display interpreted
|
||||
* results.
|
||||
*/
|
||||
export interface ShowInterpretedPageMsg {
|
||||
interface ShowInterpretedPageMsg {
|
||||
t: "showInterpretedPage";
|
||||
interpretation: Interpretation;
|
||||
database: DatabaseInfo;
|
||||
@@ -173,7 +171,7 @@ export interface NavigateMsg {
|
||||
* A message indicating that the results view should untoggle the
|
||||
* "Show results in Problems view" checkbox.
|
||||
*/
|
||||
export interface UntoggleShowProblemsMsg {
|
||||
interface UntoggleShowProblemsMsg {
|
||||
t: "untoggleShowProblems";
|
||||
}
|
||||
|
||||
@@ -203,7 +201,7 @@ export type FromResultsViewMsg =
|
||||
* Message from the results view to open a database source
|
||||
* file at the provided location.
|
||||
*/
|
||||
export interface ViewSourceFileMsg {
|
||||
interface ViewSourceFileMsg {
|
||||
t: "viewSourceFile";
|
||||
loc: ResolvableLocationValue;
|
||||
databaseUri: string;
|
||||
@@ -212,7 +210,7 @@ export interface ViewSourceFileMsg {
|
||||
/**
|
||||
* Message from the results view to open a file in an editor.
|
||||
*/
|
||||
export interface OpenFileMsg {
|
||||
interface OpenFileMsg {
|
||||
t: "openFile";
|
||||
/* Full path to the file to open. */
|
||||
filePath: string;
|
||||
@@ -274,7 +272,7 @@ export interface RawResultsSortState {
|
||||
sortDirection: SortDirection;
|
||||
}
|
||||
|
||||
export type InterpretedResultsSortColumn = "alert-message";
|
||||
type InterpretedResultsSortColumn = "alert-message";
|
||||
|
||||
export interface InterpretedResultsSortState {
|
||||
sortBy: InterpretedResultsSortColumn;
|
||||
@@ -318,7 +316,7 @@ export type FromCompareViewMessage =
|
||||
/**
|
||||
* Message from the compare view to request opening a query.
|
||||
*/
|
||||
export interface OpenQueryMessage {
|
||||
interface OpenQueryMessage {
|
||||
readonly t: "openQuery";
|
||||
readonly kind: "from" | "to";
|
||||
}
|
||||
@@ -406,12 +404,12 @@ export interface ParsedResultSets {
|
||||
resultSet: ResultSet;
|
||||
}
|
||||
|
||||
export interface SetVariantAnalysisMessage {
|
||||
interface SetVariantAnalysisMessage {
|
||||
t: "setVariantAnalysis";
|
||||
variantAnalysis: VariantAnalysis;
|
||||
}
|
||||
|
||||
export interface SetFilterSortStateMessage {
|
||||
interface SetFilterSortStateMessage {
|
||||
t: "setFilterSortState";
|
||||
filterSortState: RepositoriesFilterSortState;
|
||||
}
|
||||
@@ -420,48 +418,48 @@ export type VariantAnalysisState = {
|
||||
variantAnalysisId: number;
|
||||
};
|
||||
|
||||
export interface SetRepoResultsMessage {
|
||||
interface SetRepoResultsMessage {
|
||||
t: "setRepoResults";
|
||||
repoResults: VariantAnalysisScannedRepositoryResult[];
|
||||
}
|
||||
|
||||
export interface SetRepoStatesMessage {
|
||||
interface SetRepoStatesMessage {
|
||||
t: "setRepoStates";
|
||||
repoStates: VariantAnalysisScannedRepositoryState[];
|
||||
}
|
||||
|
||||
export interface RequestRepositoryResultsMessage {
|
||||
interface RequestRepositoryResultsMessage {
|
||||
t: "requestRepositoryResults";
|
||||
repositoryFullName: string;
|
||||
}
|
||||
|
||||
export interface OpenQueryFileMessage {
|
||||
interface OpenQueryFileMessage {
|
||||
t: "openQueryFile";
|
||||
}
|
||||
|
||||
export interface OpenQueryTextMessage {
|
||||
interface OpenQueryTextMessage {
|
||||
t: "openQueryText";
|
||||
}
|
||||
|
||||
export interface CopyRepositoryListMessage {
|
||||
interface CopyRepositoryListMessage {
|
||||
t: "copyRepositoryList";
|
||||
filterSort?: RepositoriesFilterSortStateWithIds;
|
||||
}
|
||||
|
||||
export interface ExportResultsMessage {
|
||||
interface ExportResultsMessage {
|
||||
t: "exportResults";
|
||||
filterSort?: RepositoriesFilterSortStateWithIds;
|
||||
}
|
||||
|
||||
export interface OpenLogsMessage {
|
||||
interface OpenLogsMessage {
|
||||
t: "openLogs";
|
||||
}
|
||||
|
||||
export interface CancelVariantAnalysisMessage {
|
||||
interface CancelVariantAnalysisMessage {
|
||||
t: "cancelVariantAnalysis";
|
||||
}
|
||||
|
||||
export interface ShowDataFlowPathsMessage {
|
||||
interface ShowDataFlowPathsMessage {
|
||||
t: "showDataFlowPaths";
|
||||
dataFlowPaths: DataFlowPaths;
|
||||
}
|
||||
@@ -483,7 +481,7 @@ export type FromVariantAnalysisMessage =
|
||||
| CancelVariantAnalysisMessage
|
||||
| ShowDataFlowPathsMessage;
|
||||
|
||||
export interface SetDataFlowPathsMessage {
|
||||
interface SetDataFlowPathsMessage {
|
||||
t: "setDataFlowPaths";
|
||||
dataFlowPaths: DataFlowPaths;
|
||||
}
|
||||
@@ -492,71 +490,71 @@ export type ToDataFlowPathsMessage = SetDataFlowPathsMessage;
|
||||
|
||||
export type FromDataFlowPathsMessage = CommonFromViewMessages;
|
||||
|
||||
export interface SetExtensionPackStateMessage {
|
||||
interface SetExtensionPackStateMessage {
|
||||
t: "setDataExtensionEditorViewState";
|
||||
viewState: DataExtensionEditorViewState;
|
||||
}
|
||||
|
||||
export interface SetExternalApiUsagesMessage {
|
||||
interface SetExternalApiUsagesMessage {
|
||||
t: "setExternalApiUsages";
|
||||
externalApiUsages: ExternalApiUsage[];
|
||||
}
|
||||
|
||||
export interface ShowProgressMessage {
|
||||
t: "showProgress";
|
||||
step: number;
|
||||
maxStep: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface LoadModeledMethodsMessage {
|
||||
interface LoadModeledMethodsMessage {
|
||||
t: "loadModeledMethods";
|
||||
modeledMethods: Record<string, ModeledMethod>;
|
||||
}
|
||||
|
||||
export interface AddModeledMethodsMessage {
|
||||
interface AddModeledMethodsMessage {
|
||||
t: "addModeledMethods";
|
||||
modeledMethods: Record<string, ModeledMethod>;
|
||||
}
|
||||
|
||||
export interface SwitchModeMessage {
|
||||
interface SwitchModeMessage {
|
||||
t: "switchMode";
|
||||
mode: Mode;
|
||||
}
|
||||
|
||||
export interface JumpToUsageMessage {
|
||||
interface JumpToUsageMessage {
|
||||
t: "jumpToUsage";
|
||||
location: ResolvableLocationValue;
|
||||
}
|
||||
|
||||
export interface OpenExtensionPackMessage {
|
||||
interface OpenDatabaseMessage {
|
||||
t: "openDatabase";
|
||||
}
|
||||
|
||||
interface OpenExtensionPackMessage {
|
||||
t: "openExtensionPack";
|
||||
}
|
||||
|
||||
export interface RefreshExternalApiUsages {
|
||||
interface RefreshExternalApiUsages {
|
||||
t: "refreshExternalApiUsages";
|
||||
}
|
||||
|
||||
export interface SaveModeledMethods {
|
||||
interface SaveModeledMethods {
|
||||
t: "saveModeledMethods";
|
||||
externalApiUsages: ExternalApiUsage[];
|
||||
modeledMethods: Record<string, ModeledMethod>;
|
||||
}
|
||||
|
||||
export interface GenerateExternalApiMessage {
|
||||
interface GenerateExternalApiMessage {
|
||||
t: "generateExternalApi";
|
||||
}
|
||||
|
||||
export interface GenerateExternalApiFromLlmMessage {
|
||||
interface GenerateExternalApiFromLlmMessage {
|
||||
t: "generateExternalApiFromLlm";
|
||||
externalApiUsages: ExternalApiUsage[];
|
||||
modeledMethods: Record<string, ModeledMethod>;
|
||||
}
|
||||
|
||||
interface ModelDependencyMessage {
|
||||
t: "modelDependency";
|
||||
}
|
||||
|
||||
export type ToDataExtensionsEditorMessage =
|
||||
| SetExtensionPackStateMessage
|
||||
| SetExternalApiUsagesMessage
|
||||
| ShowProgressMessage
|
||||
| LoadModeledMethodsMessage
|
||||
| AddModeledMethodsMessage;
|
||||
|
||||
@@ -564,8 +562,10 @@ export type FromDataExtensionsEditorMessage =
|
||||
| ViewLoadedMsg
|
||||
| SwitchModeMessage
|
||||
| RefreshExternalApiUsages
|
||||
| OpenDatabaseMessage
|
||||
| OpenExtensionPackMessage
|
||||
| JumpToUsageMessage
|
||||
| SaveModeledMethods
|
||||
| GenerateExternalApiMessage
|
||||
| GenerateExternalApiFromLlmMessage;
|
||||
| GenerateExternalApiFromLlmMessage
|
||||
| ModelDependencyMessage;
|
||||
|
||||
@@ -4,8 +4,20 @@ export function createRemoteFileRef(
|
||||
fileLink: FileLink,
|
||||
startLine?: number,
|
||||
endLine?: number,
|
||||
startColumn?: number,
|
||||
endColumn?: number,
|
||||
): string {
|
||||
if (startLine && endLine && startLine !== endLine) {
|
||||
if (
|
||||
startColumn &&
|
||||
endColumn &&
|
||||
startLine &&
|
||||
endLine &&
|
||||
// Verify that location information is valid; otherwise highlighting might be broken
|
||||
((startLine === endLine && startColumn < endColumn) || startLine < endLine)
|
||||
) {
|
||||
// This relies on column highlighting of new code view on GitHub
|
||||
return `${fileLink.fileLinkPrefix}/${fileLink.filePath}#L${startLine}C${startColumn}-L${endLine}C${endColumn}`;
|
||||
} else if (startLine && endLine && startLine < endLine) {
|
||||
return `${fileLink.fileLinkPrefix}/${fileLink.filePath}#L${startLine}-L${endLine}`;
|
||||
} else if (startLine) {
|
||||
return `${fileLink.fileLinkPrefix}/${fileLink.filePath}#L${startLine}`;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NotificationLogger } from "./notification-logger";
|
||||
import { AppTelemetry } from "../telemetry";
|
||||
import { RedactableError } from "../errors";
|
||||
|
||||
export interface ShowAndLogOptions {
|
||||
interface ShowAndLogOptions {
|
||||
/**
|
||||
* An alternate message that is added to the log, but not displayed in the popup.
|
||||
* This is useful for adding extra detail to the logs that would be too noisy for the popup.
|
||||
|
||||
@@ -9,6 +9,29 @@ export enum QueryLanguage {
|
||||
Swift = "swift",
|
||||
}
|
||||
|
||||
export function getLanguageDisplayName(language: string): string {
|
||||
switch (language) {
|
||||
case QueryLanguage.CSharp:
|
||||
return "C#";
|
||||
case QueryLanguage.Cpp:
|
||||
return "C / C++";
|
||||
case QueryLanguage.Go:
|
||||
return "Go";
|
||||
case QueryLanguage.Java:
|
||||
return "Java";
|
||||
case QueryLanguage.Javascript:
|
||||
return "JavaScript";
|
||||
case QueryLanguage.Python:
|
||||
return "Python";
|
||||
case QueryLanguage.Ruby:
|
||||
return "Ruby";
|
||||
case QueryLanguage.Swift:
|
||||
return "Swift";
|
||||
default:
|
||||
return language;
|
||||
}
|
||||
}
|
||||
|
||||
export const PACKS_BY_QUERY_LANGUAGE = {
|
||||
[QueryLanguage.Cpp]: ["codeql/cpp-queries"],
|
||||
[QueryLanguage.CSharp]: [
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as Sarif from "sarif";
|
||||
import type { HighlightedRegion } from "../variant-analysis/shared/analysis-result";
|
||||
import { ResolvableLocationValue } from "../common/bqrs-cli-types";
|
||||
|
||||
export interface SarifLink {
|
||||
interface SarifLink {
|
||||
dest: number;
|
||||
text: string;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ type ParsedSarifLocation =
|
||||
// that, and is appropriate for display in the UI.
|
||||
| NoLocation;
|
||||
|
||||
export type SarifMessageComponent = string | SarifLink;
|
||||
type SarifMessageComponent = string | SarifLink;
|
||||
|
||||
/**
|
||||
* Unescape "[", "]" and "\\" like in sarif plain text messages
|
||||
@@ -203,7 +203,7 @@ export function shouldHighlightLine(
|
||||
* A line of code split into: plain text before the highlighted section, the highlighted
|
||||
* text itself, and plain text after the highlighted section.
|
||||
*/
|
||||
export interface PartiallyHighlightedLine {
|
||||
interface PartiallyHighlightedLine {
|
||||
plainSection1: string;
|
||||
highlightedSection: string;
|
||||
plainSection2: string;
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
* Contains an assortment of helper constants and functions for working with time, dates, and durations.
|
||||
*/
|
||||
|
||||
export const ONE_SECOND_IN_MS = 1000;
|
||||
export const ONE_MINUTE_IN_MS = ONE_SECOND_IN_MS * 60;
|
||||
const ONE_SECOND_IN_MS = 1000;
|
||||
const ONE_MINUTE_IN_MS = ONE_SECOND_IN_MS * 60;
|
||||
export const ONE_HOUR_IN_MS = ONE_MINUTE_IN_MS * 60;
|
||||
export const TWO_HOURS_IN_MS = ONE_HOUR_IN_MS * 2;
|
||||
export const THREE_HOURS_IN_MS = ONE_HOUR_IN_MS * 3;
|
||||
export const ONE_DAY_IN_MS = ONE_HOUR_IN_MS * 24;
|
||||
|
||||
// These are approximations
|
||||
export const ONE_MONTH_IN_MS = ONE_DAY_IN_MS * 30;
|
||||
export const ONE_YEAR_IN_MS = ONE_DAY_IN_MS * 365;
|
||||
const ONE_MONTH_IN_MS = ONE_DAY_IN_MS * 30;
|
||||
const ONE_YEAR_IN_MS = ONE_DAY_IN_MS * 365;
|
||||
|
||||
const durationFormatter = new Intl.RelativeTimeFormat("en", {
|
||||
numeric: "auto",
|
||||
|
||||
@@ -8,7 +8,7 @@ import { extLogger } from "../logging/vscode";
|
||||
import { posix } from "path";
|
||||
const path = posix;
|
||||
|
||||
export class File implements vscode.FileStat {
|
||||
class File implements vscode.FileStat {
|
||||
type: vscode.FileType;
|
||||
ctime: number;
|
||||
mtime: number;
|
||||
@@ -26,7 +26,7 @@ export class File implements vscode.FileStat {
|
||||
}
|
||||
}
|
||||
|
||||
export class Directory implements vscode.FileStat {
|
||||
class Directory implements vscode.FileStat {
|
||||
type: vscode.FileType;
|
||||
ctime: number;
|
||||
mtime: number;
|
||||
@@ -41,7 +41,7 @@ export class Directory implements vscode.FileStat {
|
||||
}
|
||||
}
|
||||
|
||||
export type Entry = File | Directory;
|
||||
type Entry = File | Directory;
|
||||
|
||||
/**
|
||||
* A map containing directory hierarchy information in a convenient form.
|
||||
@@ -52,7 +52,7 @@ export type Entry = File | Directory;
|
||||
* dirMap['/foo'] = {'bar': vscode.FileType.Directory}
|
||||
* dirMap['/foo/bar'] = {'baz': vscode.FileType.File}
|
||||
*/
|
||||
export type DirectoryHierarchyMap = Map<string, Map<string, vscode.FileType>>;
|
||||
type DirectoryHierarchyMap = Map<string, Map<string, vscode.FileType>>;
|
||||
|
||||
export type ZipFileReference = {
|
||||
sourceArchiveZipPath: string;
|
||||
|
||||
@@ -38,7 +38,7 @@ export type ProgressCallback = (p: ProgressUpdate) => void;
|
||||
// Make certain properties within a type optional
|
||||
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
|
||||
|
||||
export type ProgressOptions = Optional<VSCodeProgressOptions, "location">;
|
||||
type ProgressOptions = Optional<VSCodeProgressOptions, "location">;
|
||||
|
||||
/**
|
||||
* A task that reports progress.
|
||||
|
||||
@@ -25,7 +25,7 @@ import { AppTelemetry } from "../telemetry";
|
||||
// Key is injected at build time through the APP_INSIGHTS_KEY environment variable.
|
||||
const key = "REPLACE-APP-INSIGHTS-KEY";
|
||||
|
||||
export enum CommandCompletion {
|
||||
enum CommandCompletion {
|
||||
Success = "Success",
|
||||
Failed = "Failed",
|
||||
Cancelled = "Cancelled",
|
||||
|
||||
12
extensions/ql-vscode/src/common/zlib.ts
Normal file
12
extensions/ql-vscode/src/common/zlib.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { promisify } from "util";
|
||||
import { gzip, gunzip } from "zlib";
|
||||
|
||||
/**
|
||||
* Promisified version of zlib.gzip
|
||||
*/
|
||||
export const gzipEncode = promisify(gzip);
|
||||
|
||||
/**
|
||||
* Promisified version of zlib.gunzip
|
||||
*/
|
||||
export const gzipDecode = promisify(gunzip);
|
||||
@@ -64,12 +64,6 @@ export class Setting {
|
||||
}
|
||||
}
|
||||
|
||||
export interface InspectionResult<T> {
|
||||
globalValue?: T;
|
||||
workspaceValue?: T;
|
||||
workspaceFolderValue?: T;
|
||||
}
|
||||
|
||||
const VSCODE_DEBUG_SETTING = new Setting("debug", undefined);
|
||||
export const VSCODE_SAVE_BEFORE_START_SETTING = new Setting(
|
||||
"saveBeforeStart",
|
||||
@@ -592,10 +586,6 @@ export function isIntegrationTestMode() {
|
||||
return process.env.INTEGRATION_TEST_MODE === "true";
|
||||
}
|
||||
|
||||
export function isVariantAnalysisLiveResultsEnabled(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Settings for mocking the GitHub API.
|
||||
const MOCK_GH_API_SERVER = new Setting("mockGitHubApiServer", ROOT_SETTING);
|
||||
|
||||
@@ -659,10 +649,7 @@ export function isCodespacesTemplate() {
|
||||
|
||||
const DATABASE_DOWNLOAD_SETTING = new Setting("databaseDownload", ROOT_SETTING);
|
||||
|
||||
export const ALLOW_HTTP_SETTING = new Setting(
|
||||
"allowHttp",
|
||||
DATABASE_DOWNLOAD_SETTING,
|
||||
);
|
||||
const ALLOW_HTTP_SETTING = new Setting("allowHttp", DATABASE_DOWNLOAD_SETTING);
|
||||
|
||||
export function allowHttp(): boolean {
|
||||
return ALLOW_HTTP_SETTING.getValue<boolean>() || false;
|
||||
@@ -717,6 +704,7 @@ export function showQueriesPanel(): boolean {
|
||||
|
||||
const DATA_EXTENSIONS = new Setting("dataExtensions", ROOT_SETTING);
|
||||
const LLM_GENERATION = new Setting("llmGeneration", DATA_EXTENSIONS);
|
||||
const LLM_GENERATION_V2 = new Setting("llmGenerationV2", DATA_EXTENSIONS);
|
||||
const FRAMEWORK_MODE = new Setting("frameworkMode", DATA_EXTENSIONS);
|
||||
const DISABLE_AUTO_NAME_EXTENSION_PACK = new Setting(
|
||||
"disableAutoNameExtensionPack",
|
||||
@@ -731,6 +719,10 @@ export function showLlmGeneration(): boolean {
|
||||
return !!LLM_GENERATION.getValue<boolean>();
|
||||
}
|
||||
|
||||
export function useLlmGenerationV2(): boolean {
|
||||
return !!LLM_GENERATION_V2.getValue<boolean>();
|
||||
}
|
||||
|
||||
export function enableFrameworkMode(): boolean {
|
||||
return !!FRAMEWORK_MODE.getValue<boolean>();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Credentials } from "../common/authentication";
|
||||
import { OctokitResponse } from "@octokit/types";
|
||||
|
||||
export enum AutomodelMode {
|
||||
Unspecified = "AUTOMODEL_MODE_UNSPECIFIED",
|
||||
Framework = "AUTOMODEL_MODE_FRAMEWORK",
|
||||
Application = "AUTOMODEL_MODE_APPLICATION",
|
||||
}
|
||||
|
||||
export interface ModelRequest {
|
||||
mode: AutomodelMode;
|
||||
// Base64-encoded GZIP-compressed SARIF log
|
||||
candidates: string;
|
||||
}
|
||||
|
||||
export interface ModelResponse {
|
||||
models: string;
|
||||
}
|
||||
|
||||
export async function autoModelV2(
|
||||
credentials: Credentials,
|
||||
request: ModelRequest,
|
||||
): Promise<ModelResponse> {
|
||||
const octokit = await credentials.getOctokit();
|
||||
|
||||
const response: OctokitResponse<ModelResponse> = await octokit.request(
|
||||
"POST /repos/github/codeql/code-scanning/codeql/auto-model",
|
||||
{
|
||||
data: request,
|
||||
},
|
||||
);
|
||||
|
||||
return response.data;
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
import { CodeQLCliServer, SourceInfo } from "../codeql-cli/cli";
|
||||
import { QueryRunner } from "../query-server";
|
||||
import { DatabaseItem } from "../databases/local-databases";
|
||||
import { ProgressCallback } from "../common/vscode/progress";
|
||||
import * as Sarif from "sarif";
|
||||
import { qlpackOfDatabase, resolveQueries } from "../local-queries";
|
||||
import { extLogger } from "../common/logging/vscode";
|
||||
import { Mode } from "./shared/mode";
|
||||
import { QlPacksForLanguage } from "../databases/qlpack";
|
||||
import { createLockFileForStandardQuery } from "../local-queries/standard-queries";
|
||||
import { CancellationToken, CancellationTokenSource } from "vscode";
|
||||
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
||||
import { showAndLogExceptionWithTelemetry, TeeLogger } from "../common/logging";
|
||||
import { QueryResultType } from "../query-server/new-messages";
|
||||
import { telemetryListener } from "../common/vscode/telemetry";
|
||||
import { redactableError } from "../common/errors";
|
||||
import { interpretResultsSarif } from "../query-results";
|
||||
import { join } from "path";
|
||||
import { assertNever } from "../common/helpers-pure";
|
||||
|
||||
type AutoModelQueryOptions = {
|
||||
queryTag: string;
|
||||
mode: Mode;
|
||||
cliServer: CodeQLCliServer;
|
||||
queryRunner: QueryRunner;
|
||||
databaseItem: DatabaseItem;
|
||||
qlpack: QlPacksForLanguage;
|
||||
sourceInfo: SourceInfo | undefined;
|
||||
extensionPacks: string[];
|
||||
queryStorageDir: string;
|
||||
|
||||
progress: ProgressCallback;
|
||||
token: CancellationToken;
|
||||
};
|
||||
|
||||
function modeTag(mode: Mode): string {
|
||||
switch (mode) {
|
||||
case Mode.Application:
|
||||
return "application-mode";
|
||||
case Mode.Framework:
|
||||
return "framework-mode";
|
||||
default:
|
||||
assertNever(mode);
|
||||
}
|
||||
}
|
||||
|
||||
async function runAutoModelQuery({
|
||||
queryTag,
|
||||
mode,
|
||||
cliServer,
|
||||
queryRunner,
|
||||
databaseItem,
|
||||
qlpack,
|
||||
sourceInfo,
|
||||
extensionPacks,
|
||||
queryStorageDir,
|
||||
progress,
|
||||
token,
|
||||
}: AutoModelQueryOptions): Promise<Sarif.Log | undefined> {
|
||||
// First, resolve the query that we want to run.
|
||||
// All queries are tagged like this:
|
||||
// internal extract automodel <mode> <queryTag>
|
||||
// Example: internal extract automodel framework-mode candidates
|
||||
const queries = await resolveQueries(
|
||||
cliServer,
|
||||
qlpack,
|
||||
`Extract automodel ${queryTag}`,
|
||||
{
|
||||
kind: "problem",
|
||||
"tags contain all": ["automodel", modeTag(mode), ...queryTag.split(" ")],
|
||||
},
|
||||
);
|
||||
if (queries.length > 1) {
|
||||
throw new Error(
|
||||
`Found multiple auto model queries for ${mode} ${queryTag}. Can't continue`,
|
||||
);
|
||||
}
|
||||
if (queries.length === 0) {
|
||||
throw new Error(
|
||||
`Did not found any auto model queries for ${mode} ${queryTag}. Can't continue`,
|
||||
);
|
||||
}
|
||||
|
||||
const queryPath = queries[0];
|
||||
const { cleanup: cleanupLockFile } = await createLockFileForStandardQuery(
|
||||
cliServer,
|
||||
queryPath,
|
||||
);
|
||||
|
||||
// Get metadata for the query. This is required to interpret the results. We already know the kind is problem
|
||||
// (because of the constraint in resolveQueries), so we don't need any more checks on the metadata.
|
||||
const metadata = await cliServer.resolveMetadata(queryPath);
|
||||
|
||||
const queryRun = queryRunner.createQueryRun(
|
||||
databaseItem.databaseUri.fsPath,
|
||||
{
|
||||
queryPath,
|
||||
quickEvalPosition: undefined,
|
||||
quickEvalCountOnly: false,
|
||||
},
|
||||
false,
|
||||
getOnDiskWorkspaceFolders(),
|
||||
extensionPacks,
|
||||
queryStorageDir,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
|
||||
const completedQuery = await queryRun.evaluate(
|
||||
progress,
|
||||
token,
|
||||
new TeeLogger(queryRunner.logger, queryRun.outputDir.logPath),
|
||||
);
|
||||
|
||||
await cleanupLockFile?.();
|
||||
|
||||
if (completedQuery.resultType !== QueryResultType.SUCCESS) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
extLogger,
|
||||
telemetryListener,
|
||||
redactableError`Auto-model query ${queryTag} failed: ${
|
||||
completedQuery.message ?? "No message"
|
||||
}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const interpretedResultsPath = join(
|
||||
queryStorageDir,
|
||||
`interpreted-results-${queryTag.replaceAll(" ", "-")}-${queryRun.id}.sarif`,
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- We only need the actual SARIF data, not the extra fields added by SarifInterpretationData
|
||||
const { t, sortState, ...sarif } = await interpretResultsSarif(
|
||||
cliServer,
|
||||
metadata,
|
||||
{
|
||||
resultsPath: completedQuery.outputDir.bqrsPath,
|
||||
interpretedResultsPath,
|
||||
},
|
||||
sourceInfo,
|
||||
["--sarif-add-snippets"],
|
||||
);
|
||||
|
||||
return sarif;
|
||||
}
|
||||
|
||||
type AutoModelQueriesOptions = {
|
||||
mode: Mode;
|
||||
cliServer: CodeQLCliServer;
|
||||
queryRunner: QueryRunner;
|
||||
databaseItem: DatabaseItem;
|
||||
queryStorageDir: string;
|
||||
|
||||
progress: ProgressCallback;
|
||||
};
|
||||
|
||||
export type AutoModelQueriesResult = {
|
||||
candidates: Sarif.Log;
|
||||
};
|
||||
|
||||
export async function runAutoModelQueries({
|
||||
mode,
|
||||
cliServer,
|
||||
queryRunner,
|
||||
databaseItem,
|
||||
queryStorageDir,
|
||||
progress,
|
||||
}: AutoModelQueriesOptions): Promise<AutoModelQueriesResult | undefined> {
|
||||
// maxStep for this part is 1500
|
||||
const maxStep = 1500;
|
||||
|
||||
const cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
const qlpack = await qlpackOfDatabase(cliServer, databaseItem);
|
||||
|
||||
// CodeQL needs to have access to the database to be able to retrieve the
|
||||
// snippets from it. The source location prefix is used to determine the
|
||||
// base path of the database.
|
||||
const sourceLocationPrefix = await databaseItem.getSourceLocationPrefix(
|
||||
cliServer,
|
||||
);
|
||||
const sourceArchiveUri = databaseItem.sourceArchive;
|
||||
const sourceInfo =
|
||||
sourceArchiveUri === undefined
|
||||
? undefined
|
||||
: {
|
||||
sourceArchive: sourceArchiveUri.fsPath,
|
||||
sourceLocationPrefix,
|
||||
};
|
||||
|
||||
const additionalPacks = getOnDiskWorkspaceFolders();
|
||||
const extensionPacks = Object.keys(
|
||||
await cliServer.resolveQlpacks(additionalPacks, true),
|
||||
);
|
||||
|
||||
progress({
|
||||
step: 0,
|
||||
maxStep,
|
||||
message: "Finding candidates and examples",
|
||||
});
|
||||
|
||||
const candidates = await runAutoModelQuery({
|
||||
mode,
|
||||
queryTag: "candidates",
|
||||
cliServer,
|
||||
queryRunner,
|
||||
databaseItem,
|
||||
qlpack,
|
||||
sourceInfo,
|
||||
extensionPacks,
|
||||
queryStorageDir,
|
||||
progress: (update) => {
|
||||
progress({
|
||||
step: update.step,
|
||||
maxStep,
|
||||
message: "Finding candidates and examples",
|
||||
});
|
||||
},
|
||||
token: cancellationTokenSource.token,
|
||||
});
|
||||
|
||||
if (!candidates) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
candidates,
|
||||
};
|
||||
}
|
||||
@@ -6,12 +6,14 @@ import { QueryRunner } from "../query-server";
|
||||
import { DatabaseItem } from "../databases/local-databases";
|
||||
import { interpretResultsSarif } from "../query-results";
|
||||
import { ProgressCallback } from "../common/vscode/progress";
|
||||
import { Mode } from "./shared/mode";
|
||||
|
||||
type Options = {
|
||||
cliServer: CodeQLCliServer;
|
||||
queryRunner: QueryRunner;
|
||||
databaseItem: DatabaseItem;
|
||||
queryStorageDir: string;
|
||||
queryDir: string;
|
||||
|
||||
progress: ProgressCallback;
|
||||
};
|
||||
@@ -23,6 +25,7 @@ export async function getAutoModelUsages({
|
||||
queryRunner,
|
||||
databaseItem,
|
||||
queryStorageDir,
|
||||
queryDir,
|
||||
progress,
|
||||
}: Options): Promise<UsageSnippetsBySignature> {
|
||||
const maxStep = 1500;
|
||||
@@ -32,11 +35,12 @@ export async function getAutoModelUsages({
|
||||
// This will re-run the query that was already run when opening the data extensions editor. This
|
||||
// might be unnecessary, but this makes it really easy to get the path to the BQRS file which we
|
||||
// need to interpret the results.
|
||||
const queryResult = await runQuery("applicationModeQuery", {
|
||||
const queryResult = await runQuery(Mode.Application, {
|
||||
cliServer,
|
||||
queryRunner,
|
||||
queryStorageDir,
|
||||
databaseItem,
|
||||
queryDir,
|
||||
progress: (update) =>
|
||||
progress({
|
||||
maxStep,
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { AutomodelMode, ModelRequest } from "./auto-model-api-v2";
|
||||
import { Mode } from "./shared/mode";
|
||||
import { AutoModelQueriesResult } from "./auto-model-codeml-queries";
|
||||
import { assertNever } from "../common/helpers-pure";
|
||||
import * as Sarif from "sarif";
|
||||
import { gzipEncode } from "../common/zlib";
|
||||
|
||||
/**
|
||||
* Encode a SARIF log to the format expected by the server: JSON, GZIP-compressed, base64-encoded
|
||||
* @param log SARIF log to encode
|
||||
* @returns base64-encoded GZIP-compressed SARIF log
|
||||
*/
|
||||
export async function encodeSarif(log: Sarif.Log): Promise<string> {
|
||||
const json = JSON.stringify(log);
|
||||
const buffer = Buffer.from(json, "utf-8");
|
||||
const compressed = await gzipEncode(buffer);
|
||||
return compressed.toString("base64");
|
||||
}
|
||||
|
||||
export async function createAutoModelV2Request(
|
||||
mode: Mode,
|
||||
result: AutoModelQueriesResult,
|
||||
): Promise<ModelRequest> {
|
||||
let requestMode: AutomodelMode;
|
||||
switch (mode) {
|
||||
case Mode.Application:
|
||||
requestMode = AutomodelMode.Application;
|
||||
break;
|
||||
case Mode.Framework:
|
||||
requestMode = AutomodelMode.Framework;
|
||||
break;
|
||||
default:
|
||||
assertNever(mode);
|
||||
}
|
||||
|
||||
return {
|
||||
mode: requestMode,
|
||||
candidates: await encodeSarif(result.candidates),
|
||||
};
|
||||
}
|
||||
@@ -9,7 +9,17 @@ import { join } from "path";
|
||||
import { App } from "../common/app";
|
||||
import { withProgress } from "../common/vscode/progress";
|
||||
import { pickExtensionPack } from "./extension-pack-picker";
|
||||
import { showAndLogErrorMessage } from "../common/logging";
|
||||
import {
|
||||
showAndLogErrorMessage,
|
||||
showAndLogExceptionWithTelemetry,
|
||||
} from "../common/logging";
|
||||
import { dir } from "tmp-promise";
|
||||
import { fetchExternalApiQueries } from "./queries";
|
||||
import { telemetryListener } from "../common/vscode/telemetry";
|
||||
import { redactableError } from "../common/errors";
|
||||
import { extLogger } from "../common/logging/vscode";
|
||||
import { isQueryLanguage } from "../common/query-language";
|
||||
import { setUpPack } from "./external-api-usage-query";
|
||||
|
||||
const SUPPORTED_LANGUAGES: string[] = ["java", "csharp"];
|
||||
|
||||
@@ -60,10 +70,14 @@ export class DataExtensionsEditorModule {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SUPPORTED_LANGUAGES.includes(db.language)) {
|
||||
const language = db.language;
|
||||
if (
|
||||
!SUPPORTED_LANGUAGES.includes(language) ||
|
||||
!isQueryLanguage(language)
|
||||
) {
|
||||
void showAndLogErrorMessage(
|
||||
this.app.logger,
|
||||
`The data extensions editor is not supported for ${db.language} databases.`,
|
||||
`The data extensions editor is not supported for ${language} databases.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -78,6 +92,16 @@ export class DataExtensionsEditorModule {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!(await this.cliServer.cliConstraints.supportsResolveExtensions())
|
||||
) {
|
||||
void showAndLogErrorMessage(
|
||||
this.app.logger,
|
||||
`This feature requires CodeQL CLI version ${CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_EXTENSIONS.format()} or later.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const modelFile = await pickExtensionPack(
|
||||
this.cliServer,
|
||||
db,
|
||||
@@ -89,6 +113,21 @@ export class DataExtensionsEditorModule {
|
||||
return;
|
||||
}
|
||||
|
||||
const query = fetchExternalApiQueries[language];
|
||||
if (!query) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
extLogger,
|
||||
telemetryListener,
|
||||
redactableError`No external API usage query found for language ${language}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create new temporary directory for query files and pack dependencies
|
||||
const queryDir = (await dir({ unsafeCleanup: true })).path;
|
||||
await setUpPack(queryDir, query, language);
|
||||
await this.cliServer.packInstall(queryDir);
|
||||
|
||||
const view = new DataExtensionsEditorView(
|
||||
this.ctx,
|
||||
this.app,
|
||||
@@ -96,6 +135,7 @@ export class DataExtensionsEditorModule {
|
||||
this.cliServer,
|
||||
this.queryRunner,
|
||||
this.queryStorageDir,
|
||||
queryDir,
|
||||
db,
|
||||
modelFile,
|
||||
);
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
ViewColumn,
|
||||
window,
|
||||
} from "vscode";
|
||||
import { join } from "path";
|
||||
import { RequestError } from "@octokit/request-error";
|
||||
import {
|
||||
AbstractWebview,
|
||||
@@ -15,14 +14,12 @@ import {
|
||||
FromDataExtensionsEditorMessage,
|
||||
ToDataExtensionsEditorMessage,
|
||||
} from "../common/interface-types";
|
||||
import { ProgressUpdate } from "../common/vscode/progress";
|
||||
import { ProgressCallback, withProgress } from "../common/vscode/progress";
|
||||
import { QueryRunner } from "../query-server";
|
||||
import {
|
||||
showAndLogExceptionWithTelemetry,
|
||||
showAndLogErrorMessage,
|
||||
} from "../common/logging";
|
||||
import { outputFile, readFile } from "fs-extra";
|
||||
import { load as loadYaml } from "js-yaml";
|
||||
import { DatabaseItem, DatabaseManager } from "../databases/local-databases";
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { asError, assertNever, getErrorMessage } from "../common/helpers-pure";
|
||||
@@ -34,23 +31,34 @@ import { showResolvableLocation } from "../databases/local-databases/locations";
|
||||
import { decodeBqrsToExternalApiUsages } from "./bqrs";
|
||||
import { redactableError } from "../common/errors";
|
||||
import { readQueryResults, runQuery } from "./external-api-usage-query";
|
||||
import {
|
||||
createDataExtensionYamlsForApplicationMode,
|
||||
createDataExtensionYamlsForFrameworkMode,
|
||||
loadDataExtensionYaml,
|
||||
} from "./yaml";
|
||||
import { ExternalApiUsage } from "./external-api-usage";
|
||||
import { ModeledMethod } from "./modeled-method";
|
||||
import { ExtensionPack } from "./shared/extension-pack";
|
||||
import { autoModel, ModelRequest, ModelResponse } from "./auto-model-api";
|
||||
import {
|
||||
autoModelV2,
|
||||
ModelRequest as ModelRequestV2,
|
||||
ModelResponse as ModelResponseV2,
|
||||
} from "./auto-model-api-v2";
|
||||
import {
|
||||
createAutoModelRequest,
|
||||
parsePredictedClassifications,
|
||||
} from "./auto-model";
|
||||
import { enableFrameworkMode, showLlmGeneration } from "../config";
|
||||
import {
|
||||
enableFrameworkMode,
|
||||
showLlmGeneration,
|
||||
useLlmGenerationV2,
|
||||
} from "../config";
|
||||
import { getAutoModelUsages } from "./auto-model-usages-query";
|
||||
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
||||
import { Mode } from "./shared/mode";
|
||||
import { loadModeledMethods, saveModeledMethods } from "./modeled-method-fs";
|
||||
import { join } from "path";
|
||||
import { pickExtensionPack } from "./extension-pack-picker";
|
||||
import { getLanguageDisplayName } from "../common/query-language";
|
||||
import { runAutoModelQueries } from "./auto-model-codeml-queries";
|
||||
import { createAutoModelV2Request } from "./auto-model-v2";
|
||||
import { load as loadYaml } from "js-yaml";
|
||||
import { loadDataExtensionYaml } from "./yaml";
|
||||
|
||||
export class DataExtensionsEditorView extends AbstractWebview<
|
||||
ToDataExtensionsEditorMessage,
|
||||
@@ -63,6 +71,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
private readonly cliServer: CodeQLCliServer,
|
||||
private readonly queryRunner: QueryRunner,
|
||||
private readonly queryStorageDir: string,
|
||||
private readonly queryDir: string,
|
||||
private readonly databaseItem: DatabaseItem,
|
||||
private readonly extensionPack: ExtensionPack,
|
||||
private mode: Mode = Mode.Application,
|
||||
@@ -80,7 +89,9 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
protected async getPanelConfig(): Promise<WebviewPanelConfig> {
|
||||
return {
|
||||
viewId: "data-extensions-editor",
|
||||
title: "Data Extensions Editor",
|
||||
title: `Modeling ${getLanguageDisplayName(
|
||||
this.extensionPack.language,
|
||||
)} (${this.extensionPack.name})`,
|
||||
viewColumn: ViewColumn.Active,
|
||||
preserveFocus: true,
|
||||
view: "data-extensions-editor",
|
||||
@@ -106,6 +117,13 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
case "viewLoaded":
|
||||
await this.onWebViewLoaded();
|
||||
|
||||
break;
|
||||
case "openDatabase":
|
||||
await this.app.commands.execute(
|
||||
"revealInExplorer",
|
||||
this.databaseItem.getSourceArchiveExplorerUri(),
|
||||
);
|
||||
|
||||
break;
|
||||
case "openExtensionPack":
|
||||
await this.app.commands.execute(
|
||||
@@ -123,9 +141,15 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
|
||||
break;
|
||||
case "saveModeledMethods":
|
||||
await this.saveModeledMethods(
|
||||
await saveModeledMethods(
|
||||
this.extensionPack,
|
||||
this.databaseItem.name,
|
||||
this.databaseItem.language,
|
||||
msg.externalApiUsages,
|
||||
msg.modeledMethods,
|
||||
this.mode,
|
||||
this.cliServer,
|
||||
this.app.logger,
|
||||
);
|
||||
await Promise.all([this.setViewState(), this.loadExternalApiUsages()]);
|
||||
|
||||
@@ -140,6 +164,10 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
msg.modeledMethods,
|
||||
);
|
||||
|
||||
break;
|
||||
case "modelDependency":
|
||||
await this.modelDependency();
|
||||
|
||||
break;
|
||||
case "switchMode":
|
||||
this.mode = msg.mode;
|
||||
@@ -163,12 +191,15 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
}
|
||||
|
||||
private async setViewState(): Promise<void> {
|
||||
const showLlmButton =
|
||||
this.databaseItem.language === "java" && showLlmGeneration();
|
||||
|
||||
await this.postMessage({
|
||||
t: "setDataExtensionEditorViewState",
|
||||
viewState: {
|
||||
extensionPack: this.extensionPack,
|
||||
enableFrameworkMode: enableFrameworkMode(),
|
||||
showLlmButton: showLlmGeneration(),
|
||||
showLlmButton,
|
||||
mode: this.mode,
|
||||
},
|
||||
});
|
||||
@@ -194,79 +225,16 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
}
|
||||
}
|
||||
|
||||
protected async saveModeledMethods(
|
||||
externalApiUsages: ExternalApiUsage[],
|
||||
modeledMethods: Record<string, ModeledMethod>,
|
||||
): Promise<void> {
|
||||
let yamls: Record<string, string>;
|
||||
switch (this.mode) {
|
||||
case Mode.Application:
|
||||
yamls = createDataExtensionYamlsForApplicationMode(
|
||||
this.databaseItem.language,
|
||||
externalApiUsages,
|
||||
modeledMethods,
|
||||
);
|
||||
break;
|
||||
case Mode.Framework:
|
||||
yamls = createDataExtensionYamlsForFrameworkMode(
|
||||
this.databaseItem.name,
|
||||
this.databaseItem.language,
|
||||
externalApiUsages,
|
||||
modeledMethods,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
assertNever(this.mode);
|
||||
}
|
||||
|
||||
for (const [filename, yaml] of Object.entries(yamls)) {
|
||||
await outputFile(join(this.extensionPack.path, filename), yaml);
|
||||
}
|
||||
|
||||
void this.app.logger.log(`Saved data extension YAML`);
|
||||
}
|
||||
|
||||
protected async loadExistingModeledMethods(): Promise<void> {
|
||||
try {
|
||||
const extensions = await this.cliServer.resolveExtensions(
|
||||
this.extensionPack.path,
|
||||
getOnDiskWorkspaceFolders(),
|
||||
const modeledMethods = await loadModeledMethods(
|
||||
this.extensionPack,
|
||||
this.cliServer,
|
||||
this.app.logger,
|
||||
);
|
||||
|
||||
const modelFiles = new Set<string>();
|
||||
|
||||
if (this.extensionPack.path in extensions.data) {
|
||||
for (const extension of extensions.data[this.extensionPack.path]) {
|
||||
modelFiles.add(extension.file);
|
||||
}
|
||||
}
|
||||
|
||||
const existingModeledMethods: Record<string, ModeledMethod> = {};
|
||||
|
||||
for (const modelFile of modelFiles) {
|
||||
const yaml = await readFile(modelFile, "utf8");
|
||||
|
||||
const data = loadYaml(yaml, {
|
||||
filename: modelFile,
|
||||
});
|
||||
|
||||
const modeledMethods = loadDataExtensionYaml(data);
|
||||
if (!modeledMethods) {
|
||||
void showAndLogErrorMessage(
|
||||
this.app.logger,
|
||||
`Failed to parse data extension YAML ${modelFile}.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(modeledMethods)) {
|
||||
existingModeledMethods[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
await this.postMessage({
|
||||
t: "loadModeledMethods",
|
||||
modeledMethods: existingModeledMethods,
|
||||
modeledMethods,
|
||||
});
|
||||
} catch (e: unknown) {
|
||||
void showAndLogErrorMessage(
|
||||
@@ -277,252 +245,350 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
}
|
||||
|
||||
protected async loadExternalApiUsages(): Promise<void> {
|
||||
const cancellationTokenSource = new CancellationTokenSource();
|
||||
await withProgress(
|
||||
async (progress) => {
|
||||
try {
|
||||
const cancellationTokenSource = new CancellationTokenSource();
|
||||
const queryResult = await runQuery(this.mode, {
|
||||
cliServer: this.cliServer,
|
||||
queryRunner: this.queryRunner,
|
||||
databaseItem: this.databaseItem,
|
||||
queryStorageDir: this.queryStorageDir,
|
||||
queryDir: this.queryDir,
|
||||
progress: (update) => progress({ ...update, maxStep: 1500 }),
|
||||
token: cancellationTokenSource.token,
|
||||
});
|
||||
if (!queryResult) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const queryResult = await runQuery(
|
||||
this.mode === Mode.Framework
|
||||
? "frameworkModeQuery"
|
||||
: "applicationModeQuery",
|
||||
{
|
||||
cliServer: this.cliServer,
|
||||
queryRunner: this.queryRunner,
|
||||
databaseItem: this.databaseItem,
|
||||
queryStorageDir: this.queryStorageDir,
|
||||
progress: (progressUpdate: ProgressUpdate) => {
|
||||
void this.showProgress(progressUpdate, 1500);
|
||||
},
|
||||
token: cancellationTokenSource.token,
|
||||
},
|
||||
);
|
||||
if (!queryResult) {
|
||||
await this.clearProgress();
|
||||
return;
|
||||
}
|
||||
progress({
|
||||
message: "Decoding results",
|
||||
step: 1100,
|
||||
maxStep: 1500,
|
||||
});
|
||||
|
||||
await this.showProgress({
|
||||
message: "Decoding results",
|
||||
step: 1100,
|
||||
maxStep: 1500,
|
||||
});
|
||||
const bqrsChunk = await readQueryResults({
|
||||
cliServer: this.cliServer,
|
||||
bqrsPath: queryResult.outputDir.bqrsPath,
|
||||
});
|
||||
if (!bqrsChunk) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bqrsChunk = await readQueryResults({
|
||||
cliServer: this.cliServer,
|
||||
bqrsPath: queryResult.outputDir.bqrsPath,
|
||||
});
|
||||
if (!bqrsChunk) {
|
||||
await this.clearProgress();
|
||||
return;
|
||||
}
|
||||
progress({
|
||||
message: "Finalizing results",
|
||||
step: 1450,
|
||||
maxStep: 1500,
|
||||
});
|
||||
|
||||
await this.showProgress({
|
||||
message: "Finalizing results",
|
||||
step: 1450,
|
||||
maxStep: 1500,
|
||||
});
|
||||
const externalApiUsages = decodeBqrsToExternalApiUsages(bqrsChunk);
|
||||
|
||||
const externalApiUsages = decodeBqrsToExternalApiUsages(bqrsChunk);
|
||||
|
||||
await this.postMessage({
|
||||
t: "setExternalApiUsages",
|
||||
externalApiUsages,
|
||||
});
|
||||
|
||||
await this.clearProgress();
|
||||
} catch (err) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
this.app.logger,
|
||||
this.app.telemetry,
|
||||
redactableError(
|
||||
asError(err),
|
||||
)`Failed to load external API usages: ${getErrorMessage(err)}`,
|
||||
);
|
||||
}
|
||||
await this.postMessage({
|
||||
t: "setExternalApiUsages",
|
||||
externalApiUsages,
|
||||
});
|
||||
} catch (err) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
this.app.logger,
|
||||
this.app.telemetry,
|
||||
redactableError(
|
||||
asError(err),
|
||||
)`Failed to load external API usages: ${getErrorMessage(err)}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
{ cancellable: false },
|
||||
);
|
||||
}
|
||||
|
||||
protected async generateModeledMethods(): Promise<void> {
|
||||
const tokenSource = new CancellationTokenSource();
|
||||
await withProgress(
|
||||
async (progress) => {
|
||||
const tokenSource = new CancellationTokenSource();
|
||||
|
||||
let addedDatabase: DatabaseItem | undefined;
|
||||
let addedDatabase: DatabaseItem | undefined;
|
||||
|
||||
// In application mode, we need the database of a specific library to generate
|
||||
// the modeled methods. In framework mode, we'll use the current database.
|
||||
if (this.mode === Mode.Application) {
|
||||
const selectedDatabase = this.databaseManager.currentDatabaseItem;
|
||||
|
||||
// The external API methods are in the library source code, so we need to ask
|
||||
// the user to import the library database. We need to have the database
|
||||
// imported to the query server, so we need to register it to our workspace.
|
||||
addedDatabase = await promptImportGithubDatabase(
|
||||
this.app.commands,
|
||||
this.databaseManager,
|
||||
this.app.workspaceStoragePath ?? this.app.globalStoragePath,
|
||||
this.app.credentials,
|
||||
(update) => this.showProgress(update),
|
||||
this.cliServer,
|
||||
);
|
||||
if (!addedDatabase) {
|
||||
await this.clearProgress();
|
||||
void this.app.logger.log("No database chosen");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// The library database was set as the current database by importing it,
|
||||
// but we need to set it back to the originally selected database.
|
||||
await this.databaseManager.setCurrentDatabaseItem(selectedDatabase);
|
||||
}
|
||||
|
||||
await this.showProgress({
|
||||
step: 0,
|
||||
maxStep: 4000,
|
||||
message: "Generating modeled methods for library",
|
||||
});
|
||||
|
||||
try {
|
||||
await generateFlowModel({
|
||||
cliServer: this.cliServer,
|
||||
queryRunner: this.queryRunner,
|
||||
queryStorageDir: this.queryStorageDir,
|
||||
databaseItem: addedDatabase ?? this.databaseItem,
|
||||
onResults: async (modeledMethods) => {
|
||||
const modeledMethodsByName: Record<string, ModeledMethod> = {};
|
||||
|
||||
for (const modeledMethod of modeledMethods) {
|
||||
modeledMethodsByName[modeledMethod.signature] = modeledMethod;
|
||||
// In application mode, we need the database of a specific library to generate
|
||||
// the modeled methods. In framework mode, we'll use the current database.
|
||||
if (this.mode === Mode.Application) {
|
||||
addedDatabase = await this.promptChooseNewOrExistingDatabase(
|
||||
progress,
|
||||
);
|
||||
if (!addedDatabase) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await this.postMessage({
|
||||
t: "addModeledMethods",
|
||||
modeledMethods: modeledMethodsByName,
|
||||
progress({
|
||||
step: 0,
|
||||
maxStep: 4000,
|
||||
message: "Generating modeled methods for library",
|
||||
});
|
||||
|
||||
try {
|
||||
await generateFlowModel({
|
||||
cliServer: this.cliServer,
|
||||
queryRunner: this.queryRunner,
|
||||
queryStorageDir: this.queryStorageDir,
|
||||
databaseItem: addedDatabase ?? this.databaseItem,
|
||||
onResults: async (modeledMethods) => {
|
||||
const modeledMethodsByName: Record<string, ModeledMethod> = {};
|
||||
|
||||
for (const modeledMethod of modeledMethods) {
|
||||
modeledMethodsByName[modeledMethod.signature] = modeledMethod;
|
||||
}
|
||||
|
||||
await this.postMessage({
|
||||
t: "addModeledMethods",
|
||||
modeledMethods: modeledMethodsByName,
|
||||
});
|
||||
},
|
||||
progress,
|
||||
token: tokenSource.token,
|
||||
});
|
||||
},
|
||||
progress: (update) => this.showProgress(update),
|
||||
token: tokenSource.token,
|
||||
});
|
||||
} catch (e: unknown) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
this.app.logger,
|
||||
this.app.telemetry,
|
||||
redactableError(
|
||||
asError(e),
|
||||
)`Failed to generate flow model: ${getErrorMessage(e)}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (addedDatabase) {
|
||||
// After the flow model has been generated, we can remove the temporary database
|
||||
// which we used for generating the flow model.
|
||||
await this.showProgress({
|
||||
step: 3900,
|
||||
maxStep: 4000,
|
||||
message: "Removing temporary database",
|
||||
});
|
||||
await this.databaseManager.removeDatabaseItem(addedDatabase);
|
||||
}
|
||||
|
||||
await this.clearProgress();
|
||||
} catch (e: unknown) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
this.app.logger,
|
||||
this.app.telemetry,
|
||||
redactableError(
|
||||
asError(e),
|
||||
)`Failed to generate flow model: ${getErrorMessage(e)}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
{ cancellable: false },
|
||||
);
|
||||
}
|
||||
|
||||
private async generateModeledMethodsFromLlm(
|
||||
externalApiUsages: ExternalApiUsage[],
|
||||
modeledMethods: Record<string, ModeledMethod>,
|
||||
): Promise<void> {
|
||||
const maxStep = 3000;
|
||||
await withProgress(async (progress) => {
|
||||
const maxStep = 3000;
|
||||
|
||||
await this.showProgress({
|
||||
step: 0,
|
||||
maxStep,
|
||||
message: "Retrieving usages",
|
||||
progress({
|
||||
step: 0,
|
||||
maxStep,
|
||||
message: "Retrieving usages",
|
||||
});
|
||||
|
||||
let predictedModeledMethods: Record<string, ModeledMethod>;
|
||||
|
||||
if (useLlmGenerationV2()) {
|
||||
const usages = await runAutoModelQueries({
|
||||
mode: this.mode,
|
||||
cliServer: this.cliServer,
|
||||
queryRunner: this.queryRunner,
|
||||
queryStorageDir: this.queryStorageDir,
|
||||
databaseItem: this.databaseItem,
|
||||
progress: (update) => progress({ ...update, maxStep }),
|
||||
});
|
||||
if (!usages) {
|
||||
return;
|
||||
}
|
||||
|
||||
progress({
|
||||
step: 1800,
|
||||
maxStep,
|
||||
message: "Creating request",
|
||||
});
|
||||
|
||||
const request = await createAutoModelV2Request(this.mode, usages);
|
||||
|
||||
progress({
|
||||
step: 2000,
|
||||
maxStep,
|
||||
message: "Sending request",
|
||||
});
|
||||
|
||||
const response = await this.callAutoModelApiV2(request);
|
||||
if (!response) {
|
||||
return;
|
||||
}
|
||||
|
||||
progress({
|
||||
step: 2500,
|
||||
maxStep,
|
||||
message: "Parsing response",
|
||||
});
|
||||
|
||||
const models = loadYaml(response.models, {
|
||||
filename: "auto-model.yml",
|
||||
});
|
||||
|
||||
const modeledMethods = loadDataExtensionYaml(models);
|
||||
if (!modeledMethods) {
|
||||
return;
|
||||
}
|
||||
|
||||
predictedModeledMethods = modeledMethods;
|
||||
} else {
|
||||
const usages = await getAutoModelUsages({
|
||||
cliServer: this.cliServer,
|
||||
queryRunner: this.queryRunner,
|
||||
queryStorageDir: this.queryStorageDir,
|
||||
queryDir: this.queryDir,
|
||||
databaseItem: this.databaseItem,
|
||||
progress: (update) => progress({ ...update, maxStep }),
|
||||
});
|
||||
|
||||
progress({
|
||||
step: 1800,
|
||||
maxStep,
|
||||
message: "Creating request",
|
||||
});
|
||||
|
||||
const request = createAutoModelRequest(
|
||||
this.databaseItem.language,
|
||||
externalApiUsages,
|
||||
modeledMethods,
|
||||
usages,
|
||||
this.mode,
|
||||
);
|
||||
|
||||
progress({
|
||||
step: 2000,
|
||||
maxStep,
|
||||
message: "Sending request",
|
||||
});
|
||||
|
||||
const response = await this.callAutoModelApi(request);
|
||||
if (!response) {
|
||||
return;
|
||||
}
|
||||
|
||||
progress({
|
||||
step: 2500,
|
||||
maxStep,
|
||||
message: "Parsing response",
|
||||
});
|
||||
|
||||
predictedModeledMethods = parsePredictedClassifications(
|
||||
response.predicted || [],
|
||||
);
|
||||
}
|
||||
|
||||
progress({
|
||||
step: 2800,
|
||||
maxStep,
|
||||
message: "Applying results",
|
||||
});
|
||||
|
||||
await this.postMessage({
|
||||
t: "addModeledMethods",
|
||||
modeledMethods: predictedModeledMethods,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const usages = await getAutoModelUsages({
|
||||
cliServer: this.cliServer,
|
||||
queryRunner: this.queryRunner,
|
||||
queryStorageDir: this.queryStorageDir,
|
||||
databaseItem: this.databaseItem,
|
||||
progress: (update) => this.showProgress(update, maxStep),
|
||||
private async modelDependency(): Promise<void> {
|
||||
return withProgress(async (progress, token) => {
|
||||
const addedDatabase = await this.promptChooseNewOrExistingDatabase(
|
||||
progress,
|
||||
);
|
||||
if (!addedDatabase || token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
const modelFile = await pickExtensionPack(
|
||||
this.cliServer,
|
||||
addedDatabase,
|
||||
this.app.logger,
|
||||
progress,
|
||||
token,
|
||||
);
|
||||
if (!modelFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
const view = new DataExtensionsEditorView(
|
||||
this.ctx,
|
||||
this.app,
|
||||
this.databaseManager,
|
||||
this.cliServer,
|
||||
this.queryRunner,
|
||||
this.queryStorageDir,
|
||||
this.queryDir,
|
||||
addedDatabase,
|
||||
modelFile,
|
||||
Mode.Framework,
|
||||
);
|
||||
await view.openView();
|
||||
});
|
||||
}
|
||||
|
||||
await this.showProgress({
|
||||
step: 1800,
|
||||
maxStep,
|
||||
message: "Creating request",
|
||||
});
|
||||
|
||||
const request = createAutoModelRequest(
|
||||
this.databaseItem.language,
|
||||
externalApiUsages,
|
||||
modeledMethods,
|
||||
usages,
|
||||
this.mode,
|
||||
private async promptChooseNewOrExistingDatabase(
|
||||
progress: ProgressCallback,
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
const language = this.databaseItem.language;
|
||||
const databases = this.databaseManager.databaseItems.filter(
|
||||
(db) => db.language === language,
|
||||
);
|
||||
if (databases.length === 0) {
|
||||
return await this.promptImportDatabase(progress);
|
||||
} else {
|
||||
const local = {
|
||||
label: "$(database) Use existing database",
|
||||
detail: "Use database from the workspace",
|
||||
};
|
||||
const github = {
|
||||
label: "$(repo) Import database",
|
||||
detail: "Choose database from GitHub",
|
||||
};
|
||||
const newOrExistingDatabase = await window.showQuickPick([local, github]);
|
||||
|
||||
await this.showProgress({
|
||||
step: 2000,
|
||||
maxStep,
|
||||
message: "Sending request",
|
||||
});
|
||||
if (!newOrExistingDatabase) {
|
||||
void this.app.logger.log("No database chosen");
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await this.callAutoModelApi(request);
|
||||
if (!response) {
|
||||
if (newOrExistingDatabase === local) {
|
||||
const pickedDatabase = await window.showQuickPick(
|
||||
databases.map((database) => ({
|
||||
label: database.name,
|
||||
description: database.language,
|
||||
database,
|
||||
})),
|
||||
{
|
||||
placeHolder: "Pick a database",
|
||||
},
|
||||
);
|
||||
if (!pickedDatabase) {
|
||||
void this.app.logger.log("No database chosen");
|
||||
return;
|
||||
}
|
||||
return pickedDatabase.database;
|
||||
} else {
|
||||
return await this.promptImportDatabase(progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async promptImportDatabase(
|
||||
progress: ProgressCallback,
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
// The external API methods are in the library source code, so we need to ask
|
||||
// the user to import the library database. We need to have the database
|
||||
// imported to the query server, so we need to register it to our workspace.
|
||||
const makeSelected = false;
|
||||
const addedDatabase = await promptImportGithubDatabase(
|
||||
this.app.commands,
|
||||
this.databaseManager,
|
||||
this.app.workspaceStoragePath ?? this.app.globalStoragePath,
|
||||
this.app.credentials,
|
||||
progress,
|
||||
this.cliServer,
|
||||
this.databaseItem.language,
|
||||
makeSelected,
|
||||
);
|
||||
if (!addedDatabase) {
|
||||
void this.app.logger.log("No database chosen");
|
||||
return;
|
||||
}
|
||||
|
||||
await this.showProgress({
|
||||
step: 2500,
|
||||
maxStep,
|
||||
message: "Parsing response",
|
||||
});
|
||||
|
||||
const predictedModeledMethods = parsePredictedClassifications(
|
||||
response.predicted || [],
|
||||
);
|
||||
|
||||
await this.showProgress({
|
||||
step: 2800,
|
||||
maxStep,
|
||||
message: "Applying results",
|
||||
});
|
||||
|
||||
await this.postMessage({
|
||||
t: "addModeledMethods",
|
||||
modeledMethods: predictedModeledMethods,
|
||||
});
|
||||
|
||||
await this.clearProgress();
|
||||
}
|
||||
|
||||
/*
|
||||
* Progress in this class is a bit weird. Most of the progress is based on running the query.
|
||||
* Query progress is always between 0 and 1000. However, we still have some steps that need
|
||||
* to be done after the query has finished. Therefore, the maximum step is 1500. This captures
|
||||
* that there's 1000 steps of the query progress since that takes the most time, and then
|
||||
* an additional 500 steps for the rest of the work. The progress doesn't need to be 100%
|
||||
* accurate, so this is just a rough estimate.
|
||||
*
|
||||
* For generating the modeled methods for an external library, the max step is 4000. This is
|
||||
* based on the following steps:
|
||||
* - 1000 for the summary model
|
||||
* - 1000 for the sink model
|
||||
* - 1000 for the source model
|
||||
* - 1000 for the neutral model
|
||||
*/
|
||||
private async showProgress(update: ProgressUpdate, maxStep?: number) {
|
||||
await this.postMessage({
|
||||
t: "showProgress",
|
||||
step: update.step,
|
||||
maxStep: maxStep ?? update.maxStep,
|
||||
message: update.message,
|
||||
});
|
||||
}
|
||||
|
||||
private async clearProgress() {
|
||||
await this.showProgress({
|
||||
step: 0,
|
||||
maxStep: 0,
|
||||
message: "",
|
||||
});
|
||||
return addedDatabase;
|
||||
}
|
||||
|
||||
private async callAutoModelApi(
|
||||
@@ -531,8 +597,25 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
try {
|
||||
return await autoModel(this.app.credentials, request);
|
||||
} catch (e) {
|
||||
await this.clearProgress();
|
||||
if (e instanceof RequestError && e.status === 429) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
this.app.logger,
|
||||
this.app.telemetry,
|
||||
redactableError(e)`Rate limit hit, please try again soon.`,
|
||||
);
|
||||
return null;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async callAutoModelApiV2(
|
||||
request: ModelRequestV2,
|
||||
): Promise<ModelResponseV2 | null> {
|
||||
try {
|
||||
return await autoModelV2(this.app.credentials, request);
|
||||
} catch (e) {
|
||||
if (e instanceof RequestError && e.status === 429) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
this.app.logger,
|
||||
|
||||
@@ -43,6 +43,10 @@ export async function pickExtensionPack(
|
||||
|
||||
// Get all existing extension packs in the workspace
|
||||
const additionalPacks = getOnDiskWorkspaceFolders();
|
||||
// the CLI doesn't check packs in the .github folder, so we need to add it manually
|
||||
if (additionalPacks.length === 1) {
|
||||
additionalPacks.push(`${additionalPacks[0]}/.github`);
|
||||
}
|
||||
const extensionPacksInfo = await cliServer.resolveQlpacks(
|
||||
additionalPacks,
|
||||
true,
|
||||
@@ -88,7 +92,7 @@ export async function pickExtensionPack(
|
||||
|
||||
let extensionPack: ExtensionPack;
|
||||
try {
|
||||
extensionPack = await readExtensionPack(path);
|
||||
extensionPack = await readExtensionPack(path, databaseItem.language);
|
||||
} catch (e: unknown) {
|
||||
void showAndLogErrorMessage(
|
||||
logger,
|
||||
@@ -253,7 +257,10 @@ async function autoCreateExtensionPack(
|
||||
if (existingExtensionPackPaths?.length === 1) {
|
||||
let extensionPack: ExtensionPack;
|
||||
try {
|
||||
extensionPack = await readExtensionPack(existingExtensionPackPaths[0]);
|
||||
extensionPack = await readExtensionPack(
|
||||
existingExtensionPackPaths[0],
|
||||
language,
|
||||
);
|
||||
} catch (e: unknown) {
|
||||
void showAndLogErrorMessage(
|
||||
logger,
|
||||
@@ -317,6 +324,7 @@ async function writeExtensionPack(
|
||||
yamlPath: packYamlPath,
|
||||
name: formatPackName(packName),
|
||||
version: "0.0.0",
|
||||
language,
|
||||
extensionTargets: {
|
||||
[`codeql/${language}-all`]: "*",
|
||||
},
|
||||
@@ -337,7 +345,10 @@ async function writeExtensionPack(
|
||||
return extensionPack;
|
||||
}
|
||||
|
||||
async function readExtensionPack(path: string): Promise<ExtensionPack> {
|
||||
async function readExtensionPack(
|
||||
path: string,
|
||||
language: string,
|
||||
): Promise<ExtensionPack> {
|
||||
const qlpackPath = await getQlPackPath(path);
|
||||
if (!qlpackPath) {
|
||||
throw new Error(
|
||||
@@ -374,6 +385,7 @@ async function readExtensionPack(path: string): Promise<ExtensionPack> {
|
||||
yamlPath: qlpackPath,
|
||||
name: qlpack.name,
|
||||
version: qlpack.version,
|
||||
language,
|
||||
extensionTargets: qlpack.extensionTargets,
|
||||
dataExtensions,
|
||||
};
|
||||
|
||||
@@ -1,71 +1,44 @@
|
||||
import { CoreCompletedQuery, QueryRunner } from "../query-server";
|
||||
import { dir } from "tmp-promise";
|
||||
import { writeFile } from "fs-extra";
|
||||
import { dump as dumpYaml } from "js-yaml";
|
||||
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
||||
import { extLogger } from "../common/logging/vscode";
|
||||
import { showAndLogExceptionWithTelemetry, TeeLogger } from "../common/logging";
|
||||
import { isQueryLanguage } from "../common/query-language";
|
||||
import { CancellationToken } from "vscode";
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { DatabaseItem } from "../databases/local-databases";
|
||||
import { ProgressCallback } from "../common/vscode/progress";
|
||||
import { fetchExternalApiQueries } from "./queries";
|
||||
import { QueryResultType } from "../query-server/new-messages";
|
||||
import { join } from "path";
|
||||
import { redactableError } from "../common/errors";
|
||||
import { telemetryListener } from "../common/vscode/telemetry";
|
||||
import { join } from "path";
|
||||
import { Mode } from "./shared/mode";
|
||||
import { writeFile } from "fs-extra";
|
||||
import { Query } from "./queries/query";
|
||||
import { QueryLanguage } from "../common/query-language";
|
||||
import { dump } from "js-yaml";
|
||||
|
||||
export type RunQueryOptions = {
|
||||
type RunQueryOptions = {
|
||||
cliServer: Pick<CodeQLCliServer, "resolveQlpacks">;
|
||||
queryRunner: Pick<QueryRunner, "createQueryRun" | "logger">;
|
||||
databaseItem: Pick<DatabaseItem, "contents" | "databaseUri" | "language">;
|
||||
queryStorageDir: string;
|
||||
queryDir: string;
|
||||
|
||||
progress: ProgressCallback;
|
||||
token: CancellationToken;
|
||||
};
|
||||
|
||||
export async function runQuery(
|
||||
queryName: keyof Omit<Query, "dependencies">,
|
||||
{
|
||||
cliServer,
|
||||
queryRunner,
|
||||
databaseItem,
|
||||
queryStorageDir,
|
||||
progress,
|
||||
token,
|
||||
}: RunQueryOptions,
|
||||
): Promise<CoreCompletedQuery | undefined> {
|
||||
// The below code is temporary to allow for rapid prototyping of the queries. Once the queries are stabilized, we will
|
||||
// move these queries into the `github/codeql` repository and use them like any other contextual (e.g. AST) queries.
|
||||
// This is intentionally not pretty code, as it will be removed soon.
|
||||
// For a reference of what this should do in the future, see the previous implementation in
|
||||
// https://github.com/github/vscode-codeql/blob/089d3566ef0bc67d9b7cc66e8fd6740b31c1c0b0/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts#L33-L72
|
||||
|
||||
if (!isQueryLanguage(databaseItem.language)) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
extLogger,
|
||||
telemetryListener,
|
||||
redactableError`Unsupported database language ${databaseItem.language}`,
|
||||
export async function setUpPack(
|
||||
queryDir: string,
|
||||
query: Query,
|
||||
language: QueryLanguage,
|
||||
) {
|
||||
Object.values(Mode).map(async (mode) => {
|
||||
const queryFile = join(
|
||||
queryDir,
|
||||
`FetchExternalApis${mode.charAt(0).toUpperCase() + mode.slice(1)}Mode.ql`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const query = fetchExternalApiQueries[databaseItem.language];
|
||||
if (!query) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
extLogger,
|
||||
telemetryListener,
|
||||
redactableError`No external API usage query found for language ${databaseItem.language}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const queryDir = (await dir({ unsafeCleanup: true })).path;
|
||||
const queryFile = join(queryDir, "FetchExternalApis.ql");
|
||||
await writeFile(queryFile, query[queryName], "utf8");
|
||||
await writeFile(queryFile, query[`${mode}ModeQuery`], "utf8");
|
||||
});
|
||||
|
||||
if (query.dependencies) {
|
||||
for (const [filename, contents] of Object.entries(query.dependencies)) {
|
||||
@@ -78,18 +51,42 @@ export async function runQuery(
|
||||
name: "codeql/external-api-usage",
|
||||
version: "0.0.0",
|
||||
dependencies: {
|
||||
[`codeql/${databaseItem.language}-all`]: "*",
|
||||
[`codeql/${language}-all`]: "*",
|
||||
},
|
||||
};
|
||||
|
||||
const qlpackFile = join(queryDir, "codeql-pack.yml");
|
||||
await writeFile(qlpackFile, dumpYaml(syntheticQueryPack), "utf8");
|
||||
await writeFile(qlpackFile, dump(syntheticQueryPack), "utf8");
|
||||
}
|
||||
|
||||
export async function runQuery(
|
||||
mode: Mode,
|
||||
{
|
||||
cliServer,
|
||||
queryRunner,
|
||||
databaseItem,
|
||||
queryStorageDir,
|
||||
queryDir,
|
||||
progress,
|
||||
token,
|
||||
}: RunQueryOptions,
|
||||
): Promise<CoreCompletedQuery | undefined> {
|
||||
// The below code is temporary to allow for rapid prototyping of the queries. Once the queries are stabilized, we will
|
||||
// move these queries into the `github/codeql` repository and use them like any other contextual (e.g. AST) queries.
|
||||
// This is intentionally not pretty code, as it will be removed soon.
|
||||
// For a reference of what this should do in the future, see the previous implementation in
|
||||
// https://github.com/github/vscode-codeql/blob/089d3566ef0bc67d9b7cc66e8fd6740b31c1c0b0/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts#L33-L72
|
||||
|
||||
const additionalPacks = getOnDiskWorkspaceFolders();
|
||||
const extensionPacks = Object.keys(
|
||||
await cliServer.resolveQlpacks(additionalPacks, true),
|
||||
);
|
||||
|
||||
const queryFile = join(
|
||||
queryDir,
|
||||
`FetchExternalApis${mode.charAt(0).toUpperCase() + mode.slice(1)}Mode.ql`,
|
||||
);
|
||||
|
||||
const queryRun = queryRunner.createQueryRun(
|
||||
databaseItem.databaseUri.fsPath,
|
||||
{
|
||||
@@ -125,7 +122,7 @@ export async function runQuery(
|
||||
return completedQuery;
|
||||
}
|
||||
|
||||
export type GetResultsOptions = {
|
||||
type GetResultsOptions = {
|
||||
cliServer: Pick<CodeQLCliServer, "bqrsInfo" | "bqrsDecode">;
|
||||
bqrsPath: string;
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ export enum CallClassification {
|
||||
Generated = "generated",
|
||||
}
|
||||
|
||||
export type Usage = Call & {
|
||||
type Usage = Call & {
|
||||
classification: CallClassification;
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import { QueryResultType } from "../query-server/new-messages";
|
||||
import { file } from "tmp-promise";
|
||||
import { writeFile } from "fs-extra";
|
||||
import { dump } from "js-yaml";
|
||||
import { qlpackOfDatabase } from "../language-support";
|
||||
import { qlpackOfDatabase } from "../local-queries";
|
||||
import { telemetryListener } from "../common/vscode/telemetry";
|
||||
|
||||
type FlowModelOptions = {
|
||||
|
||||
@@ -14,7 +14,7 @@ import { basename, extname } from "../common/path";
|
||||
const semverRegex =
|
||||
/-[v=\s]*(?<version>([0-9]+)(\.([0-9]+)(?:(\.([0-9]+))?(?:[-.]?((?:[0-9]+|\d*[a-zA-Z-][a-zA-Z0-9-]*)(?:\.(?:[0-9]+|\d*[a-zA-Z-][a-zA-Z0-9-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?)?)?)/g;
|
||||
|
||||
export interface Library {
|
||||
interface Library {
|
||||
name: string;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import { outputFile, readFile } from "fs-extra";
|
||||
import { ExternalApiUsage } from "./external-api-usage";
|
||||
import { ModeledMethod } from "./modeled-method";
|
||||
import { Mode } from "./shared/mode";
|
||||
import { createDataExtensionYamls, loadDataExtensionYaml } from "./yaml";
|
||||
import { join, relative } from "path";
|
||||
import { ExtensionPack } from "./shared/extension-pack";
|
||||
import { NotificationLogger, showAndLogErrorMessage } from "../common/logging";
|
||||
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
||||
import { load as loadYaml } from "js-yaml";
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { pathsEqual } from "../common/files";
|
||||
|
||||
export async function saveModeledMethods(
|
||||
extensionPack: ExtensionPack,
|
||||
databaseName: string,
|
||||
language: string,
|
||||
externalApiUsages: ExternalApiUsage[],
|
||||
modeledMethods: Record<string, ModeledMethod>,
|
||||
mode: Mode,
|
||||
cliServer: CodeQLCliServer,
|
||||
logger: NotificationLogger,
|
||||
): Promise<void> {
|
||||
const existingModeledMethods = await loadModeledMethodFiles(
|
||||
extensionPack,
|
||||
cliServer,
|
||||
logger,
|
||||
);
|
||||
|
||||
const yamls = createDataExtensionYamls(
|
||||
databaseName,
|
||||
language,
|
||||
externalApiUsages,
|
||||
modeledMethods,
|
||||
existingModeledMethods,
|
||||
mode,
|
||||
);
|
||||
|
||||
for (const [filename, yaml] of Object.entries(yamls)) {
|
||||
await outputFile(join(extensionPack.path, filename), yaml);
|
||||
}
|
||||
|
||||
void logger.log(`Saved data extension YAML`);
|
||||
}
|
||||
|
||||
async function loadModeledMethodFiles(
|
||||
extensionPack: ExtensionPack,
|
||||
cliServer: CodeQLCliServer,
|
||||
logger: NotificationLogger,
|
||||
): Promise<Record<string, Record<string, ModeledMethod>>> {
|
||||
const modelFiles = await listModelFiles(extensionPack.path, cliServer);
|
||||
|
||||
const modeledMethodsByFile: Record<
|
||||
string,
|
||||
Record<string, ModeledMethod>
|
||||
> = {};
|
||||
|
||||
for (const modelFile of modelFiles) {
|
||||
const yaml = await readFile(join(extensionPack.path, modelFile), "utf8");
|
||||
|
||||
const data = loadYaml(yaml, {
|
||||
filename: modelFile,
|
||||
});
|
||||
|
||||
const modeledMethods = loadDataExtensionYaml(data);
|
||||
if (!modeledMethods) {
|
||||
void showAndLogErrorMessage(
|
||||
logger,
|
||||
`Failed to parse data extension YAML ${modelFile}.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
modeledMethodsByFile[modelFile] = modeledMethods;
|
||||
}
|
||||
|
||||
return modeledMethodsByFile;
|
||||
}
|
||||
|
||||
export async function loadModeledMethods(
|
||||
extensionPack: ExtensionPack,
|
||||
cliServer: CodeQLCliServer,
|
||||
logger: NotificationLogger,
|
||||
): Promise<Record<string, ModeledMethod>> {
|
||||
const existingModeledMethods: Record<string, ModeledMethod> = {};
|
||||
|
||||
const modeledMethodsByFile = await loadModeledMethodFiles(
|
||||
extensionPack,
|
||||
cliServer,
|
||||
logger,
|
||||
);
|
||||
for (const modeledMethods of Object.values(modeledMethodsByFile)) {
|
||||
for (const [key, value] of Object.entries(modeledMethods)) {
|
||||
existingModeledMethods[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return existingModeledMethods;
|
||||
}
|
||||
|
||||
export async function listModelFiles(
|
||||
extensionPackPath: string,
|
||||
cliServer: CodeQLCliServer,
|
||||
): Promise<Set<string>> {
|
||||
const result = await cliServer.resolveExtensions(
|
||||
extensionPackPath,
|
||||
getOnDiskWorkspaceFolders(),
|
||||
);
|
||||
|
||||
const modelFiles = new Set<string>();
|
||||
for (const [path, extensions] of Object.entries(result.data)) {
|
||||
if (pathsEqual(path, extensionPackPath)) {
|
||||
for (const extension of extensions) {
|
||||
modelFiles.add(relative(extensionPackPath, extension.file));
|
||||
}
|
||||
}
|
||||
}
|
||||
return modelFiles;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ export interface ExtensionPack {
|
||||
|
||||
name: string;
|
||||
version: string;
|
||||
language: string;
|
||||
|
||||
extensionTargets: Record<string, string>;
|
||||
dataExtensions: string[];
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
|
||||
import * as dataSchemaJson from "./data-schema.json";
|
||||
import { sanitizeExtensionPackName } from "./extension-pack-name";
|
||||
import { Mode } from "./shared/mode";
|
||||
import { assertNever } from "../common/helpers-pure";
|
||||
|
||||
const ajv = new Ajv({ allErrors: true });
|
||||
const dataSchemaValidate = ajv.compile(dataSchemaJson);
|
||||
@@ -66,30 +68,83 @@ export function createDataExtensionYaml(
|
||||
${extensions.join("\n")}`;
|
||||
}
|
||||
|
||||
export function createDataExtensionYamls(
|
||||
databaseName: string,
|
||||
language: string,
|
||||
externalApiUsages: ExternalApiUsage[],
|
||||
newModeledMethods: Record<string, ModeledMethod>,
|
||||
existingModeledMethods: Record<string, Record<string, ModeledMethod>>,
|
||||
mode: Mode,
|
||||
) {
|
||||
switch (mode) {
|
||||
case Mode.Application:
|
||||
return createDataExtensionYamlsForApplicationMode(
|
||||
language,
|
||||
externalApiUsages,
|
||||
newModeledMethods,
|
||||
existingModeledMethods,
|
||||
);
|
||||
case Mode.Framework:
|
||||
return createDataExtensionYamlsForFrameworkMode(
|
||||
databaseName,
|
||||
language,
|
||||
externalApiUsages,
|
||||
newModeledMethods,
|
||||
existingModeledMethods,
|
||||
);
|
||||
default:
|
||||
assertNever(mode);
|
||||
}
|
||||
}
|
||||
|
||||
export function createDataExtensionYamlsForApplicationMode(
|
||||
language: string,
|
||||
externalApiUsages: ExternalApiUsage[],
|
||||
modeledMethods: Record<string, ModeledMethod>,
|
||||
newModeledMethods: Record<string, ModeledMethod>,
|
||||
existingModeledMethods: Record<string, Record<string, ModeledMethod>>,
|
||||
): Record<string, string> {
|
||||
const methodsByLibraryFilename: Record<string, ModeledMethod[]> = {};
|
||||
const methodsByLibraryFilename: Record<
|
||||
string,
|
||||
Record<string, ModeledMethod>
|
||||
> = {};
|
||||
|
||||
// We only want to generate a yaml file when it's a known external API usage
|
||||
// and there are new modeled methods for it. This avoids us overwriting other
|
||||
// files that may contain data we don't know about.
|
||||
for (const externalApiUsage of externalApiUsages) {
|
||||
const modeledMethod = modeledMethods[externalApiUsage.signature];
|
||||
if (!modeledMethod) {
|
||||
continue;
|
||||
if (externalApiUsage.signature in newModeledMethods) {
|
||||
methodsByLibraryFilename[
|
||||
createFilenameForLibrary(externalApiUsage.library)
|
||||
] = {};
|
||||
}
|
||||
}
|
||||
|
||||
const filename = createFilenameForLibrary(externalApiUsage.library);
|
||||
// First populate methodsByLibraryFilename with any existing modeled methods.
|
||||
for (const [filename, methods] of Object.entries(existingModeledMethods)) {
|
||||
if (filename in methodsByLibraryFilename) {
|
||||
for (const [signature, method] of Object.entries(methods)) {
|
||||
methodsByLibraryFilename[filename][signature] = method;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
methodsByLibraryFilename[filename] =
|
||||
methodsByLibraryFilename[filename] || [];
|
||||
methodsByLibraryFilename[filename].push(modeledMethod);
|
||||
// Add the new modeled methods, potentially overwriting existing modeled methods
|
||||
// but not removing existing modeled methods that are not in the new set.
|
||||
for (const externalApiUsage of externalApiUsages) {
|
||||
const method = newModeledMethods[externalApiUsage.signature];
|
||||
if (method) {
|
||||
const filename = createFilenameForLibrary(externalApiUsage.library);
|
||||
methodsByLibraryFilename[filename][method.signature] = method;
|
||||
}
|
||||
}
|
||||
|
||||
const result: Record<string, string> = {};
|
||||
|
||||
for (const [filename, methods] of Object.entries(methodsByLibraryFilename)) {
|
||||
result[filename] = createDataExtensionYaml(language, methods);
|
||||
result[filename] = createDataExtensionYaml(
|
||||
language,
|
||||
Object.values(methods),
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -99,7 +154,8 @@ export function createDataExtensionYamlsForFrameworkMode(
|
||||
databaseName: string,
|
||||
language: string,
|
||||
externalApiUsages: ExternalApiUsage[],
|
||||
modeledMethods: Record<string, ModeledMethod>,
|
||||
newModeledMethods: Record<string, ModeledMethod>,
|
||||
existingModeledMethods: Record<string, Record<string, ModeledMethod>>,
|
||||
prefix = "models/",
|
||||
suffix = ".model",
|
||||
): Record<string, string> {
|
||||
@@ -108,16 +164,28 @@ export function createDataExtensionYamlsForFrameworkMode(
|
||||
.slice(1)
|
||||
.map((part) => sanitizeExtensionPackName(part))
|
||||
.join("-");
|
||||
const filename = `${prefix}${libraryName}${suffix}.yml`;
|
||||
|
||||
const methods = externalApiUsages
|
||||
.map((externalApiUsage) => modeledMethods[externalApiUsage.signature])
|
||||
.filter((modeledMethod) => modeledMethod !== undefined);
|
||||
const methods: Record<string, ModeledMethod> = {};
|
||||
|
||||
// First populate methodsByLibraryFilename with any existing modeled methods.
|
||||
for (const [signature, method] of Object.entries(
|
||||
existingModeledMethods[filename] || {},
|
||||
)) {
|
||||
methods[signature] = method;
|
||||
}
|
||||
|
||||
// Add the new modeled methods, potentially overwriting existing modeled methods
|
||||
// but not removing existing modeled methods that are not in the new set.
|
||||
for (const externalApiUsage of externalApiUsages) {
|
||||
const modeledMethod = newModeledMethods[externalApiUsage.signature];
|
||||
if (modeledMethod) {
|
||||
methods[modeledMethod.signature] = modeledMethod;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
[`${prefix}${libraryName}${suffix}.yml`]: createDataExtensionYaml(
|
||||
language,
|
||||
methods,
|
||||
),
|
||||
[filename]: createDataExtensionYaml(language, Object.values(methods)),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface DbConfig {
|
||||
selected?: SelectedDbItem;
|
||||
}
|
||||
|
||||
export interface DbConfigDatabases {
|
||||
interface DbConfigDatabases {
|
||||
variantAnalysis: RemoteDbConfig;
|
||||
local: LocalDbConfig;
|
||||
}
|
||||
@@ -31,39 +31,39 @@ export enum SelectedDbItemKind {
|
||||
VariantAnalysisRepository = "variantAnalysisRepository",
|
||||
}
|
||||
|
||||
export interface SelectedLocalUserDefinedList {
|
||||
interface SelectedLocalUserDefinedList {
|
||||
kind: SelectedDbItemKind.LocalUserDefinedList;
|
||||
listName: string;
|
||||
}
|
||||
|
||||
export interface SelectedLocalDatabase {
|
||||
interface SelectedLocalDatabase {
|
||||
kind: SelectedDbItemKind.LocalDatabase;
|
||||
databaseName: string;
|
||||
listName?: string;
|
||||
}
|
||||
|
||||
export interface SelectedRemoteSystemDefinedList {
|
||||
interface SelectedRemoteSystemDefinedList {
|
||||
kind: SelectedDbItemKind.VariantAnalysisSystemDefinedList;
|
||||
listName: string;
|
||||
}
|
||||
|
||||
export interface SelectedVariantAnalysisUserDefinedList {
|
||||
interface SelectedVariantAnalysisUserDefinedList {
|
||||
kind: SelectedDbItemKind.VariantAnalysisUserDefinedList;
|
||||
listName: string;
|
||||
}
|
||||
|
||||
export interface SelectedRemoteOwner {
|
||||
interface SelectedRemoteOwner {
|
||||
kind: SelectedDbItemKind.VariantAnalysisOwner;
|
||||
ownerName: string;
|
||||
}
|
||||
|
||||
export interface SelectedRemoteRepository {
|
||||
interface SelectedRemoteRepository {
|
||||
kind: SelectedDbItemKind.VariantAnalysisRepository;
|
||||
repositoryName: string;
|
||||
listName?: string;
|
||||
}
|
||||
|
||||
export interface RemoteDbConfig {
|
||||
interface RemoteDbConfig {
|
||||
repositoryLists: RemoteRepositoryList[];
|
||||
owners: string[];
|
||||
repositories: string[];
|
||||
@@ -74,7 +74,7 @@ export interface RemoteRepositoryList {
|
||||
repositories: string[];
|
||||
}
|
||||
|
||||
export interface LocalDbConfig {
|
||||
interface LocalDbConfig {
|
||||
lists: LocalList[];
|
||||
databases: LocalDatabase[];
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
} from "../common/github-url-identifier-helper";
|
||||
import { Credentials } from "../common/authentication";
|
||||
import { AppCommandManager } from "../common/commands";
|
||||
import { ALLOW_HTTP_SETTING } from "../config";
|
||||
import { allowHttp } from "../config";
|
||||
import { showAndLogInformationMessage } from "../common/logging";
|
||||
|
||||
/**
|
||||
@@ -85,6 +85,8 @@ export async function promptImportInternetDatabase(
|
||||
* @param credentials the credentials to use to authenticate with GitHub
|
||||
* @param progress the progress callback
|
||||
* @param cli the CodeQL CLI server
|
||||
* @param language the language to download. If undefined, the user will be prompted to choose a language.
|
||||
* @param makeSelected make the new database selected in the databases panel (default: true)
|
||||
*/
|
||||
export async function promptImportGithubDatabase(
|
||||
commandManager: AppCommandManager,
|
||||
@@ -93,6 +95,8 @@ export async function promptImportGithubDatabase(
|
||||
credentials: Credentials | undefined,
|
||||
progress: ProgressCallback,
|
||||
cli?: CodeQLCliServer,
|
||||
language?: string,
|
||||
makeSelected = true,
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
const githubRepo = await askForGitHubRepo(progress);
|
||||
if (!githubRepo) {
|
||||
@@ -106,10 +110,14 @@ export async function promptImportGithubDatabase(
|
||||
credentials,
|
||||
progress,
|
||||
cli,
|
||||
language,
|
||||
makeSelected,
|
||||
);
|
||||
|
||||
if (databaseItem) {
|
||||
await commandManager.execute("codeQLDatabases.focus");
|
||||
if (makeSelected) {
|
||||
await commandManager.execute("codeQLDatabases.focus");
|
||||
}
|
||||
void showAndLogInformationMessage(
|
||||
extLogger,
|
||||
"Database downloaded and imported successfully.",
|
||||
@@ -154,6 +162,7 @@ export async function askForGitHubRepo(
|
||||
* @param progress the progress callback
|
||||
* @param cli the CodeQL CLI server
|
||||
* @param language the language to download. If undefined, the user will be prompted to choose a language.
|
||||
* @param makeSelected make the new database selected in the databases panel (default: true)
|
||||
**/
|
||||
export async function downloadGitHubDatabase(
|
||||
githubRepo: string,
|
||||
@@ -163,6 +172,7 @@ export async function downloadGitHubDatabase(
|
||||
progress: ProgressCallback,
|
||||
cli?: CodeQLCliServer,
|
||||
language?: string,
|
||||
makeSelected = true,
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
const nwo = getNwoFromGitHubUrl(githubRepo) || githubRepo;
|
||||
if (!isValidGitHubNwo(nwo)) {
|
||||
@@ -207,6 +217,7 @@ export async function downloadGitHubDatabase(
|
||||
`${owner}/${name}`,
|
||||
progress,
|
||||
cli,
|
||||
makeSelected,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -265,6 +276,7 @@ export async function importArchiveDatabase(
|
||||
* @param storagePath where to store the unzipped database.
|
||||
* @param nameOverride a name for the database that overrides the default
|
||||
* @param progress callback to send progress messages to
|
||||
* @param makeSelected make the new database selected in the databases panel (default: true)
|
||||
*/
|
||||
async function databaseArchiveFetcher(
|
||||
databaseUrl: string,
|
||||
@@ -274,6 +286,7 @@ async function databaseArchiveFetcher(
|
||||
nameOverride: string | undefined,
|
||||
progress: ProgressCallback,
|
||||
cli?: CodeQLCliServer,
|
||||
makeSelected = true,
|
||||
): Promise<DatabaseItem> {
|
||||
progress({
|
||||
message: "Getting database",
|
||||
@@ -312,8 +325,6 @@ async function databaseArchiveFetcher(
|
||||
});
|
||||
await ensureZippedSourceLocation(dbPath);
|
||||
|
||||
const makeSelected = true;
|
||||
|
||||
const item = await databaseManager.openDatabase(
|
||||
Uri.file(dbPath),
|
||||
makeSelected,
|
||||
@@ -360,7 +371,7 @@ function validateUrl(databaseUrl: string) {
|
||||
throw new Error(`Invalid url: ${databaseUrl}`);
|
||||
}
|
||||
|
||||
if (!ALLOW_HTTP_SETTING.getValue() && uri.scheme !== "https") {
|
||||
if (!allowHttp() && uri.scheme !== "https") {
|
||||
throw new Error("Must use https for downloading a database.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,16 +13,16 @@ export enum ExpandedDbItemKind {
|
||||
RemoteUserDefinedList = "remoteUserDefinedList",
|
||||
}
|
||||
|
||||
export interface RootLocalExpandedDbItem {
|
||||
interface RootLocalExpandedDbItem {
|
||||
kind: ExpandedDbItemKind.RootLocal;
|
||||
}
|
||||
|
||||
export interface LocalUserDefinedListExpandedDbItem {
|
||||
interface LocalUserDefinedListExpandedDbItem {
|
||||
kind: ExpandedDbItemKind.LocalUserDefinedList;
|
||||
listName: string;
|
||||
}
|
||||
|
||||
export interface RootRemoteExpandedDbItem {
|
||||
interface RootRemoteExpandedDbItem {
|
||||
kind: ExpandedDbItemKind.RootRemote;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,20 +11,6 @@ export enum DbItemKind {
|
||||
RemoteRepo = "RemoteRepo",
|
||||
}
|
||||
|
||||
export const remoteDbKinds = [
|
||||
DbItemKind.RootRemote,
|
||||
DbItemKind.RemoteSystemDefinedList,
|
||||
DbItemKind.RemoteUserDefinedList,
|
||||
DbItemKind.RemoteOwner,
|
||||
DbItemKind.RemoteRepo,
|
||||
];
|
||||
|
||||
export const localDbKinds = [
|
||||
DbItemKind.RootLocal,
|
||||
DbItemKind.LocalList,
|
||||
DbItemKind.LocalDatabase,
|
||||
];
|
||||
|
||||
export enum DbListKind {
|
||||
Local = "Local",
|
||||
Remote = "Remote",
|
||||
@@ -103,12 +89,6 @@ export interface RemoteRepoDbItem {
|
||||
parentListName?: string;
|
||||
}
|
||||
|
||||
export function isRemoteSystemDefinedListDbItem(
|
||||
dbItem: DbItem,
|
||||
): dbItem is RemoteSystemDefinedListDbItem {
|
||||
return dbItem.kind === DbItemKind.RemoteSystemDefinedList;
|
||||
}
|
||||
|
||||
export function isRemoteUserDefinedListDbItem(
|
||||
dbItem: DbItem,
|
||||
): dbItem is RemoteUserDefinedListDbItem {
|
||||
@@ -135,7 +115,7 @@ export function isLocalDatabaseDbItem(
|
||||
return dbItem.kind === DbItemKind.LocalDatabase;
|
||||
}
|
||||
|
||||
export type SelectableDbItem = RemoteDbItem | LocalDbItem;
|
||||
type SelectableDbItem = RemoteDbItem | LocalDbItem;
|
||||
|
||||
export function isSelectableDbItem(dbItem: DbItem): dbItem is SelectableDbItem {
|
||||
return SelectableDbItemKinds.includes(dbItem.kind);
|
||||
|
||||
@@ -47,7 +47,7 @@ export interface AddListQuickPickItem extends QuickPickItem {
|
||||
databaseKind: DbListKind;
|
||||
}
|
||||
|
||||
export interface CodeSearchQuickPickItem extends QuickPickItem {
|
||||
interface CodeSearchQuickPickItem extends QuickPickItem {
|
||||
language: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DbItem, DbItemKind, isSelectableDbItem } from "../db-item";
|
||||
|
||||
export type DbTreeViewItemAction =
|
||||
type DbTreeViewItemAction =
|
||||
| "canBeSelected"
|
||||
| "canBeRemoved"
|
||||
| "canBeRenamed"
|
||||
|
||||
@@ -315,6 +315,7 @@ const MIN_VERSION = "1.67.0";
|
||||
*
|
||||
* @returns CodeQLExtensionInterface
|
||||
*/
|
||||
// ts-unused-exports:disable-next-line
|
||||
export async function activate(
|
||||
ctx: ExtensionContext,
|
||||
): Promise<CodeQLExtensionInterface | undefined> {
|
||||
|
||||
@@ -12,16 +12,13 @@ import { CodeQLCliServer } from "../../codeql-cli/cli";
|
||||
import { DatabaseManager, DatabaseItem } from "../../databases/local-databases";
|
||||
import { ProgressCallback } from "../../common/vscode/progress";
|
||||
import { KeyType } from "./key-type";
|
||||
import {
|
||||
qlpackOfDatabase,
|
||||
resolveQueries,
|
||||
runContextualQuery,
|
||||
} from "./query-resolver";
|
||||
import { resolveQueries, runContextualQuery } from "./query-resolver";
|
||||
import { CancellationToken, LocationLink, Uri } from "vscode";
|
||||
import { QueryOutputDir } from "../../run-queries-shared";
|
||||
import { QueryRunner } from "../../query-server";
|
||||
import { QueryResultType } from "../../query-server/new-messages";
|
||||
import { fileRangeFromURI } from "./file-range-from-uri";
|
||||
import { qlpackOfDatabase } from "../../local-queries";
|
||||
|
||||
export const SELECT_QUERY_NAME = "#select";
|
||||
export const SELECTED_SOURCE_FILE = "selectedSourceFile";
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
import { writeFile, promises } from "fs-extra";
|
||||
import { dump } from "js-yaml";
|
||||
import { file } from "tmp-promise";
|
||||
import { basename, dirname, resolve } from "path";
|
||||
|
||||
import { getOnDiskWorkspaceFolders } from "../../common/vscode/workspace-folders";
|
||||
import {
|
||||
getPrimaryDbscheme,
|
||||
getQlPackForDbscheme,
|
||||
QlPacksForLanguage,
|
||||
} from "../../databases/qlpack";
|
||||
import { QlPacksForLanguage } from "../../databases/qlpack";
|
||||
import {
|
||||
KeyType,
|
||||
kindOfKeyType,
|
||||
@@ -17,154 +8,22 @@ import {
|
||||
} from "./key-type";
|
||||
import { CodeQLCliServer } from "../../codeql-cli/cli";
|
||||
import { DatabaseItem } from "../../databases/local-databases";
|
||||
import { resolveQueries as resolveLocalQueries } from "../../local-queries/query-resolver";
|
||||
import { extLogger } from "../../common/logging/vscode";
|
||||
import {
|
||||
showAndLogExceptionWithTelemetry,
|
||||
TeeLogger,
|
||||
} from "../../common/logging";
|
||||
import { TeeLogger } from "../../common/logging";
|
||||
import { CancellationToken } from "vscode";
|
||||
import { ProgressCallback } from "../../common/vscode/progress";
|
||||
import { CoreCompletedQuery, QueryRunner } from "../../query-server";
|
||||
import { redactableError } from "../../common/errors";
|
||||
import { QLPACK_FILENAMES } from "../../common/ql";
|
||||
import { telemetryListener } from "../../common/vscode/telemetry";
|
||||
|
||||
export async function qlpackOfDatabase(
|
||||
cli: Pick<CodeQLCliServer, "resolveQlpacks">,
|
||||
db: Pick<DatabaseItem, "contents">,
|
||||
): Promise<QlPacksForLanguage> {
|
||||
if (db.contents === undefined) {
|
||||
throw new Error("Database is invalid and cannot infer QLPack.");
|
||||
}
|
||||
const datasetPath = db.contents.datasetUri.fsPath;
|
||||
const dbscheme = await getPrimaryDbscheme(datasetPath);
|
||||
return await getQlPackForDbscheme(cli, dbscheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the contextual queries with the specified key in a list of CodeQL packs.
|
||||
*
|
||||
* @param cli The CLI instance to use.
|
||||
* @param qlpacks The list of packs to search.
|
||||
* @param keyType The contextual query key of the query to search for.
|
||||
* @returns The found queries from the first pack in which any matching queries were found.
|
||||
*/
|
||||
async function resolveQueriesFromPacks(
|
||||
cli: CodeQLCliServer,
|
||||
qlpacks: string[],
|
||||
keyType: KeyType,
|
||||
): Promise<string[]> {
|
||||
const suiteFile = (
|
||||
await file({
|
||||
postfix: ".qls",
|
||||
})
|
||||
).path;
|
||||
const suiteYaml = [];
|
||||
for (const qlpack of qlpacks) {
|
||||
suiteYaml.push({
|
||||
from: qlpack,
|
||||
queries: ".",
|
||||
include: {
|
||||
kind: kindOfKeyType(keyType),
|
||||
"tags contain": tagOfKeyType(keyType),
|
||||
},
|
||||
});
|
||||
}
|
||||
await writeFile(suiteFile, dump(suiteYaml), "utf8");
|
||||
|
||||
const queries = await cli.resolveQueriesInSuite(
|
||||
suiteFile,
|
||||
getOnDiskWorkspaceFolders(),
|
||||
);
|
||||
return queries;
|
||||
}
|
||||
import { createLockFileForStandardQuery } from "../../local-queries/standard-queries";
|
||||
|
||||
export async function resolveQueries(
|
||||
cli: CodeQLCliServer,
|
||||
qlpacks: QlPacksForLanguage,
|
||||
keyType: KeyType,
|
||||
): Promise<string[]> {
|
||||
const packsToSearch: string[] = [];
|
||||
|
||||
// The CLI can handle both library packs and query packs, so search both packs in order.
|
||||
packsToSearch.push(qlpacks.dbschemePack);
|
||||
if (qlpacks.queryPack !== undefined) {
|
||||
packsToSearch.push(qlpacks.queryPack);
|
||||
}
|
||||
|
||||
const queries = await resolveQueriesFromPacks(cli, packsToSearch, keyType);
|
||||
if (queries.length > 0) {
|
||||
return queries;
|
||||
}
|
||||
|
||||
// No queries found. Determine the correct error message for the various scenarios.
|
||||
const keyTypeName = nameOfKeyType(keyType);
|
||||
const keyTypeTag = tagOfKeyType(keyType);
|
||||
const joinedPacksToSearch = packsToSearch.join(", ");
|
||||
const error = redactableError`No ${keyTypeName} queries (tagged "${keyTypeTag}") could be found in the \
|
||||
current library path (tried searching the following packs: ${joinedPacksToSearch}). \
|
||||
Try upgrading the CodeQL libraries. If that doesn't work, then ${keyTypeName} queries are not yet available \
|
||||
for this language.`;
|
||||
|
||||
void showAndLogExceptionWithTelemetry(extLogger, telemetryListener, error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
async function resolveContextualQuery(
|
||||
cli: CodeQLCliServer,
|
||||
query: string,
|
||||
): Promise<{ packPath: string; createdTempLockFile: boolean }> {
|
||||
// Contextual queries now live within the standard library packs.
|
||||
// This simplifies distribution (you don't need the standard query pack to use the AST viewer),
|
||||
// but if the library pack doesn't have a lockfile, we won't be able to find
|
||||
// other pack dependencies of the library pack.
|
||||
|
||||
// Work out the enclosing pack.
|
||||
const packContents = await cli.packPacklist(query, false);
|
||||
const packFilePath = packContents.find((p) =>
|
||||
QLPACK_FILENAMES.includes(basename(p)),
|
||||
);
|
||||
if (packFilePath === undefined) {
|
||||
// Should not happen; we already resolved this query.
|
||||
throw new Error(
|
||||
`Could not find a CodeQL pack file for the pack enclosing the contextual query ${query}`,
|
||||
);
|
||||
}
|
||||
const packPath = dirname(packFilePath);
|
||||
const lockFilePath = packContents.find((p) =>
|
||||
["codeql-pack.lock.yml", "qlpack.lock.yml"].includes(basename(p)),
|
||||
);
|
||||
let createdTempLockFile = false;
|
||||
if (!lockFilePath) {
|
||||
// No lock file, likely because this library pack is in the package cache.
|
||||
// Create a lock file so that we can resolve dependencies and library path
|
||||
// for the contextual query.
|
||||
void extLogger.log(
|
||||
`Library pack ${packPath} is missing a lock file; creating a temporary lock file`,
|
||||
);
|
||||
await cli.packResolveDependencies(packPath);
|
||||
createdTempLockFile = true;
|
||||
// Clear CLI server pack cache before installing dependencies,
|
||||
// so that it picks up the new lock file, not the previously cached pack.
|
||||
void extLogger.log("Clearing the CodeQL CLI server's pack cache");
|
||||
await cli.clearCache();
|
||||
// Install dependencies.
|
||||
void extLogger.log(
|
||||
`Installing package dependencies for library pack ${packPath}`,
|
||||
);
|
||||
await cli.packInstall(packPath);
|
||||
}
|
||||
return { packPath, createdTempLockFile };
|
||||
}
|
||||
|
||||
async function removeTemporaryLockFile(packPath: string) {
|
||||
const tempLockFilePath = resolve(packPath, "codeql-pack.lock.yml");
|
||||
void extLogger.log(
|
||||
`Deleting temporary package lock file at ${tempLockFilePath}`,
|
||||
);
|
||||
// It's fine if the file doesn't exist.
|
||||
await promises.rm(resolve(packPath, "codeql-pack.lock.yml"), {
|
||||
force: true,
|
||||
return resolveLocalQueries(cli, qlpacks, nameOfKeyType(keyType), {
|
||||
kind: kindOfKeyType(keyType),
|
||||
"tags contain": [tagOfKeyType(keyType)],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -178,10 +37,7 @@ export async function runContextualQuery(
|
||||
token: CancellationToken,
|
||||
templates: Record<string, string>,
|
||||
): Promise<CoreCompletedQuery> {
|
||||
const { packPath, createdTempLockFile } = await resolveContextualQuery(
|
||||
cli,
|
||||
query,
|
||||
);
|
||||
const { cleanup } = await createLockFileForStandardQuery(cli, query);
|
||||
const queryRun = qs.createQueryRun(
|
||||
db.databaseUri.fsPath,
|
||||
{ queryPath: query, quickEvalPosition: undefined },
|
||||
@@ -200,8 +56,6 @@ export async function runContextualQuery(
|
||||
token,
|
||||
new TeeLogger(qs.logger, queryRun.outputDir.logPath),
|
||||
);
|
||||
if (createdTempLockFile) {
|
||||
await removeTemporaryLockFile(packPath);
|
||||
}
|
||||
await cleanup?.();
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -27,11 +27,7 @@ import {
|
||||
SELECTED_SOURCE_LINE,
|
||||
SELECTED_SOURCE_COLUMN,
|
||||
} from "./location-finder";
|
||||
import {
|
||||
qlpackOfDatabase,
|
||||
resolveQueries,
|
||||
runContextualQuery,
|
||||
} from "./query-resolver";
|
||||
import { resolveQueries, runContextualQuery } from "./query-resolver";
|
||||
import {
|
||||
isCanary,
|
||||
NO_CACHE_AST_VIEWER,
|
||||
@@ -39,6 +35,7 @@ import {
|
||||
} from "../../config";
|
||||
import { CoreCompletedQuery, QueryRunner } from "../../query-server";
|
||||
import { AstBuilder } from "../ast-viewer/ast-builder";
|
||||
import { qlpackOfDatabase } from "../../local-queries";
|
||||
|
||||
/**
|
||||
* Runs templated CodeQL queries to find definitions in
|
||||
|
||||
@@ -20,6 +20,7 @@ export function install() {
|
||||
decreaseIndentPattern: /^((?!.*?\/\*).*\*\/)?\s*[\}\]].*$/,
|
||||
increaseIndentPattern: /^((?!\/\/).)*(\{[^}"'`]*|\([^)"'`]*|\[[^\]"'`]*)$/,
|
||||
};
|
||||
delete langConfig.autoClosingPairs;
|
||||
|
||||
languages.setLanguageConfiguration("ql", langConfig);
|
||||
languages.setLanguageConfiguration("qll", langConfig);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from "./local-queries";
|
||||
export * from "./local-query-run";
|
||||
export * from "./query-resolver";
|
||||
export * from "./quick-eval-code-lens-provider";
|
||||
export * from "./quick-query";
|
||||
export * from "./results-view";
|
||||
|
||||
131
extensions/ql-vscode/src/local-queries/query-resolver.ts
Normal file
131
extensions/ql-vscode/src/local-queries/query-resolver.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { DatabaseItem } from "../databases/local-databases";
|
||||
import {
|
||||
getPrimaryDbscheme,
|
||||
getQlPackForDbscheme,
|
||||
QlPacksForLanguage,
|
||||
} from "../databases/qlpack";
|
||||
import { file } from "tmp-promise";
|
||||
import { writeFile } from "fs-extra";
|
||||
import { dump } from "js-yaml";
|
||||
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
||||
import { redactableError } from "../common/errors";
|
||||
import { showAndLogExceptionWithTelemetry } from "../common/logging";
|
||||
import { extLogger } from "../common/logging/vscode";
|
||||
import { telemetryListener } from "../common/vscode/telemetry";
|
||||
|
||||
export async function qlpackOfDatabase(
|
||||
cli: Pick<CodeQLCliServer, "resolveQlpacks">,
|
||||
db: Pick<DatabaseItem, "contents">,
|
||||
): Promise<QlPacksForLanguage> {
|
||||
if (db.contents === undefined) {
|
||||
throw new Error("Database is invalid and cannot infer QLPack.");
|
||||
}
|
||||
const datasetPath = db.contents.datasetUri.fsPath;
|
||||
const dbscheme = await getPrimaryDbscheme(datasetPath);
|
||||
return await getQlPackForDbscheme(cli, dbscheme);
|
||||
}
|
||||
|
||||
export interface QueryConstraints {
|
||||
kind?: string;
|
||||
"tags contain"?: string[];
|
||||
"tags contain all"?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the queries with the specified kind and tags in a list of CodeQL packs.
|
||||
*
|
||||
* @param cli The CLI instance to use.
|
||||
* @param qlpacks The list of packs to search.
|
||||
* @param constraints Constraints on the queries to search for.
|
||||
* @returns The found queries from the first pack in which any matching queries were found.
|
||||
*/
|
||||
async function resolveQueriesFromPacks(
|
||||
cli: CodeQLCliServer,
|
||||
qlpacks: string[],
|
||||
constraints: QueryConstraints,
|
||||
): Promise<string[]> {
|
||||
const suiteFile = (
|
||||
await file({
|
||||
postfix: ".qls",
|
||||
})
|
||||
).path;
|
||||
const suiteYaml = [];
|
||||
for (const qlpack of qlpacks) {
|
||||
suiteYaml.push({
|
||||
from: qlpack,
|
||||
queries: ".",
|
||||
include: constraints,
|
||||
});
|
||||
}
|
||||
await writeFile(
|
||||
suiteFile,
|
||||
dump(suiteYaml, {
|
||||
noRefs: true, // CodeQL doesn't really support refs
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
return await cli.resolveQueriesInSuite(
|
||||
suiteFile,
|
||||
getOnDiskWorkspaceFolders(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the queries with the specified kind and tags in a QLPack.
|
||||
*
|
||||
* @param cli The CLI instance to use.
|
||||
* @param qlpacks The list of packs to search.
|
||||
* @param name The name of the query to use in error messages.
|
||||
* @param constraints Constraints on the queries to search for.
|
||||
* @returns The found queries from the first pack in which any matching queries were found.
|
||||
*/
|
||||
export async function resolveQueries(
|
||||
cli: CodeQLCliServer,
|
||||
qlpacks: QlPacksForLanguage,
|
||||
name: string,
|
||||
constraints: QueryConstraints,
|
||||
): Promise<string[]> {
|
||||
const packsToSearch: string[] = [];
|
||||
|
||||
// The CLI can handle both library packs and query packs, so search both packs in order.
|
||||
packsToSearch.push(qlpacks.dbschemePack);
|
||||
if (qlpacks.queryPack !== undefined) {
|
||||
packsToSearch.push(qlpacks.queryPack);
|
||||
}
|
||||
|
||||
const queries = await resolveQueriesFromPacks(
|
||||
cli,
|
||||
packsToSearch,
|
||||
constraints,
|
||||
);
|
||||
if (queries.length > 0) {
|
||||
return queries;
|
||||
}
|
||||
|
||||
// No queries found. Determine the correct error message for the various scenarios.
|
||||
const humanConstraints = [];
|
||||
if (constraints.kind !== undefined) {
|
||||
humanConstraints.push(`kind "${constraints.kind}"`);
|
||||
}
|
||||
if (constraints["tags contain"] !== undefined) {
|
||||
humanConstraints.push(`tagged "${constraints["tags contain"].join(" ")}"`);
|
||||
}
|
||||
if (constraints["tags contain all"] !== undefined) {
|
||||
humanConstraints.push(
|
||||
`tagged all of "${constraints["tags contain all"].join(" ")}"`,
|
||||
);
|
||||
}
|
||||
|
||||
const joinedPacksToSearch = packsToSearch.join(", ");
|
||||
const error = redactableError`No ${name} queries (${humanConstraints.join(
|
||||
", ",
|
||||
)}) could be found in the \
|
||||
current library path (tried searching the following packs: ${joinedPacksToSearch}). \
|
||||
Try upgrading the CodeQL libraries. If that doesn't work, then ${name} queries are not yet available \
|
||||
for this language.`;
|
||||
|
||||
void showAndLogExceptionWithTelemetry(extLogger, telemetryListener, error);
|
||||
throw error;
|
||||
}
|
||||
77
extensions/ql-vscode/src/local-queries/standard-queries.ts
Normal file
77
extensions/ql-vscode/src/local-queries/standard-queries.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { QLPACK_FILENAMES, QLPACK_LOCK_FILENAMES } from "../common/ql";
|
||||
import { basename, dirname, resolve } from "path";
|
||||
import { extLogger } from "../common/logging/vscode";
|
||||
import { promises } from "fs-extra";
|
||||
import { BaseLogger } from "../common/logging";
|
||||
|
||||
type LockFileForStandardQueryResult = {
|
||||
cleanup?: () => Promise<void>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a temporary query suite for a given query living within the standard library packs.
|
||||
*
|
||||
* This will create a lock file so the CLI can run the query without having the ql submodule.
|
||||
*/
|
||||
export async function createLockFileForStandardQuery(
|
||||
cli: CodeQLCliServer,
|
||||
queryPath: string,
|
||||
logger: BaseLogger = extLogger,
|
||||
): Promise<LockFileForStandardQueryResult> {
|
||||
// These queries live within the standard library packs.
|
||||
// This simplifies distribution (you don't need the standard query pack to use the AST viewer),
|
||||
// but if the library pack doesn't have a lockfile, we won't be able to find
|
||||
// other pack dependencies of the library pack.
|
||||
|
||||
// Work out the enclosing pack.
|
||||
const packContents = await cli.packPacklist(queryPath, false);
|
||||
const packFilePath = packContents.find((p) =>
|
||||
QLPACK_FILENAMES.includes(basename(p)),
|
||||
);
|
||||
if (packFilePath === undefined) {
|
||||
// Should not happen; we already resolved this query.
|
||||
throw new Error(
|
||||
`Could not find a CodeQL pack file for the pack enclosing the contextual query ${queryPath}`,
|
||||
);
|
||||
}
|
||||
const packPath = dirname(packFilePath);
|
||||
const lockFilePath = packContents.find((p) =>
|
||||
QLPACK_LOCK_FILENAMES.includes(basename(p)),
|
||||
);
|
||||
|
||||
let cleanup: (() => Promise<void>) | undefined = undefined;
|
||||
|
||||
if (!lockFilePath) {
|
||||
// No lock file, likely because this library pack is in the package cache.
|
||||
// Create a lock file so that we can resolve dependencies and library path
|
||||
// for the contextual query.
|
||||
void logger.log(
|
||||
`Library pack ${packPath} is missing a lock file; creating a temporary lock file`,
|
||||
);
|
||||
await cli.packResolveDependencies(packPath);
|
||||
|
||||
cleanup = async () => {
|
||||
const tempLockFilePath = resolve(packPath, "codeql-pack.lock.yml");
|
||||
void logger.log(
|
||||
`Deleting temporary package lock file at ${tempLockFilePath}`,
|
||||
);
|
||||
// It's fine if the file doesn't exist.
|
||||
await promises.rm(resolve(packPath, "codeql-pack.lock.yml"), {
|
||||
force: true,
|
||||
});
|
||||
};
|
||||
|
||||
// Clear CLI server pack cache before installing dependencies,
|
||||
// so that it picks up the new lock file, not the previously cached pack.
|
||||
void logger.log("Clearing the CodeQL CLI server's pack cache");
|
||||
await cli.clearCache();
|
||||
// Install dependencies.
|
||||
void logger.log(
|
||||
`Installing package dependencies for library pack ${packPath}`,
|
||||
);
|
||||
await cli.packInstall(packPath);
|
||||
}
|
||||
|
||||
return { cleanup };
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { SummaryEvent } from "./log-summary";
|
||||
import { readJsonlFile } from "../common/jsonl-reader";
|
||||
import { Disposable } from "../common/disposable-object";
|
||||
|
||||
/**
|
||||
* Callback interface used to report diagnostics from a log scanner.
|
||||
@@ -62,13 +63,6 @@ export interface EvaluationLogScannerProvider {
|
||||
): EvaluationLogScanner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as VSCode's `Disposable`, but avoids a dependency on VS Code.
|
||||
*/
|
||||
export interface Disposable {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export class EvaluationLogScannerSet {
|
||||
private readonly scannerProviders = new Map<
|
||||
number,
|
||||
|
||||
@@ -4,11 +4,11 @@ export interface PipelineRun {
|
||||
duplicationPercentages: number[];
|
||||
}
|
||||
|
||||
export interface Ra {
|
||||
interface Ra {
|
||||
[key: string]: string[];
|
||||
}
|
||||
|
||||
export type EvaluationStrategy =
|
||||
type EvaluationStrategy =
|
||||
| "COMPUTE_SIMPLE"
|
||||
| "COMPUTE_RECURSIVE"
|
||||
| "IN_LAYER"
|
||||
@@ -58,30 +58,30 @@ export interface InLayer extends ResultEventBase {
|
||||
predicateIterationMillis: number[];
|
||||
}
|
||||
|
||||
export interface ComputedExtensional extends ResultEventBase {
|
||||
interface ComputedExtensional extends ResultEventBase {
|
||||
evaluationStrategy: "COMPUTED_EXTENSIONAL";
|
||||
queryCausingWork?: string;
|
||||
}
|
||||
|
||||
export interface NonComputedExtensional extends ResultEventBase {
|
||||
interface NonComputedExtensional extends ResultEventBase {
|
||||
evaluationStrategy: "EXTENSIONAL";
|
||||
queryCausingWork?: string;
|
||||
}
|
||||
|
||||
export interface SentinelEmpty extends SummaryEventBase {
|
||||
interface SentinelEmpty extends SummaryEventBase {
|
||||
evaluationStrategy: "SENTINEL_EMPTY";
|
||||
sentinelRaHash: string;
|
||||
}
|
||||
|
||||
export interface Cachaca extends ResultEventBase {
|
||||
interface Cachaca extends ResultEventBase {
|
||||
evaluationStrategy: "CACHACA";
|
||||
}
|
||||
|
||||
export interface CacheHit extends ResultEventBase {
|
||||
interface CacheHit extends ResultEventBase {
|
||||
evaluationStrategy: "CACHE_HIT";
|
||||
}
|
||||
|
||||
export type Extensional = ComputedExtensional | NonComputedExtensional;
|
||||
type Extensional = ComputedExtensional | NonComputedExtensional;
|
||||
|
||||
export type SummaryEvent =
|
||||
| ComputeSimple
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface PipelineInfo {
|
||||
/**
|
||||
* Location information for a single predicate in the RA.
|
||||
*/
|
||||
export interface PredicateSymbol {
|
||||
interface PredicateSymbol {
|
||||
/**
|
||||
* `PipelineInfo` for each iteration. A non-recursive predicate will have a single iteration `0`.
|
||||
*/
|
||||
|
||||
@@ -10,7 +10,7 @@ import { EOL } from "os";
|
||||
import { containsPath } from "../common/files";
|
||||
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
||||
|
||||
export interface QueryPack {
|
||||
interface QueryPack {
|
||||
path: string;
|
||||
language: QueryLanguage | undefined;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ import {
|
||||
} from "./query-status";
|
||||
import { readQueryHistoryFromFile, writeQueryHistoryToFile } from "./store";
|
||||
import { pathExists } from "fs-extra";
|
||||
import { CliVersionConstraint } from "../codeql-cli/cli";
|
||||
import { HistoryItemLabelProvider } from "./history-item-label-provider";
|
||||
import { ResultsView, WebviewReveal } from "../local-queries";
|
||||
import { EvalLogTreeBuilder, EvalLogViewer } from "../query-evaluation-logging";
|
||||
@@ -95,7 +94,7 @@ const SHOW_QUERY_TEXT_QUICK_EVAL_MSG = `\
|
||||
|
||||
`;
|
||||
|
||||
export enum SortOrder {
|
||||
enum SortOrder {
|
||||
NameAsc = "NameAsc",
|
||||
NameDesc = "NameDesc",
|
||||
DateAsc = "DateAsc",
|
||||
@@ -760,7 +759,7 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
private warnNoEvalLogs() {
|
||||
void showAndLogWarningMessage(
|
||||
this.app.logger,
|
||||
`Evaluator log, summary, and viewer are not available for this run. Perhaps it failed before evaluation, or you are running with a version of CodeQL before ' + ${CliVersionConstraint.CLI_VERSION_WITH_PER_QUERY_EVAL_LOG}?`,
|
||||
`Evaluator log, summary, and viewer are not available for this run. Perhaps it failed before evaluation?`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -953,8 +952,7 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.app.commands.execute(
|
||||
"codeQL.copyVariantAnalysisRepoList",
|
||||
await this.variantAnalysisManager.copyRepoListToClipboard(
|
||||
item.variantAnalysis.id,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { pathExists, stat, remove, readFile } from "fs-extra";
|
||||
import { pathExists, remove, readFile } from "fs-extra";
|
||||
import { EOL } from "os";
|
||||
import { join } from "path";
|
||||
import { Disposable, ExtensionContext } from "vscode";
|
||||
@@ -6,6 +6,7 @@ import { extLogger } from "../common/logging/vscode";
|
||||
import { readDirFullPaths } from "../common/files";
|
||||
import { QueryHistoryDirs } from "./query-history-dirs";
|
||||
import { QueryHistoryManager } from "./query-history-manager";
|
||||
import { getErrorMessage } from "../common/helpers-pure";
|
||||
|
||||
const LAST_SCRUB_TIME_KEY = "lastScrubTime";
|
||||
|
||||
@@ -132,46 +133,55 @@ async function scrubDirectory(
|
||||
errorMsg?: string;
|
||||
deleted: boolean;
|
||||
}> {
|
||||
const timestampFile = join(dir, "timestamp");
|
||||
try {
|
||||
let deleted = true;
|
||||
if (!(await stat(dir)).isDirectory()) {
|
||||
void extLogger.log(` ${dir} is not a directory. Deleting.`);
|
||||
await remove(dir);
|
||||
} else if (!(await pathExists(timestampFile))) {
|
||||
void extLogger.log(` ${dir} has no timestamp file. Deleting.`);
|
||||
await remove(dir);
|
||||
} else if (!(await stat(timestampFile)).isFile()) {
|
||||
void extLogger.log(` ${timestampFile} is not a file. Deleting.`);
|
||||
if (await shouldScrubDirectory(dir, now, maxQueryTime)) {
|
||||
await remove(dir);
|
||||
return { deleted: true };
|
||||
} else {
|
||||
const timestampText = await readFile(timestampFile, "utf8");
|
||||
const timestamp = parseInt(timestampText, 10);
|
||||
|
||||
if (Number.isNaN(timestamp)) {
|
||||
void extLogger.log(
|
||||
` ${dir} has invalid timestamp '${timestampText}'. Deleting.`,
|
||||
);
|
||||
await remove(dir);
|
||||
} else if (now - timestamp > maxQueryTime) {
|
||||
void extLogger.log(
|
||||
` ${dir} is older than ${maxQueryTime / 1000} seconds. Deleting.`,
|
||||
);
|
||||
await remove(dir);
|
||||
} else {
|
||||
void extLogger.log(
|
||||
` ${dir} is not older than ${maxQueryTime / 1000} seconds. Keeping.`,
|
||||
);
|
||||
deleted = false;
|
||||
}
|
||||
return { deleted: false };
|
||||
}
|
||||
return {
|
||||
deleted,
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
errorMsg: ` Could not delete '${dir}': ${err}`,
|
||||
errorMsg: ` Could not delete '${dir}': ${getErrorMessage(err)}`,
|
||||
deleted: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function shouldScrubDirectory(
|
||||
dir: string,
|
||||
now: number,
|
||||
maxQueryTime: number,
|
||||
): Promise<boolean> {
|
||||
const timestamp = await getTimestamp(join(dir, "timestamp"));
|
||||
if (timestamp === undefined || Number.isNaN(timestamp)) {
|
||||
void extLogger.log(` ${dir} timestamp is missing or invalid. Deleting.`);
|
||||
return true;
|
||||
} else if (now - timestamp > maxQueryTime) {
|
||||
void extLogger.log(
|
||||
` ${dir} is older than ${maxQueryTime / 1000} seconds. Deleting.`,
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
void extLogger.log(
|
||||
` ${dir} is not older than ${maxQueryTime / 1000} seconds. Keeping.`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function getTimestamp(
|
||||
timestampFile: string,
|
||||
): Promise<number | undefined> {
|
||||
try {
|
||||
const timestampText = await readFile(timestampFile, "utf8");
|
||||
return parseInt(timestampText, 10);
|
||||
} catch (err) {
|
||||
void extLogger.log(
|
||||
` Could not read timestamp file '${timestampFile}': ${getErrorMessage(
|
||||
err,
|
||||
)}`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,22 +146,11 @@ export class QueryServerClient extends DisposableObject {
|
||||
|
||||
args.push("--require-db-registration");
|
||||
|
||||
if (!(await this.cliServer.cliConstraints.supportsPerQueryEvalLog())) {
|
||||
args.push("--old-eval-stats");
|
||||
}
|
||||
const structuredLogFile = `${this.opts.contextStoragePath}/structured-evaluator-log.json`;
|
||||
await ensureFile(structuredLogFile);
|
||||
|
||||
if (await this.cliServer.cliConstraints.supportsStructuredEvalLog()) {
|
||||
const structuredLogFile = `${this.opts.contextStoragePath}/structured-evaluator-log.json`;
|
||||
await ensureFile(structuredLogFile);
|
||||
|
||||
args.push("--evaluator-log");
|
||||
args.push(structuredLogFile);
|
||||
|
||||
// We hard-code the verbosity level to 5 and minify to false.
|
||||
// This will be the behavior of the per-query structured logging in the CLI after 2.8.3.
|
||||
args.push("--evaluator-log-level");
|
||||
args.push("5");
|
||||
}
|
||||
args.push("--evaluator-log");
|
||||
args.push(structuredLogFile);
|
||||
|
||||
if (this.config.debug) {
|
||||
args.push("--debug", "--tuple-counting");
|
||||
|
||||
@@ -129,10 +129,7 @@ async function runQuery(
|
||||
dbDir: dbContents.datasetUri.fsPath,
|
||||
workingSet: "default",
|
||||
};
|
||||
if (
|
||||
generateEvalLog &&
|
||||
(await qs.cliServer.cliConstraints.supportsPerQueryEvalLog())
|
||||
) {
|
||||
if (generateEvalLog) {
|
||||
await qs.sendRequest(messages.startLog, {
|
||||
db: dataset,
|
||||
logPath: outputDir.evalLogPath,
|
||||
@@ -149,10 +146,7 @@ async function runQuery(
|
||||
await qs.sendRequest(messages.runQueries, params, token, progress);
|
||||
} finally {
|
||||
qs.unRegisterCallback(callbackId);
|
||||
if (
|
||||
generateEvalLog &&
|
||||
(await qs.cliServer.cliConstraints.supportsPerQueryEvalLog())
|
||||
) {
|
||||
if (generateEvalLog) {
|
||||
await qs.sendRequest(messages.endLog, {
|
||||
db: dataset,
|
||||
logPath: outputDir.evalLogPath,
|
||||
|
||||
@@ -194,11 +194,6 @@ export class QueryServerClient extends DisposableObject {
|
||||
args.push("--evaluator-log");
|
||||
args.push(structuredLogFile);
|
||||
|
||||
// We hard-code the verbosity level to 5 and minify to false.
|
||||
// This will be the behavior of the per-query structured logging in the CLI after 2.8.3.
|
||||
args.push("--evaluator-log-level");
|
||||
args.push("5");
|
||||
|
||||
if (this.config.debug) {
|
||||
args.push("--debug", "--tuple-counting");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { dirname, extname } from "path";
|
||||
import { extname } from "path";
|
||||
import * as vscode from "vscode";
|
||||
import {
|
||||
TestAdapter,
|
||||
@@ -41,14 +41,6 @@ export function getActualFile(testPath: string): string {
|
||||
return getTestOutputFile(testPath, ".actual");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the directory containing the specified QL test.
|
||||
* @param testPath The full path to the test file.
|
||||
*/
|
||||
export function getTestDirectory(testPath: string): string {
|
||||
return dirname(testPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the the full path to a particular output file of the specified QL test.
|
||||
* @param testPath The full path to the QL test.
|
||||
|
||||
@@ -7,7 +7,7 @@ import { basename } from "path";
|
||||
import { App } from "../common/app";
|
||||
import { TestTreeNode } from "./test-tree-node";
|
||||
|
||||
export type TestNode = TestTreeNode | TestItem;
|
||||
type TestNode = TestTreeNode | TestItem;
|
||||
|
||||
/**
|
||||
* Base class for both the legacy and new test services. Implements commands that are common to
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react";
|
||||
|
||||
import { VariantAnalysisContainer } from "../../view/variant-analysis/VariantAnalysisContainer";
|
||||
@@ -16,9 +16,9 @@ export default {
|
||||
</VariantAnalysisContainer>
|
||||
),
|
||||
],
|
||||
} as ComponentMeta<typeof Alert>;
|
||||
} as Meta<typeof Alert>;
|
||||
|
||||
const Template: ComponentStory<typeof Alert> = (args) => <Alert {...args} />;
|
||||
const Template: StoryFn<typeof Alert> = (args) => <Alert {...args} />;
|
||||
|
||||
export const Warning = Template.bind({});
|
||||
Warning.args = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { CodePaths } from "../../view/common";
|
||||
import type { CodeFlow } from "../../variant-analysis/shared/analysis-result";
|
||||
@@ -9,11 +9,9 @@ export default {
|
||||
title: "Code Paths",
|
||||
component: CodePaths,
|
||||
decorators: [(Story) => <Story />],
|
||||
} as ComponentMeta<typeof CodePaths>;
|
||||
} as Meta<typeof CodePaths>;
|
||||
|
||||
const Template: ComponentStory<typeof CodePaths> = (args) => (
|
||||
<CodePaths {...args} />
|
||||
);
|
||||
const Template: StoryFn<typeof CodePaths> = (args) => <CodePaths {...args} />;
|
||||
|
||||
export const PowerShell = Template.bind({});
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { FileCodeSnippet } from "../../view/common";
|
||||
|
||||
export default {
|
||||
title: "File Code Snippet",
|
||||
component: FileCodeSnippet,
|
||||
} as ComponentMeta<typeof FileCodeSnippet>;
|
||||
} as Meta<typeof FileCodeSnippet>;
|
||||
|
||||
const Template: ComponentStory<typeof FileCodeSnippet> = (args) => (
|
||||
const Template: StoryFn<typeof FileCodeSnippet> = (args) => (
|
||||
<FileCodeSnippet {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
|
||||
import { LastUpdated as LastUpdatedComponent } from "../../view/common/LastUpdated";
|
||||
|
||||
export default {
|
||||
title: "Last Updated",
|
||||
component: LastUpdatedComponent,
|
||||
} as ComponentMeta<typeof LastUpdatedComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof LastUpdatedComponent> = (args) => (
|
||||
<LastUpdatedComponent {...args} />
|
||||
);
|
||||
|
||||
export const LastUpdated = Template.bind({});
|
||||
|
||||
LastUpdated.args = {
|
||||
lastUpdated: new Date(Date.now() - 3_600_000).toISOString(), // 1 hour ago
|
||||
};
|
||||
@@ -1,15 +1,15 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import StarCountComponent from "../../view/common/StarCount";
|
||||
|
||||
export default {
|
||||
title: "Star Count",
|
||||
component: StarCountComponent,
|
||||
} as ComponentMeta<typeof StarCountComponent>;
|
||||
} as Meta<typeof StarCountComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof StarCountComponent> = (args) => (
|
||||
const Template: StoryFn<typeof StarCountComponent> = (args) => (
|
||||
<StarCountComponent {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import TextButtonComponent from "../../view/common/TextButton";
|
||||
|
||||
@@ -15,9 +15,9 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof TextButtonComponent>;
|
||||
} as Meta<typeof TextButtonComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof TextButtonComponent> = (args) => (
|
||||
const Template: StoryFn<typeof TextButtonComponent> = (args) => (
|
||||
<TextButtonComponent {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { CodePaths, Codicon as CodiconComponent } from "../../../view/common";
|
||||
import { Codicon as CodiconComponent } from "../../../view/common";
|
||||
|
||||
// To regenerate the icons, use the following command from the `extensions/ql-vscode` directory:
|
||||
// jq -R '[inputs | [splits(", *")] as $row | $row[0]]' < node_modules/@vscode/codicons/dist/codicon.csv > src/stories/common/icon/vscode-icons.json
|
||||
@@ -17,9 +17,9 @@ export default {
|
||||
options: icons,
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof CodePaths>;
|
||||
} as Meta<typeof CodiconComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof CodiconComponent> = (args) => (
|
||||
const Template: StoryFn<typeof CodiconComponent> = (args) => (
|
||||
<CodiconComponent {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import {
|
||||
CodePaths,
|
||||
@@ -10,9 +10,9 @@ import {
|
||||
export default {
|
||||
title: "Icon/Error Icon",
|
||||
component: ErrorIconComponent,
|
||||
} as ComponentMeta<typeof CodePaths>;
|
||||
} as Meta<typeof CodePaths>;
|
||||
|
||||
const Template: ComponentStory<typeof ErrorIconComponent> = (args) => (
|
||||
const Template: StoryFn<typeof ErrorIconComponent> = (args) => (
|
||||
<ErrorIconComponent {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import {
|
||||
CodePaths,
|
||||
@@ -10,9 +10,9 @@ import {
|
||||
export default {
|
||||
title: "Icon/Success Icon",
|
||||
component: SuccessIconComponent,
|
||||
} as ComponentMeta<typeof CodePaths>;
|
||||
} as Meta<typeof CodePaths>;
|
||||
|
||||
const Template: ComponentStory<typeof SuccessIconComponent> = (args) => (
|
||||
const Template: StoryFn<typeof SuccessIconComponent> = (args) => (
|
||||
<SuccessIconComponent {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import {
|
||||
CodePaths,
|
||||
@@ -10,9 +10,9 @@ import {
|
||||
export default {
|
||||
title: "Icon/Warning Icon",
|
||||
component: WarningIconComponent,
|
||||
} as ComponentMeta<typeof CodePaths>;
|
||||
} as Meta<typeof CodePaths>;
|
||||
|
||||
const Template: ComponentStory<typeof WarningIconComponent> = (args) => (
|
||||
const Template: StoryFn<typeof WarningIconComponent> = (args) => (
|
||||
<WarningIconComponent {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { Mode } from "../../data-extensions-editor/shared/mode";
|
||||
import { DataExtensionsEditor as DataExtensionsEditorComponent } from "../../view/data-extensions-editor/DataExtensionsEditor";
|
||||
@@ -9,11 +9,11 @@ import { CallClassification } from "../../data-extensions-editor/external-api-us
|
||||
export default {
|
||||
title: "Data Extensions Editor/Data Extensions Editor",
|
||||
component: DataExtensionsEditorComponent,
|
||||
} as ComponentMeta<typeof DataExtensionsEditorComponent>;
|
||||
} as Meta<typeof DataExtensionsEditorComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof DataExtensionsEditorComponent> = (
|
||||
args,
|
||||
) => <DataExtensionsEditorComponent {...args} />;
|
||||
const Template: StoryFn<typeof DataExtensionsEditorComponent> = (args) => (
|
||||
<DataExtensionsEditorComponent {...args} />
|
||||
);
|
||||
|
||||
export const DataExtensionsEditor = Template.bind({});
|
||||
DataExtensionsEditor.args = {
|
||||
@@ -24,6 +24,7 @@ DataExtensionsEditor.args = {
|
||||
"/home/user/vscode-codeql-starter/codeql-custom-queries-java/sql2o/codeql-pack.yml",
|
||||
name: "codeql/sql2o-models",
|
||||
version: "0.0.0",
|
||||
language: "java",
|
||||
extensionTargets: {},
|
||||
dataExtensions: [],
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { MethodRow as MethodRowComponent } from "../../view/data-extensions-editor/MethodRow";
|
||||
import { CallClassification } from "../../data-extensions-editor/external-api-usage";
|
||||
@@ -8,9 +8,9 @@ import { CallClassification } from "../../data-extensions-editor/external-api-us
|
||||
export default {
|
||||
title: "Data Extensions Editor/Method Row",
|
||||
component: MethodRowComponent,
|
||||
} as ComponentMeta<typeof MethodRowComponent>;
|
||||
} as Meta<typeof MethodRowComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof MethodRowComponent> = (args) => (
|
||||
const Template: StoryFn<typeof MethodRowComponent> = (args) => (
|
||||
<MethodRowComponent {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { DataFlowPaths as DataFlowPathsComponent } from "../../view/data-flow-paths/DataFlowPaths";
|
||||
import { createMockDataFlowPaths } from "../../../test/factories/variant-analysis/shared/data-flow-paths";
|
||||
export default {
|
||||
title: "Data Flow Paths/Data Flow Paths",
|
||||
component: DataFlowPathsComponent,
|
||||
} as ComponentMeta<typeof DataFlowPathsComponent>;
|
||||
} as Meta<typeof DataFlowPathsComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof DataFlowPathsComponent> = (args) => (
|
||||
const Template: StoryFn<typeof DataFlowPathsComponent> = (args) => (
|
||||
<DataFlowPathsComponent {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import AnalysisAlertResult from "../../view/variant-analysis/AnalysisAlertResult";
|
||||
import type { AnalysisAlert } from "../../variant-analysis/shared/analysis-result";
|
||||
@@ -8,9 +8,9 @@ import type { AnalysisAlert } from "../../variant-analysis/shared/analysis-resul
|
||||
export default {
|
||||
title: "Variant Analysis/Analysis Alert Result",
|
||||
component: AnalysisAlertResult,
|
||||
} as ComponentMeta<typeof AnalysisAlertResult>;
|
||||
} as Meta<typeof AnalysisAlertResult>;
|
||||
|
||||
const Template: ComponentStory<typeof AnalysisAlertResult> = (args) => (
|
||||
const Template: StoryFn<typeof AnalysisAlertResult> = (args) => (
|
||||
<AnalysisAlertResult {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
import { VariantAnalysisFailureReason } from "../../variant-analysis/shared/variant-analysis";
|
||||
import { FailureReasonAlert } from "../../view/variant-analysis/FailureReasonAlert";
|
||||
|
||||
export default {
|
||||
title: "Variant Analysis/Failure reason alert",
|
||||
component: FailureReasonAlert,
|
||||
} as ComponentMeta<typeof FailureReasonAlert>;
|
||||
} as Meta<typeof FailureReasonAlert>;
|
||||
|
||||
const Template: ComponentStory<typeof FailureReasonAlert> = (args) => (
|
||||
const Template: StoryFn<typeof FailureReasonAlert> = (args) => (
|
||||
<FailureReasonAlert {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { VariantAnalysisContainer } from "../../view/variant-analysis/VariantAnalysisContainer";
|
||||
import { QueryDetails as QueryDetailsComponent } from "../../view/variant-analysis/QueryDetails";
|
||||
@@ -29,9 +29,9 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof QueryDetailsComponent>;
|
||||
} as Meta<typeof QueryDetailsComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof QueryDetailsComponent> = (args) => (
|
||||
const Template: StoryFn<typeof QueryDetailsComponent> = (args) => (
|
||||
<QueryDetailsComponent {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { VariantAnalysisContainer } from "../../view/variant-analysis/VariantAnalysisContainer";
|
||||
import {
|
||||
@@ -27,9 +27,9 @@ export default {
|
||||
</VariantAnalysisContainer>
|
||||
),
|
||||
],
|
||||
} as ComponentMeta<typeof RepoRow>;
|
||||
} as Meta<typeof RepoRow>;
|
||||
|
||||
const Template: ComponentStory<typeof RepoRow> = (args: RepoRowProps) => (
|
||||
const Template: StoryFn<typeof RepoRow> = (args: RepoRowProps) => (
|
||||
<RepoRow {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { ComponentMeta } from "@storybook/react";
|
||||
import { Meta } from "@storybook/react";
|
||||
|
||||
import { RepositoriesFilter as RepositoriesFilterComponent } from "../../view/variant-analysis/RepositoriesFilter";
|
||||
import { FilterKey } from "../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
@@ -16,7 +16,7 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof RepositoriesFilterComponent>;
|
||||
} as Meta<typeof RepositoriesFilterComponent>;
|
||||
|
||||
export const RepositoriesFilter = () => {
|
||||
const [value, setValue] = useState(FilterKey.All);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { ComponentMeta } from "@storybook/react";
|
||||
import { Meta } from "@storybook/react";
|
||||
|
||||
import { RepositoriesSearch as RepositoriesSearchComponent } from "../../view/variant-analysis/RepositoriesSearch";
|
||||
|
||||
@@ -15,7 +15,7 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof RepositoriesSearchComponent>;
|
||||
} as Meta<typeof RepositoriesSearchComponent>;
|
||||
|
||||
export const RepositoriesSearch = () => {
|
||||
const [value, setValue] = useState("");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { ComponentMeta } from "@storybook/react";
|
||||
import { Meta } from "@storybook/react";
|
||||
|
||||
import { RepositoriesSearchSortRow as RepositoriesSearchSortRowComponent } from "../../view/variant-analysis/RepositoriesSearchSortRow";
|
||||
import { defaultFilterSortState } from "../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
@@ -16,7 +16,7 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof RepositoriesSearchSortRowComponent>;
|
||||
} as Meta<typeof RepositoriesSearchSortRowComponent>;
|
||||
|
||||
export const RepositoriesSearchSortRow = () => {
|
||||
const [value, setValue] = useState(defaultFilterSortState);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { ComponentMeta } from "@storybook/react";
|
||||
import { Meta } from "@storybook/react";
|
||||
|
||||
import { RepositoriesSort as RepositoriesSortComponent } from "../../view/variant-analysis/RepositoriesSort";
|
||||
import { SortKey } from "../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
@@ -16,7 +16,7 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof RepositoriesSortComponent>;
|
||||
} as Meta<typeof RepositoriesSortComponent>;
|
||||
|
||||
export const RepositoriesSort = () => {
|
||||
const [value, setValue] = useState(SortKey.Alphabetically);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { VariantAnalysis as VariantAnalysisComponent } from "../../view/variant-analysis/VariantAnalysis";
|
||||
import {
|
||||
@@ -18,9 +18,9 @@ import { createMockRepositoryWithMetadata } from "../../../test/factories/varian
|
||||
export default {
|
||||
title: "Variant Analysis/Variant Analysis",
|
||||
component: VariantAnalysisComponent,
|
||||
} as ComponentMeta<typeof VariantAnalysisComponent>;
|
||||
} as Meta<typeof VariantAnalysisComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof VariantAnalysisComponent> = (args) => (
|
||||
const Template: StoryFn<typeof VariantAnalysisComponent> = (args) => (
|
||||
<VariantAnalysisComponent {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { VariantAnalysisContainer } from "../../view/variant-analysis/VariantAnalysisContainer";
|
||||
import { VariantAnalysisStatus } from "../../variant-analysis/shared/variant-analysis";
|
||||
@@ -36,9 +36,9 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof VariantAnalysisActions>;
|
||||
} as Meta<typeof VariantAnalysisActions>;
|
||||
|
||||
const Template: ComponentStory<typeof VariantAnalysisActions> = (args) => (
|
||||
const Template: StoryFn<typeof VariantAnalysisActions> = (args) => (
|
||||
<VariantAnalysisActions {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { faker } from "@faker-js/faker";
|
||||
|
||||
@@ -28,11 +28,11 @@ export default {
|
||||
</VariantAnalysisContainer>
|
||||
),
|
||||
],
|
||||
} as ComponentMeta<typeof VariantAnalysisAnalyzedRepos>;
|
||||
} as Meta<typeof VariantAnalysisAnalyzedRepos>;
|
||||
|
||||
const Template: ComponentStory<typeof VariantAnalysisAnalyzedRepos> = (
|
||||
args,
|
||||
) => <VariantAnalysisAnalyzedRepos {...args} />;
|
||||
const Template: StoryFn<typeof VariantAnalysisAnalyzedRepos> = (args) => (
|
||||
<VariantAnalysisAnalyzedRepos {...args} />
|
||||
);
|
||||
|
||||
const interpretedResultsForRepo = (
|
||||
nwo: string,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { VariantAnalysisContainer } from "../../view/variant-analysis/VariantAnalysisContainer";
|
||||
import { VariantAnalysisHeader } from "../../view/variant-analysis/VariantAnalysisHeader";
|
||||
@@ -59,9 +59,9 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof VariantAnalysisHeader>;
|
||||
} as Meta<typeof VariantAnalysisHeader>;
|
||||
|
||||
const Template: ComponentStory<typeof VariantAnalysisHeader> = (args) => (
|
||||
const Template: StoryFn<typeof VariantAnalysisHeader> = (args) => (
|
||||
<VariantAnalysisHeader {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { VariantAnalysisContainer } from "../../view/variant-analysis/VariantAnalysisContainer";
|
||||
import { VariantAnalysisLoading as VariantAnalysisLoadingComponent } from "../../view/variant-analysis/VariantAnalysisLoading";
|
||||
@@ -16,9 +16,9 @@ export default {
|
||||
),
|
||||
],
|
||||
argTypes: {},
|
||||
} as ComponentMeta<typeof VariantAnalysisLoadingComponent>;
|
||||
} as Meta<typeof VariantAnalysisLoadingComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof VariantAnalysisLoadingComponent> = () => (
|
||||
const Template: StoryFn<typeof VariantAnalysisLoadingComponent> = () => (
|
||||
<VariantAnalysisLoadingComponent />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { VariantAnalysisContainer } from "../../view/variant-analysis/VariantAnalysisContainer";
|
||||
import { VariantAnalysisOutcomePanels } from "../../view/variant-analysis/VariantAnalysisOutcomePanels";
|
||||
@@ -27,11 +27,9 @@ export default {
|
||||
</VariantAnalysisContainer>
|
||||
),
|
||||
],
|
||||
} as ComponentMeta<typeof VariantAnalysisOutcomePanels>;
|
||||
} as Meta<typeof VariantAnalysisOutcomePanels>;
|
||||
|
||||
const Template: ComponentStory<typeof VariantAnalysisOutcomePanels> = (
|
||||
args,
|
||||
) => {
|
||||
const Template: StoryFn<typeof VariantAnalysisOutcomePanels> = (args) => {
|
||||
const [filterSortState, setFilterSortState] =
|
||||
useState<RepositoriesFilterSortState>(defaultFilterSortState);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { VariantAnalysisContainer } from "../../view/variant-analysis/VariantAnalysisContainer";
|
||||
import { VariantAnalysisSkippedRepositoriesTab } from "../../view/variant-analysis/VariantAnalysisSkippedRepositoriesTab";
|
||||
@@ -16,9 +16,9 @@ export default {
|
||||
</VariantAnalysisContainer>
|
||||
),
|
||||
],
|
||||
} as ComponentMeta<typeof VariantAnalysisSkippedRepositoriesTab>;
|
||||
} as Meta<typeof VariantAnalysisSkippedRepositoriesTab>;
|
||||
|
||||
const Template: ComponentStory<typeof VariantAnalysisSkippedRepositoriesTab> = (
|
||||
const Template: StoryFn<typeof VariantAnalysisSkippedRepositoriesTab> = (
|
||||
args,
|
||||
) => <VariantAnalysisSkippedRepositoriesTab {...args} />;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { VariantAnalysisContainer } from "../../view/variant-analysis/VariantAnalysisContainer";
|
||||
import { VariantAnalysisStats } from "../../view/variant-analysis/VariantAnalysisStats";
|
||||
@@ -24,9 +24,9 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof VariantAnalysisStats>;
|
||||
} as Meta<typeof VariantAnalysisStats>;
|
||||
|
||||
const Template: ComponentStory<typeof VariantAnalysisStats> = (args) => (
|
||||
const Template: StoryFn<typeof VariantAnalysisStats> = (args) => (
|
||||
<VariantAnalysisStats {...args} />
|
||||
);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user