init commit
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2
|
||||
# Reference: https://github.com/microsoft/vscode/wiki/How-to-Contribute
|
||||
properties:
|
||||
resources:
|
||||
- resource: Microsoft.WinGet.DSC/WinGetPackage
|
||||
directives:
|
||||
description: Install Git
|
||||
allowPrerelease: true
|
||||
settings:
|
||||
id: Git.Git
|
||||
source: winget
|
||||
- resource: Microsoft.WinGet.DSC/WinGetPackage
|
||||
id: npm
|
||||
directives:
|
||||
description: Install NodeJS version 20
|
||||
allowPrerelease: true
|
||||
settings:
|
||||
id: OpenJS.NodeJS.LTS
|
||||
version: "20.14.0"
|
||||
source: winget
|
||||
- resource: NpmDsc/NpmPackage
|
||||
id: yarn
|
||||
dependsOn:
|
||||
- npm
|
||||
directives:
|
||||
description: Install Yarn
|
||||
allowPrerelease: true
|
||||
settings:
|
||||
Name: 'yarn'
|
||||
Global: true
|
||||
PackageDirectory: '${WinGetConfigRoot}\..\'
|
||||
- resource: Microsoft.WinGet.DSC/WinGetPackage
|
||||
directives:
|
||||
description: Install Python 3.10
|
||||
allowPrerelease: true
|
||||
settings:
|
||||
id: Python.Python.3.10
|
||||
source: winget
|
||||
- resource: Microsoft.WinGet.DSC/WinGetPackage
|
||||
id: vsPackage
|
||||
directives:
|
||||
description: Install Visual Studio 2022 (any edition is OK)
|
||||
allowPrerelease: true
|
||||
settings:
|
||||
id: Microsoft.VisualStudio.2022.BuildTools
|
||||
source: winget
|
||||
- resource: Microsoft.VisualStudio.DSC/VSComponents
|
||||
dependsOn:
|
||||
- vsPackage
|
||||
directives:
|
||||
description: Install required VS workloads
|
||||
allowPrerelease: true
|
||||
settings:
|
||||
productId: Microsoft.VisualStudio.Product.BuildTools
|
||||
channelId: VisualStudio.17.Release
|
||||
includeRecommended: true
|
||||
components:
|
||||
- Microsoft.VisualStudio.Workload.VCTools
|
||||
- resource: YarnDsc/YarnInstall
|
||||
dependsOn:
|
||||
- npm
|
||||
directives:
|
||||
description: Install dependencies
|
||||
allowPrerelease: true
|
||||
settings:
|
||||
PackageDirectory: '${WinGetConfigRoot}\..\'
|
||||
configurationVersion: 0.2.0
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"alias": false,
|
||||
"resolveExtensions": [
|
||||
".js",
|
||||
".jsx",
|
||||
".ts",
|
||||
".tsx",
|
||||
".vue",
|
||||
".scss",
|
||||
".less"
|
||||
],
|
||||
"entryFilePath": "/src/vs/code/electron-main/main.ts"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
FROM mcr.microsoft.com/devcontainers/typescript-node:20-bookworm
|
||||
|
||||
ADD install-vscode.sh /root/
|
||||
RUN /root/install-vscode.sh
|
||||
|
||||
RUN git config --system codespaces-theme.hide-status 1
|
||||
|
||||
USER node
|
||||
RUN npm install -g node-gyp
|
||||
RUN YARN_CACHE="$(yarn cache dir)" && rm -rf "$YARN_CACHE" && ln -s /vscode-dev/yarn-cache "$YARN_CACHE"
|
||||
RUN echo 'export DISPLAY="${DISPLAY:-:1}"' | tee -a ~/.bashrc >> ~/.zshrc
|
||||
|
||||
USER root
|
||||
CMD chown node:node /vscode-dev && sudo -u node mkdir -p /vscode-dev/yarn-cache && sleep inf
|
||||
@@ -0,0 +1,112 @@
|
||||
# Code - OSS Development Container
|
||||
|
||||
[](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/vscode)
|
||||
|
||||
This repository includes configuration for a development container for working with Code - OSS in a local container or using [GitHub Codespaces](https://github.com/features/codespaces).
|
||||
|
||||
> **Tip:** The default VNC password is `vscode`. The VNC server runs on port `5901` and a web client is available on port `6080`.
|
||||
|
||||
## Quick start - local
|
||||
|
||||
If you already have VS Code and Docker installed, you can click the badge above or [here](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/vscode) to get started. Clicking these links will cause VS Code to automatically install the Dev Containers extension if needed, clone the source code into a container volume, and spin up a dev container for use.
|
||||
|
||||
1. Install Docker Desktop or Docker for Linux on your local machine. (See [docs](https://aka.ms/vscode-remote/containers/getting-started) for additional details.)
|
||||
|
||||
2. **Important**: Docker needs at least **4 Cores and 8 GB of RAM** to run a full build with **9 GB of RAM** being recommended. If you are on macOS, or are using the old Hyper-V engine for Windows, update these values for Docker Desktop by right-clicking on the Docker status bar item and going to **Preferences/Settings > Resources > Advanced**.
|
||||
|
||||
> **Note:** The [Resource Monitor](https://marketplace.visualstudio.com/items?itemName=mutantdino.resourcemonitor) extension is included in the container so you can keep an eye on CPU/Memory in the status bar.
|
||||
|
||||
3. Install [Visual Studio Code Stable](https://code.visualstudio.com/) or [Insiders](https://code.visualstudio.com/insiders/) and the [Dev Containers](https://aka.ms/vscode-remote/download/containers) extension.
|
||||
|
||||

|
||||
|
||||
> **Note:** The Dev Containers extension requires the Visual Studio Code distribution of Code - OSS. See the [FAQ](https://aka.ms/vscode-remote/faq/license) for details.
|
||||
|
||||
4. Press <kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd> or <kbd>F1</kbd> and select **Dev Containers: Clone Repository in Container Volume...**.
|
||||
|
||||
> **Tip:** While you can use your local source tree instead, operations like `yarn install` can be slow on macOS or when using the Hyper-V engine on Windows. We recommend using the WSL filesystem on Windows or the "clone repository in container" approach on Windows and macOS instead since it uses "named volume" rather than the local filesystem.
|
||||
|
||||
5. Type `https://github.com/microsoft/vscode` (or a branch or PR URL) in the input box and press <kbd>Enter</kbd>.
|
||||
|
||||
6. After the container is running:
|
||||
1. If you have the `DISPLAY` or `WAYLAND_DISPLAY` environment variables set locally (or in WSL on Windows), desktop apps in the container will be shown in local windows.
|
||||
2. If these are not set, open a web browser and go to [http://localhost:6080](http://localhost:6080), or use a [VNC Viewer][def] to connect to `localhost:5901` and enter `vscode` as the password. Anything you start in VS Code, or the integrated terminal, will appear here.
|
||||
|
||||
Next: **[Try it out!](#try-it)**
|
||||
|
||||
## Quick start - GitHub Codespaces
|
||||
|
||||
1. From the [microsoft/vscode GitHub repository](https://github.com/microsoft/vscode), click on the **Code** dropdown, select **Open with Codespaces**, and then click on **New codespace**. If prompted, select the **Standard** machine size (which is also the default).
|
||||
|
||||
> **Note:** You will not see these options within GitHub if you are not in the Codespaces beta.
|
||||
|
||||
2. After the codespace is up and running in your browser, press <kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd> or <kbd>F1</kbd> and select **Ports: Focus on Ports View**.
|
||||
|
||||
3. You should see **VNC web client (6080)** under in the list of ports. Select the line and click on the globe icon to open it in a browser tab.
|
||||
|
||||
> **Tip:** If you do not see the port, <kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd> or <kbd>F1</kbd>, select **Forward a Port** and enter port `6080`.
|
||||
|
||||
4. In the new tab, you should see noVNC. Click **Connect** and enter `vscode` as the password.
|
||||
|
||||
Anything you start in VS Code, or the integrated terminal, will appear here.
|
||||
|
||||
Next: **[Try it out!](#try-it)**
|
||||
|
||||
### Using VS Code with GitHub Codespaces
|
||||
|
||||
You may see improved VNC responsiveness when accessing a codespace from VS Code client since you can use a [VNC Viewer][def]. Here's how to do it.
|
||||
|
||||
1. Install [Visual Studio Code Stable](https://code.visualstudio.com/) or [Insiders](https://code.visualstudio.com/insiders/) and the the [GitHub Codespaces extension](https://marketplace.visualstudio.com/items?itemName=GitHub.codespaces).
|
||||
|
||||
> **Note:** The GitHub Codespaces extension requires the Visual Studio Code distribution of Code - OSS.
|
||||
|
||||
2. After the VS Code is up and running, press <kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd> or <kbd>F1</kbd>, choose **Codespaces: Create New Codespace**, and use the following settings:
|
||||
|
||||
- `microsoft/vscode` for the repository.
|
||||
- Select any branch (e.g. **main**) - you can select a different one later.
|
||||
- Choose **Standard** (4-core, 8GB) as the size.
|
||||
|
||||
3. After you have connected to the codespace, you can use a [VNC Viewer][def] to connect to `localhost:5901` and enter `vscode` as the password.
|
||||
|
||||
> **Tip:** You may also need change your VNC client's **Picture Quality** setting to **High** to get a full color desktop.
|
||||
|
||||
4. Anything you start in VS Code, or the integrated terminal, will appear here.
|
||||
|
||||
Next: **[Try it out!](#try-it)**
|
||||
|
||||
## Try it
|
||||
|
||||
This container uses the [Fluxbox](http://fluxbox.org/) window manager to keep things lean. **Right-click on the desktop** to see menu options. It works with GNOME and GTK applications, so other tools can be installed if needed.
|
||||
|
||||
> **Note:** You can also set the resolution from the command line by typing `set-resolution`.
|
||||
|
||||
To start working with Code - OSS, follow these steps:
|
||||
|
||||
1. In your local VS Code client, open a terminal (<kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>\`</kbd>) and type the following commands:
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
bash scripts/code.sh
|
||||
```
|
||||
|
||||
2. After the build is complete, open a web browser or a [VNC Viewer][def] to connect to the desktop environment as described in the quick start and enter `vscode` as the password.
|
||||
|
||||
3. You should now see Code - OSS!
|
||||
|
||||
Next, let's try debugging.
|
||||
|
||||
1. Shut down Code - OSS by clicking the box in the upper right corner of the Code - OSS window through your browser or VNC viewer.
|
||||
|
||||
2. Go to your local VS Code client, and use the **Run / Debug** view to launch the **VS Code** configuration. (Typically the default, so you can likely just press <kbd>F5</kbd>).
|
||||
|
||||
> **Note:** If launching times out, you can increase the value of `timeout` in the "VS Code", "Attach Main Process", "Attach Extension Host", and "Attach to Shared Process" configurations in [launch.json](../../.vscode/launch.json). However, running `./scripts/code.sh` first will set up Electron which will usually solve timeout issues.
|
||||
|
||||
3. After a bit, Code - OSS will appear with the debugger attached!
|
||||
|
||||
Enjoy!
|
||||
|
||||
### Notes
|
||||
|
||||
The container comes with VS Code Insiders installed. To run it from an Integrated Terminal use `VSCODE_IPC_HOOK_CLI= /usr/bin/code-insiders .`.
|
||||
|
||||
[def]: https://www.realvnc.com/en/connect/download/viewer/
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/desktop-lite:1": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "ghcr.io/devcontainers/features/desktop-lite@sha256:e7dc4d37ab9e3d6e7ebb221bac741f5bfe07dae47025399d038b17af2ed8ddb7",
|
||||
"integrity": "sha256:e7dc4d37ab9e3d6e7ebb221bac741f5bfe07dae47025399d038b17af2ed8ddb7"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/rust:1": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "ghcr.io/devcontainers/features/rust@sha256:aba6f47303b197976902bf544c786b5efecc03c238ff593583e5e74dfa9c7ccb",
|
||||
"integrity": "sha256:aba6f47303b197976902bf544c786b5efecc03c238ff593583e5e74dfa9c7ccb"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "Code - OSS",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile"
|
||||
},
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/desktop-lite:1": {},
|
||||
"ghcr.io/devcontainers/features/rust:1": {}
|
||||
},
|
||||
"containerEnv": {
|
||||
"DISPLAY": "" // Allow the Dev Containers extension to set DISPLAY, post-create.sh will add it back in ~/.bashrc and ~/.zshrc if not set.
|
||||
},
|
||||
"overrideCommand": false,
|
||||
"privileged": true,
|
||||
"mounts": [
|
||||
{
|
||||
"source": "vscode-dev",
|
||||
"target": "/vscode-dev",
|
||||
"type": "volume"
|
||||
}
|
||||
],
|
||||
"postCreateCommand": "./.devcontainer/post-create.sh",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"resmon.show.battery": false,
|
||||
"resmon.show.cpufreq": false
|
||||
},
|
||||
"extensions": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"EditorConfig.EditorConfig",
|
||||
"GitHub.vscode-pull-request-github",
|
||||
"ms-vscode.vscode-github-issue-notebooks",
|
||||
"ms-vscode.vscode-selfhost-test-provider",
|
||||
"mutantdino.resourcemonitor"
|
||||
]
|
||||
}
|
||||
},
|
||||
"forwardPorts": [6080, 5901],
|
||||
"portsAttributes": {
|
||||
"6080": {
|
||||
"label": "VNC web client (noVNC)",
|
||||
"onAutoForward": "silent"
|
||||
},
|
||||
"5901": {
|
||||
"label": "VNC TCP port",
|
||||
"onAutoForward": "silent"
|
||||
}
|
||||
},
|
||||
"hostRequirements": {
|
||||
"memory": "9gb"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
apt update
|
||||
apt install -y wget gpg
|
||||
|
||||
wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > packages.microsoft.gpg
|
||||
install -D -o root -g root -m 644 packages.microsoft.gpg /etc/apt/keyrings/packages.microsoft.gpg
|
||||
sh -c 'echo "deb [arch=amd64,arm64,armhf signed-by=/etc/apt/keyrings/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main" > /etc/apt/sources.list.d/vscode.list'
|
||||
rm -f packages.microsoft.gpg
|
||||
|
||||
apt update
|
||||
apt install -y code-insiders libsecret-1-dev libxkbfile-dev libkrb5-dev
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
yarn install --network-timeout 180000
|
||||
yarn electron
|
||||
@@ -0,0 +1,15 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Tab indentation
|
||||
[*]
|
||||
indent_style = tab
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# The indent size used in the `package.json` file cannot be changed
|
||||
# https://github.com/npm/npm/pull/3180#issuecomment-16336516
|
||||
[{*.yml,*.yaml,package.json}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
@@ -0,0 +1,44 @@
|
||||
**/build/*/**/*.js
|
||||
**/dist/**/*.js
|
||||
**/extensions/**/*.d.ts
|
||||
**/extensions/**/build/**
|
||||
**/extensions/**/colorize-fixtures/**
|
||||
**/extensions/css-language-features/server/test/pathCompletionFixtures/**
|
||||
**/extensions/html-language-features/server/lib/jquery.d.ts
|
||||
**/extensions/html-language-features/server/src/test/pathCompletionFixtures/**
|
||||
**/extensions/ipynb/notebook-out/**
|
||||
**/extensions/markdown-language-features/media/**
|
||||
**/extensions/markdown-language-features/notebook-out/**
|
||||
**/extensions/markdown-math/notebook-out/**
|
||||
**/extensions/notebook-renderers/renderer-out/index.js
|
||||
**/extensions/simple-browser/media/index.js
|
||||
**/extensions/typescript-language-features/test-workspace/**
|
||||
**/extensions/typescript-language-features/extension.webpack.config.js
|
||||
**/extensions/typescript-language-features/extension-browser.webpack.config.js
|
||||
**/extensions/typescript-language-features/package-manager/node-maintainer/**
|
||||
**/extensions/vscode-api-tests/testWorkspace/**
|
||||
**/extensions/vscode-api-tests/testWorkspace2/**
|
||||
**/fixtures/**
|
||||
**/node_modules/**
|
||||
**/out-*/**/*.js
|
||||
**/out-editor-*/**
|
||||
**/out/**/*.js
|
||||
**/src/**/dompurify.js
|
||||
**/src/**/marked.js
|
||||
**/src/**/semver.js
|
||||
**/src/typings/**/*.d.ts
|
||||
**/src/vs/*/**/*.d.ts
|
||||
**/src/vs/base/test/common/filters.perf.data.js
|
||||
**/src/vs/loader.js
|
||||
**/src2/**/dompurify.js
|
||||
**/src2/**/marked.js
|
||||
**/src2/**/semver.js
|
||||
**/src2/typings/**/*.d.ts
|
||||
**/src2/vs/*/**/*.d.ts
|
||||
**/src2/vs/base/test/common/filters.perf.data.js
|
||||
**/src2/vs/loader.js
|
||||
**/test/unit/assert.js
|
||||
**/test/unit/assert-esm.js
|
||||
**/test/automation/out/**
|
||||
**/typings/**
|
||||
!.vscode
|
||||
@@ -0,0 +1,60 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { join } from 'path';
|
||||
|
||||
|
||||
export = new class ApiProviderNaming implements eslint.Rule.RuleModule {
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
messages: {
|
||||
amdX: 'Use `import type` for import declarations, use `amdX#importAMDNodeModule` for import expressions'
|
||||
}
|
||||
};
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
|
||||
const modules = new Set<string>();
|
||||
|
||||
try {
|
||||
const { dependencies, optionalDependencies } = require(join(__dirname, '../package.json'));
|
||||
const all = Object.keys(dependencies).concat(Object.keys(optionalDependencies));
|
||||
for (const key of all) {
|
||||
modules.add(key);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
|
||||
const checkImport = (node: any) => {
|
||||
|
||||
if (node.type !== 'Literal' || typeof node.value !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.parent.importKind === 'type') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!modules.has(node.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'amdX'
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
['ImportExpression Literal']: checkImport,
|
||||
['ImportDeclaration Literal']: checkImport
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
|
||||
export = new class DeclareServiceBrand implements eslint.Rule.RuleModule {
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
fixable: 'code'
|
||||
};
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
return {
|
||||
['PropertyDefinition[key.name="_serviceBrand"][value]']: (node: any) => {
|
||||
return context.report({
|
||||
node,
|
||||
message: `The '_serviceBrand'-property should not have a value`,
|
||||
fix: (fixer) => {
|
||||
return fixer.replaceText(node, 'declare _serviceBrand: undefined;')
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { Node } from 'estree';
|
||||
|
||||
export = new class EnsureNoDisposablesAreLeakedInTestSuite implements eslint.Rule.RuleModule {
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
type: 'problem',
|
||||
messages: {
|
||||
ensure: 'Suites should include a call to `ensureNoDisposablesAreLeakedInTestSuite()` to ensure no disposables are leaked in tests.'
|
||||
}
|
||||
};
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
const config = <{ exclude: string[] }>context.options[0];
|
||||
|
||||
const needle = context.getFilename().replace(/\\/g, '/');
|
||||
if (config.exclude.some((e) => needle.endsWith(e))) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
[`Program > ExpressionStatement > CallExpression[callee.name='suite']`]: (node: Node) => {
|
||||
const src = context.getSourceCode().getText(node)
|
||||
if (!src.includes('ensureNoDisposablesAreLeakedInTestSuite(')) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'ensure',
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,255 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { TSESTree } from '@typescript-eslint/experimental-utils';
|
||||
import * as path from 'path';
|
||||
import minimatch from 'minimatch';
|
||||
import { createImportRuleListener } from './utils';
|
||||
|
||||
const REPO_ROOT = path.normalize(path.join(__dirname, '../'));
|
||||
|
||||
interface ConditionalPattern {
|
||||
when?: 'hasBrowser' | 'hasNode' | 'test';
|
||||
pattern: string;
|
||||
}
|
||||
|
||||
interface RawImportPatternsConfig {
|
||||
target: string;
|
||||
layer?: 'common' | 'worker' | 'browser' | 'electron-sandbox' | 'node' | 'electron-main';
|
||||
test?: boolean;
|
||||
restrictions: string | (string | ConditionalPattern)[];
|
||||
}
|
||||
|
||||
interface LayerAllowRule {
|
||||
when: 'hasBrowser' | 'hasNode' | 'test';
|
||||
allow: string[];
|
||||
}
|
||||
|
||||
type RawOption = RawImportPatternsConfig | LayerAllowRule;
|
||||
|
||||
function isLayerAllowRule(option: RawOption): option is LayerAllowRule {
|
||||
return !!((<LayerAllowRule>option).when && (<LayerAllowRule>option).allow);
|
||||
}
|
||||
|
||||
interface ImportPatternsConfig {
|
||||
target: string;
|
||||
restrictions: string[];
|
||||
}
|
||||
|
||||
export = new class implements eslint.Rule.RuleModule {
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
messages: {
|
||||
badImport: 'Imports violates \'{{restrictions}}\' restrictions. See https://github.com/microsoft/vscode/wiki/Source-Code-Organization',
|
||||
badFilename: 'Missing definition in `code-import-patterns` for this file. Define rules at https://github.com/microsoft/vscode/blob/main/.eslintrc.json'
|
||||
},
|
||||
docs: {
|
||||
url: 'https://github.com/microsoft/vscode/wiki/Source-Code-Organization'
|
||||
}
|
||||
};
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
const options = <RawOption[]>context.options;
|
||||
const configs = this._processOptions(options);
|
||||
const relativeFilename = getRelativeFilename(context);
|
||||
|
||||
for (const config of configs) {
|
||||
if (minimatch(relativeFilename, config.target)) {
|
||||
return createImportRuleListener((node, value) => this._checkImport(context, config, node, value));
|
||||
}
|
||||
}
|
||||
|
||||
context.report({
|
||||
loc: { line: 1, column: 0 },
|
||||
messageId: 'badFilename'
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
private _optionsCache = new WeakMap<RawOption[], ImportPatternsConfig[]>();
|
||||
|
||||
private _processOptions(options: RawOption[]): ImportPatternsConfig[] {
|
||||
if (this._optionsCache.has(options)) {
|
||||
return this._optionsCache.get(options)!;
|
||||
}
|
||||
|
||||
type Layer = 'common' | 'worker' | 'browser' | 'electron-sandbox' | 'node' | 'electron-main';
|
||||
|
||||
interface ILayerRule {
|
||||
layer: Layer;
|
||||
deps: string;
|
||||
isBrowser?: boolean;
|
||||
isNode?: boolean;
|
||||
}
|
||||
|
||||
function orSegment(variants: Layer[]): string {
|
||||
return (variants.length === 1 ? variants[0] : `{${variants.join(',')}}`);
|
||||
}
|
||||
|
||||
const layerRules: ILayerRule[] = [
|
||||
{ layer: 'common', deps: orSegment(['common']) },
|
||||
{ layer: 'worker', deps: orSegment(['common', 'worker']) },
|
||||
{ layer: 'browser', deps: orSegment(['common', 'browser']), isBrowser: true },
|
||||
{ layer: 'electron-sandbox', deps: orSegment(['common', 'browser', 'electron-sandbox']), isBrowser: true },
|
||||
{ layer: 'node', deps: orSegment(['common', 'node']), isNode: true },
|
||||
{ layer: 'electron-main', deps: orSegment(['common', 'node', 'electron-main']), isNode: true },
|
||||
];
|
||||
|
||||
let browserAllow: string[] = [];
|
||||
let nodeAllow: string[] = [];
|
||||
let testAllow: string[] = [];
|
||||
for (const option of options) {
|
||||
if (isLayerAllowRule(option)) {
|
||||
if (option.when === 'hasBrowser') {
|
||||
browserAllow = option.allow.slice(0);
|
||||
} else if (option.when === 'hasNode') {
|
||||
nodeAllow = option.allow.slice(0);
|
||||
} else if (option.when === 'test') {
|
||||
testAllow = option.allow.slice(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findLayer(layer: Layer): ILayerRule | null {
|
||||
for (const layerRule of layerRules) {
|
||||
if (layerRule.layer === layer) {
|
||||
return layerRule;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function generateConfig(layerRule: ILayerRule, target: string, rawRestrictions: (string | ConditionalPattern)[]): [ImportPatternsConfig, ImportPatternsConfig] {
|
||||
const restrictions: string[] = [];
|
||||
const testRestrictions: string[] = [...testAllow];
|
||||
|
||||
if (layerRule.isBrowser) {
|
||||
restrictions.push(...browserAllow);
|
||||
}
|
||||
|
||||
if (layerRule.isNode) {
|
||||
restrictions.push(...nodeAllow);
|
||||
}
|
||||
|
||||
for (const rawRestriction of rawRestrictions) {
|
||||
let importPattern: string;
|
||||
let when: 'hasBrowser' | 'hasNode' | 'test' | undefined = undefined;
|
||||
if (typeof rawRestriction === 'string') {
|
||||
importPattern = rawRestriction;
|
||||
} else {
|
||||
importPattern = rawRestriction.pattern;
|
||||
when = rawRestriction.when;
|
||||
}
|
||||
if (typeof when === 'undefined'
|
||||
|| (when === 'hasBrowser' && layerRule.isBrowser)
|
||||
|| (when === 'hasNode' && layerRule.isNode)
|
||||
) {
|
||||
restrictions.push(importPattern.replace(/\/\~$/, `/${layerRule.deps}/**`));
|
||||
testRestrictions.push(importPattern.replace(/\/\~$/, `/test/${layerRule.deps}/**`));
|
||||
} else if (when === 'test') {
|
||||
testRestrictions.push(importPattern.replace(/\/\~$/, `/${layerRule.deps}/**`));
|
||||
testRestrictions.push(importPattern.replace(/\/\~$/, `/test/${layerRule.deps}/**`));
|
||||
}
|
||||
}
|
||||
|
||||
testRestrictions.push(...restrictions);
|
||||
|
||||
return [
|
||||
{
|
||||
target: target.replace(/\/\~$/, `/${layerRule.layer}/**`),
|
||||
restrictions: restrictions
|
||||
},
|
||||
{
|
||||
target: target.replace(/\/\~$/, `/test/${layerRule.layer}/**`),
|
||||
restrictions: testRestrictions
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
const configs: ImportPatternsConfig[] = [];
|
||||
for (const option of options) {
|
||||
if (isLayerAllowRule(option)) {
|
||||
continue;
|
||||
}
|
||||
const target = option.target;
|
||||
const targetIsVS = /^src\/vs\//.test(target);
|
||||
const restrictions = (typeof option.restrictions === 'string' ? [option.restrictions] : option.restrictions).slice(0);
|
||||
|
||||
if (targetIsVS) {
|
||||
// Always add "vs/nls" and "vs/amdX"
|
||||
restrictions.push('vs/nls');
|
||||
restrictions.push('vs/amdX'); // TODO@jrieken remove after ESM is real
|
||||
}
|
||||
|
||||
if (targetIsVS && option.layer) {
|
||||
// single layer => simple substitution for /~
|
||||
const layerRule = findLayer(option.layer);
|
||||
if (layerRule) {
|
||||
const [config, testConfig] = generateConfig(layerRule, target, restrictions);
|
||||
if (option.test) {
|
||||
configs.push(testConfig);
|
||||
} else {
|
||||
configs.push(config);
|
||||
}
|
||||
}
|
||||
} else if (targetIsVS && /\/\~$/.test(target)) {
|
||||
// generate all layers
|
||||
for (const layerRule of layerRules) {
|
||||
const [config, testConfig] = generateConfig(layerRule, target, restrictions);
|
||||
configs.push(config);
|
||||
configs.push(testConfig);
|
||||
}
|
||||
} else {
|
||||
configs.push({ target, restrictions: <string[]>restrictions.filter(r => typeof r === 'string') });
|
||||
}
|
||||
}
|
||||
this._optionsCache.set(options, configs);
|
||||
return configs;
|
||||
}
|
||||
|
||||
private _checkImport(context: eslint.Rule.RuleContext, config: ImportPatternsConfig, node: TSESTree.Node, importPath: string) {
|
||||
|
||||
// resolve relative paths
|
||||
if (importPath[0] === '.') {
|
||||
const relativeFilename = getRelativeFilename(context);
|
||||
importPath = path.posix.join(path.posix.dirname(relativeFilename), importPath);
|
||||
if (/^src\/vs\//.test(importPath)) {
|
||||
// resolve using AMD base url
|
||||
importPath = importPath.substring('src/'.length);
|
||||
}
|
||||
}
|
||||
|
||||
const restrictions = config.restrictions;
|
||||
|
||||
let matched = false;
|
||||
for (const pattern of restrictions) {
|
||||
if (minimatch(importPath, pattern)) {
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matched) {
|
||||
// None of the restrictions matched
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
messageId: 'badImport',
|
||||
data: {
|
||||
restrictions: restrictions.join(' or ')
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the filename relative to the project root and using `/` as separators
|
||||
*/
|
||||
function getRelativeFilename(context: eslint.Rule.RuleContext): string {
|
||||
const filename = path.normalize(context.getFilename());
|
||||
return filename.substring(REPO_ROOT.length).replace(/\\/g, '/');
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { join, dirname } from 'path';
|
||||
import { createImportRuleListener } from './utils';
|
||||
|
||||
type Config = {
|
||||
allowed: Set<string>;
|
||||
disallowed: Set<string>;
|
||||
};
|
||||
|
||||
export = new class implements eslint.Rule.RuleModule {
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
messages: {
|
||||
layerbreaker: 'Bad layering. You are not allowed to access {{from}} from here, allowed layers are: [{{allowed}}]'
|
||||
},
|
||||
docs: {
|
||||
url: 'https://github.com/microsoft/vscode/wiki/Source-Code-Organization'
|
||||
}
|
||||
};
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
|
||||
const fileDirname = dirname(context.getFilename());
|
||||
const parts = fileDirname.split(/\\|\//);
|
||||
const ruleArgs = <Record<string, string[]>>context.options[0];
|
||||
|
||||
let config: Config | undefined;
|
||||
for (let i = parts.length - 1; i >= 0; i--) {
|
||||
if (ruleArgs[parts[i]]) {
|
||||
config = {
|
||||
allowed: new Set(ruleArgs[parts[i]]).add(parts[i]),
|
||||
disallowed: new Set()
|
||||
};
|
||||
Object.keys(ruleArgs).forEach(key => {
|
||||
if (!config!.allowed.has(key)) {
|
||||
config!.disallowed.add(key);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
// nothing
|
||||
return {};
|
||||
}
|
||||
|
||||
return createImportRuleListener((node, path) => {
|
||||
if (path[0] === '.') {
|
||||
path = join(dirname(context.getFilename()), path);
|
||||
}
|
||||
|
||||
const parts = dirname(path).split(/\\|\//);
|
||||
for (let i = parts.length - 1; i >= 0; i--) {
|
||||
const part = parts[i];
|
||||
|
||||
if (config!.allowed.has(part)) {
|
||||
// GOOD - same layer
|
||||
break;
|
||||
}
|
||||
|
||||
if (config!.disallowed.has(part)) {
|
||||
// BAD - wrong layer
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
messageId: 'layerbreaker',
|
||||
data: {
|
||||
from: part,
|
||||
allowed: [...config!.allowed.keys()].join(', ')
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { TSESTree } from '@typescript-eslint/experimental-utils';
|
||||
|
||||
const VALID_USES = new Set<TSESTree.AST_NODE_TYPES | undefined>([
|
||||
TSESTree.AST_NODE_TYPES.AwaitExpression,
|
||||
TSESTree.AST_NODE_TYPES.VariableDeclarator,
|
||||
]);
|
||||
|
||||
export = new class MustUseResults implements eslint.Rule.RuleModule {
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
|
||||
const config = <{ message: string, functions: string[] }[]>context.options[0];
|
||||
const listener: eslint.Rule.RuleListener = {};
|
||||
|
||||
for (const { message, functions } of config) {
|
||||
for (const fn of functions) {
|
||||
const query = `CallExpression[callee.property.name='${fn}'], CallExpression[callee.name='${fn}']`
|
||||
listener[query] = (node: any) => {
|
||||
const cast: TSESTree.CallExpression = node;
|
||||
if (!VALID_USES.has(cast.parent?.type)) {
|
||||
context.report({ node, message });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return listener;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
|
||||
export = new class NoAsyncSuite implements eslint.Rule.RuleModule {
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
function doesCallSuperDispose(node: any) {
|
||||
|
||||
if (!node.override) {
|
||||
return;
|
||||
}
|
||||
|
||||
const body = context.getSourceCode().getText(node)
|
||||
|
||||
if (body.includes('super.dispose')) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node,
|
||||
message: 'dispose() should call super.dispose()'
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
['MethodDefinition[override][key.name="dispose"]']: doesCallSuperDispose,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { TSESTree } from '@typescript-eslint/experimental-utils';
|
||||
|
||||
export = new class NoDangerousTypeAssertions implements eslint.Rule.RuleModule {
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
// Disable in tests for now
|
||||
if (context.getFilename().includes('.test')) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
// Disallow type assertions on object literals: <T>{ ... } or {} as T
|
||||
['TSTypeAssertion > ObjectExpression, TSAsExpression > ObjectExpression']: (node: any) => {
|
||||
const objectNode = node as TSESTree.Node;
|
||||
|
||||
const parent = objectNode.parent as TSESTree.TSTypeAssertion | TSESTree.TSAsExpression;
|
||||
if (
|
||||
// Allow `as const` assertions
|
||||
(parent.typeAnnotation.type === 'TSTypeReference' && parent.typeAnnotation.typeName.type === 'Identifier' && parent.typeAnnotation.typeName.name === 'const')
|
||||
|
||||
// For also now still allow `any` casts
|
||||
|| (parent.typeAnnotation.type === 'TSAnyKeyword')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node,
|
||||
message: "Don't use type assertions for creating objects as this can hide type errors."
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
|
||||
export = new class NoGlobalDocumentListener implements eslint.Rule.RuleModule {
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
return {
|
||||
CallExpression(node: any) {
|
||||
if (
|
||||
(
|
||||
node.callee.name === 'addDisposableListener' ||
|
||||
node.callee.property?.name === 'addDisposableListener'
|
||||
) &&
|
||||
node.arguments.length > 0 &&
|
||||
node.arguments[0].type === 'Identifier' &&
|
||||
node.arguments[0].name === 'document'
|
||||
) {
|
||||
context.report({
|
||||
node,
|
||||
message: 'Use <targetWindow>.document to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant.',
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { TSESTree } from '@typescript-eslint/experimental-utils';
|
||||
import * as ESTree from 'estree';
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const _positiveLookBehind = /\(\?<=.+/;
|
||||
const _negativeLookBehind = /\(\?<!.+/;
|
||||
|
||||
function _containsLookBehind(pattern: string | unknown): boolean {
|
||||
if (typeof pattern !== 'string') {
|
||||
return false;
|
||||
}
|
||||
return _positiveLookBehind.test(pattern) || _negativeLookBehind.test(pattern);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
create(context: eslint.Rule.RuleContext) {
|
||||
return {
|
||||
// /.../
|
||||
['Literal[regex]']: (node: any) => {
|
||||
type RegexLiteral = TSESTree.Literal & { regex: { pattern: string; flags: string } };
|
||||
const pattern = (<RegexLiteral>node).regex?.pattern;
|
||||
if (_containsLookBehind(pattern)) {
|
||||
context.report({
|
||||
node,
|
||||
message: 'Look behind assertions are not yet supported in all browsers'
|
||||
});
|
||||
}
|
||||
},
|
||||
// new Regex("...")
|
||||
['NewExpression[callee.name="RegExp"] Literal']: (node: ESTree.Literal) => {
|
||||
if (_containsLookBehind(node.value)) {
|
||||
context.report({
|
||||
node,
|
||||
message: 'Look behind assertions are not yet supported in all browsers'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
|
||||
export = new class ApiProviderNaming implements eslint.Rule.RuleModule {
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
messages: {
|
||||
slow: 'Native private fields are much slower and should only be used when needed. Ignore this warning if you know what you are doing, use compile-time private otherwise. See https://github.com/microsoft/vscode/issues/185991#issuecomment-1614468158 for details',
|
||||
}
|
||||
};
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
|
||||
return {
|
||||
['PropertyDefinition PrivateIdentifier']: (node: any) => {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'slow'
|
||||
});
|
||||
},
|
||||
['MethodDefinition PrivateIdentifier']: (node: any) => {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'slow'
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { join } from 'path';
|
||||
import { createImportRuleListener } from './utils';
|
||||
|
||||
export = new class NoNlsInStandaloneEditorRule implements eslint.Rule.RuleModule {
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
messages: {
|
||||
noNls: 'Not allowed to import vs/nls in standalone editor modules. Use standaloneStrings.ts'
|
||||
}
|
||||
};
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
|
||||
const fileName = context.getFilename();
|
||||
if (
|
||||
/vs(\/|\\)editor(\/|\\)standalone(\/|\\)/.test(fileName)
|
||||
|| /vs(\/|\\)editor(\/|\\)common(\/|\\)standalone(\/|\\)/.test(fileName)
|
||||
|| /vs(\/|\\)editor(\/|\\)editor.api/.test(fileName)
|
||||
|| /vs(\/|\\)editor(\/|\\)editor.main/.test(fileName)
|
||||
|| /vs(\/|\\)editor(\/|\\)editor.worker/.test(fileName)
|
||||
) {
|
||||
return createImportRuleListener((node, path) => {
|
||||
// resolve relative paths
|
||||
if (path[0] === '.') {
|
||||
path = join(context.getFilename(), path);
|
||||
}
|
||||
|
||||
if (
|
||||
/vs(\/|\\)nls/.test(path)
|
||||
) {
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
messageId: 'noNls'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
|
||||
/**
|
||||
* Checks for potentially unsafe usage of `DisposableStore` / `MutableDisposable`.
|
||||
*
|
||||
* These have been the source of leaks in the past.
|
||||
*/
|
||||
export = new class implements eslint.Rule.RuleModule {
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
function checkVariableDeclaration(inNode: any) {
|
||||
context.report({
|
||||
node: inNode,
|
||||
message: `Use const for 'DisposableStore' to avoid leaks by accidental reassignment.`
|
||||
});
|
||||
}
|
||||
|
||||
function checkProperty(inNode: any) {
|
||||
context.report({
|
||||
node: inNode,
|
||||
message: `Use readonly for DisposableStore/MutableDisposable to avoid leaks through accidental reassignment.`
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
'VariableDeclaration[kind!="const"] NewExpression[callee.name="DisposableStore"]': checkVariableDeclaration,
|
||||
|
||||
'PropertyDefinition[readonly!=true][typeAnnotation.typeAnnotation.typeName.name=/DisposableStore|MutableDisposable/]': checkProperty,
|
||||
'PropertyDefinition[readonly!=true] NewExpression[callee.name=/DisposableStore|MutableDisposable/]': checkProperty,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TSESTree } from '@typescript-eslint/typescript-estree';
|
||||
import * as eslint from 'eslint';
|
||||
import { dirname, join, relative } from 'path';
|
||||
import minimatch from 'minimatch';
|
||||
import { createImportRuleListener } from './utils';
|
||||
|
||||
export = new class implements eslint.Rule.RuleModule {
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
messages: {
|
||||
layerbreaker: 'You are only allowed to import {{import}} from here using `import type ...`.'
|
||||
},
|
||||
schema: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
additionalProperties: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
let fileRelativePath = relative(dirname(__dirname), context.getFilename());
|
||||
if (!fileRelativePath.endsWith('/')) {
|
||||
fileRelativePath += '/';
|
||||
}
|
||||
const ruleArgs = <Record<string, string[]>>context.options[0];
|
||||
|
||||
const matchingKey = Object.keys(ruleArgs).find(key => fileRelativePath.startsWith(key) || minimatch(fileRelativePath, key));
|
||||
if (!matchingKey) {
|
||||
// nothing
|
||||
return {};
|
||||
}
|
||||
|
||||
const restrictedImports = ruleArgs[matchingKey];
|
||||
return createImportRuleListener((node, path) => {
|
||||
if (path[0] === '.') {
|
||||
path = join(dirname(context.getFilename()), path);
|
||||
}
|
||||
|
||||
if ((
|
||||
restrictedImports.includes(path) || restrictedImports.some(restriction => minimatch(path, restriction))
|
||||
) && !(
|
||||
(node.parent?.type === TSESTree.AST_NODE_TYPES.ImportDeclaration && node.parent.importKind === 'type') ||
|
||||
(node.parent && 'exportKind' in node.parent && node.parent.exportKind === 'type'))) { // the export could be multiple types
|
||||
context.report({
|
||||
loc: node.parent!.loc,
|
||||
messageId: 'layerbreaker',
|
||||
data: {
|
||||
import: path
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { join } from 'path';
|
||||
import { createImportRuleListener } from './utils';
|
||||
|
||||
export = new class NoNlsInStandaloneEditorRule implements eslint.Rule.RuleModule {
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
messages: {
|
||||
badImport: 'Not allowed to import standalone editor modules.'
|
||||
},
|
||||
docs: {
|
||||
url: 'https://github.com/microsoft/vscode/wiki/Source-Code-Organization'
|
||||
}
|
||||
};
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
|
||||
if (/vs(\/|\\)editor/.test(context.getFilename())) {
|
||||
// the vs/editor folder is allowed to use the standalone editor
|
||||
return {};
|
||||
}
|
||||
|
||||
return createImportRuleListener((node, path) => {
|
||||
|
||||
// resolve relative paths
|
||||
if (path[0] === '.') {
|
||||
path = join(context.getFilename(), path);
|
||||
}
|
||||
|
||||
if (
|
||||
/vs(\/|\\)editor(\/|\\)standalone(\/|\\)/.test(path)
|
||||
|| /vs(\/|\\)editor(\/|\\)common(\/|\\)standalone(\/|\\)/.test(path)
|
||||
|| /vs(\/|\\)editor(\/|\\)editor.api/.test(path)
|
||||
|| /vs(\/|\\)editor(\/|\\)editor.main/.test(path)
|
||||
|| /vs(\/|\\)editor(\/|\\)editor.worker/.test(path)
|
||||
) {
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
messageId: 'badImport'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { TSESTree } from '@typescript-eslint/experimental-utils';
|
||||
|
||||
/**
|
||||
* WORKAROUND for https://github.com/evanw/esbuild/issues/3823
|
||||
*/
|
||||
export = new class implements eslint.Rule.RuleModule {
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
|
||||
function checkProperty(inNode: any) {
|
||||
|
||||
const classDeclaration = context.getAncestors().find(node => node.type === 'ClassDeclaration');
|
||||
const propertyDefinition = <TSESTree.PropertyDefinition>inNode;
|
||||
|
||||
if (!classDeclaration || !classDeclaration.id?.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!propertyDefinition.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const classCtor = classDeclaration.body.body.find(node => node.type === 'MethodDefinition' && node.kind === 'constructor')
|
||||
|
||||
if (!classCtor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const name = classDeclaration.id.name;
|
||||
const valueText = context.getSourceCode().getText(<any>propertyDefinition.value)
|
||||
|
||||
if (valueText.includes(name + '.')) {
|
||||
|
||||
if (classCtor.value?.type === 'FunctionExpression' && !classCtor.value.params.find((param: any) => param.type === 'TSParameterProperty' && param.decorators?.length > 0)) {
|
||||
return
|
||||
}
|
||||
|
||||
context.report({
|
||||
loc: propertyDefinition.value.loc,
|
||||
message: `Static properties in decorated classes should not reference the class they are defined in. Use 'this' instead. This is a workaround for https://github.com/evanw/esbuild/issues/3823.`
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
'PropertyDefinition[static=true]': checkProperty,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TSESTree } from '@typescript-eslint/experimental-utils';
|
||||
import * as eslint from 'eslint';
|
||||
|
||||
function isCallExpression(node: TSESTree.Node): node is TSESTree.CallExpression {
|
||||
return node.type === 'CallExpression';
|
||||
}
|
||||
|
||||
function isFunctionExpression(node: TSESTree.Node): node is TSESTree.FunctionExpression {
|
||||
return node.type.includes('FunctionExpression');
|
||||
}
|
||||
|
||||
export = new class NoAsyncSuite implements eslint.Rule.RuleModule {
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
function hasAsyncSuite(node: any) {
|
||||
if (isCallExpression(node) && node.arguments.length >= 2 && isFunctionExpression(node.arguments[1]) && node.arguments[1].async) {
|
||||
return context.report({
|
||||
node: node as any,
|
||||
message: 'suite factory function should never be async'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
['CallExpression[callee.name=/suite$/][arguments]']: hasAsyncSuite,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
|
||||
export = new class NoTestOnly implements eslint.Rule.RuleModule {
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
return {
|
||||
['MemberExpression[object.name=/^(test|suite)$/][property.name="only"]']: (node: any) => {
|
||||
return context.report({
|
||||
node,
|
||||
message: 'only is a dev-time tool and CANNOT be pushed'
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,159 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/experimental-utils';
|
||||
|
||||
function isStringLiteral(node: TSESTree.Node | null | undefined): node is TSESTree.StringLiteral {
|
||||
return !!node && node.type === AST_NODE_TYPES.Literal && typeof node.value === 'string';
|
||||
}
|
||||
|
||||
function isDoubleQuoted(node: TSESTree.StringLiteral): boolean {
|
||||
return node.raw[0] === '"' && node.raw[node.raw.length - 1] === '"';
|
||||
}
|
||||
|
||||
export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule {
|
||||
|
||||
private static _rNlsKeys = /^[_a-zA-Z0-9][ .\-_a-zA-Z0-9]*$/;
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
messages: {
|
||||
doubleQuoted: 'Only use double-quoted strings for externalized strings.',
|
||||
badKey: 'The key \'{{key}}\' doesn\'t conform to a valid localize identifier.',
|
||||
duplicateKey: 'Duplicate key \'{{key}}\' with different message value.',
|
||||
badMessage: 'Message argument to \'{{message}}\' must be a string literal.'
|
||||
}
|
||||
};
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
|
||||
const externalizedStringLiterals = new Map<string, { call: TSESTree.CallExpression; message: TSESTree.Node }[]>();
|
||||
const doubleQuotedStringLiterals = new Set<TSESTree.Node>();
|
||||
|
||||
function collectDoubleQuotedStrings(node: TSESTree.Literal) {
|
||||
if (isStringLiteral(node) && isDoubleQuoted(node)) {
|
||||
doubleQuotedStringLiterals.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
function visitLocalizeCall(node: TSESTree.CallExpression) {
|
||||
|
||||
// localize(key, message)
|
||||
const [keyNode, messageNode] = (<TSESTree.CallExpression>node).arguments;
|
||||
|
||||
// (1)
|
||||
// extract key so that it can be checked later
|
||||
let key: string | undefined;
|
||||
if (isStringLiteral(keyNode)) {
|
||||
doubleQuotedStringLiterals.delete(keyNode);
|
||||
key = keyNode.value;
|
||||
|
||||
} else if (keyNode.type === AST_NODE_TYPES.ObjectExpression) {
|
||||
for (const property of keyNode.properties) {
|
||||
if (property.type === AST_NODE_TYPES.Property && !property.computed) {
|
||||
if (property.key.type === AST_NODE_TYPES.Identifier && property.key.name === 'key') {
|
||||
if (isStringLiteral(property.value)) {
|
||||
doubleQuotedStringLiterals.delete(property.value);
|
||||
key = property.value.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (typeof key === 'string') {
|
||||
let array = externalizedStringLiterals.get(key);
|
||||
if (!array) {
|
||||
array = [];
|
||||
externalizedStringLiterals.set(key, array);
|
||||
}
|
||||
array.push({ call: node, message: messageNode });
|
||||
}
|
||||
|
||||
// (2)
|
||||
// remove message-argument from doubleQuoted list and make
|
||||
// sure it is a string-literal
|
||||
doubleQuotedStringLiterals.delete(messageNode);
|
||||
if (!isStringLiteral(messageNode)) {
|
||||
context.report({
|
||||
loc: messageNode.loc,
|
||||
messageId: 'badMessage',
|
||||
data: { message: context.getSourceCode().getText(<any>node) }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function visitL10NCall(node: TSESTree.CallExpression) {
|
||||
|
||||
// localize(key, message)
|
||||
const [messageNode] = (<TSESTree.CallExpression>node).arguments;
|
||||
|
||||
// remove message-argument from doubleQuoted list and make
|
||||
// sure it is a string-literal
|
||||
if (isStringLiteral(messageNode)) {
|
||||
doubleQuotedStringLiterals.delete(messageNode);
|
||||
} else if (messageNode.type === AST_NODE_TYPES.ObjectExpression) {
|
||||
for (const prop of messageNode.properties) {
|
||||
if (prop.type === AST_NODE_TYPES.Property) {
|
||||
if (prop.key.type === AST_NODE_TYPES.Identifier && prop.key.name === 'message') {
|
||||
doubleQuotedStringLiterals.delete(prop.value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reportBadStringsAndBadKeys() {
|
||||
// (1)
|
||||
// report all strings that are in double quotes
|
||||
for (const node of doubleQuotedStringLiterals) {
|
||||
context.report({ loc: node.loc, messageId: 'doubleQuoted' });
|
||||
}
|
||||
|
||||
for (const [key, values] of externalizedStringLiterals) {
|
||||
|
||||
// (2)
|
||||
// report all invalid NLS keys
|
||||
if (!key.match(NoUnexternalizedStrings._rNlsKeys)) {
|
||||
for (const value of values) {
|
||||
context.report({ loc: value.call.loc, messageId: 'badKey', data: { key } });
|
||||
}
|
||||
}
|
||||
|
||||
// (2)
|
||||
// report all invalid duplicates (same key, different message)
|
||||
if (values.length > 1) {
|
||||
for (let i = 1; i < values.length; i++) {
|
||||
if (context.getSourceCode().getText(<any>values[i - 1].message) !== context.getSourceCode().getText(<any>values[i].message)) {
|
||||
context.report({ loc: values[i].call.loc, messageId: 'duplicateKey', data: { key } });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
['Literal']: (node: any) => collectDoubleQuotedStrings(node),
|
||||
['ExpressionStatement[directive] Literal:exit']: (node: any) => doubleQuotedStringLiterals.delete(node),
|
||||
|
||||
// localize(...)
|
||||
['CallExpression[callee.type="MemberExpression"][callee.object.name="nls"][callee.property.name="localize"]:exit']: (node: any) => visitLocalizeCall(node),
|
||||
|
||||
// localize2(...)
|
||||
['CallExpression[callee.type="MemberExpression"][callee.object.name="nls"][callee.property.name="localize2"]:exit']: (node: any) => visitLocalizeCall(node),
|
||||
|
||||
// vscode.l10n.t(...)
|
||||
['CallExpression[callee.type="MemberExpression"][callee.object.property.name="l10n"][callee.property.name="t"]:exit']: (node: any) => visitL10NCall(node),
|
||||
|
||||
// l10n.t(...)
|
||||
['CallExpression[callee.object.name="l10n"][callee.property.name="t"]:exit']: (node: any) => visitL10NCall(node),
|
||||
|
||||
['CallExpression[callee.name="localize"][arguments.length>=2]:exit']: (node: any) => visitLocalizeCall(node),
|
||||
['CallExpression[callee.name="localize2"][arguments.length>=2]:exit']: (node: any) => visitLocalizeCall(node),
|
||||
['Program:exit']: reportBadStringsAndBadKeys,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,151 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// FORKED FROM https://github.com/eslint/eslint/blob/b23ad0d789a909baf8d7c41a35bc53df932eaf30/lib/rules/no-unused-expressions.js
|
||||
// and added support for `OptionalCallExpression`, see https://github.com/facebook/create-react-app/issues/8107 and https://github.com/eslint/eslint/issues/12642
|
||||
|
||||
/**
|
||||
* @fileoverview Flag expressions in statement position that do not side effect
|
||||
* @author Michael Ficarra
|
||||
*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { TSESTree } from '@typescript-eslint/experimental-utils';
|
||||
import * as ESTree from 'estree';
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
|
||||
docs: {
|
||||
description: 'disallow unused expressions',
|
||||
category: 'Best Practices',
|
||||
recommended: false,
|
||||
url: 'https://eslint.org/docs/rules/no-unused-expressions'
|
||||
},
|
||||
|
||||
schema: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
allowShortCircuit: {
|
||||
type: 'boolean',
|
||||
default: false
|
||||
},
|
||||
allowTernary: {
|
||||
type: 'boolean',
|
||||
default: false
|
||||
},
|
||||
allowTaggedTemplates: {
|
||||
type: 'boolean',
|
||||
default: false
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
create(context: eslint.Rule.RuleContext) {
|
||||
const config = context.options[0] || {},
|
||||
allowShortCircuit = config.allowShortCircuit || false,
|
||||
allowTernary = config.allowTernary || false,
|
||||
allowTaggedTemplates = config.allowTaggedTemplates || false;
|
||||
|
||||
// eslint-disable-next-line jsdoc/require-description
|
||||
/**
|
||||
* @param node any node
|
||||
* @returns whether the given node structurally represents a directive
|
||||
*/
|
||||
function looksLikeDirective(node: TSESTree.Node): boolean {
|
||||
return node.type === 'ExpressionStatement' &&
|
||||
node.expression.type === 'Literal' && typeof node.expression.value === 'string';
|
||||
}
|
||||
|
||||
// eslint-disable-next-line jsdoc/require-description
|
||||
/**
|
||||
* @param predicate ([a] -> Boolean) the function used to make the determination
|
||||
* @param list the input list
|
||||
* @returns the leading sequence of members in the given list that pass the given predicate
|
||||
*/
|
||||
function takeWhile<T>(predicate: (item: T) => boolean, list: T[]): T[] {
|
||||
for (let i = 0; i < list.length; ++i) {
|
||||
if (!predicate(list[i])) {
|
||||
return list.slice(0, i);
|
||||
}
|
||||
}
|
||||
return list.slice();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line jsdoc/require-description
|
||||
/**
|
||||
* @param node a Program or BlockStatement node
|
||||
* @returns the leading sequence of directive nodes in the given node's body
|
||||
*/
|
||||
function directives(node: TSESTree.Program | TSESTree.BlockStatement): TSESTree.Node[] {
|
||||
return takeWhile(looksLikeDirective, node.body);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line jsdoc/require-description
|
||||
/**
|
||||
* @param node any node
|
||||
* @param ancestors the given node's ancestors
|
||||
* @returns whether the given node is considered a directive in its current position
|
||||
*/
|
||||
function isDirective(node: TSESTree.Node, ancestors: TSESTree.Node[]): boolean {
|
||||
const parent = ancestors[ancestors.length - 1],
|
||||
grandparent = ancestors[ancestors.length - 2];
|
||||
|
||||
return (parent.type === 'Program' || parent.type === 'BlockStatement' &&
|
||||
(/Function/u.test(grandparent.type))) &&
|
||||
directives(parent).indexOf(node) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not a given node is a valid expression. Recurses on short circuit eval and ternary nodes if enabled by flags.
|
||||
* @param node any node
|
||||
* @returns whether the given node is a valid expression
|
||||
*/
|
||||
function isValidExpression(node: TSESTree.Node): boolean {
|
||||
if (allowTernary) {
|
||||
|
||||
// Recursive check for ternary and logical expressions
|
||||
if (node.type === 'ConditionalExpression') {
|
||||
return isValidExpression(node.consequent) && isValidExpression(node.alternate);
|
||||
}
|
||||
}
|
||||
|
||||
if (allowShortCircuit) {
|
||||
if (node.type === 'LogicalExpression') {
|
||||
return isValidExpression(node.right);
|
||||
}
|
||||
}
|
||||
|
||||
if (allowTaggedTemplates && node.type === 'TaggedTemplateExpression') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (node.type === 'ExpressionStatement') {
|
||||
return isValidExpression(node.expression);
|
||||
}
|
||||
|
||||
return /^(?:Assignment|OptionalCall|Call|New|Update|Yield|Await|Chain)Expression$/u.test(node.type) ||
|
||||
(node.type === 'UnaryExpression' && ['delete', 'void'].indexOf(node.operator) >= 0);
|
||||
}
|
||||
|
||||
return {
|
||||
ExpressionStatement(node: TSESTree.ExpressionStatement) {
|
||||
if (!isValidExpression(node.expression) && !isDirective(node, <TSESTree.Node[]>context.getAncestors())) {
|
||||
context.report({ node: <ESTree.Node>node, message: `Expected an assignment or function call and instead saw an expression. ${node.expression}` });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { TSESTree } from '@typescript-eslint/experimental-utils';
|
||||
|
||||
/**
|
||||
* Enforces that all parameter properties have an explicit access modifier (public, protected, private).
|
||||
*
|
||||
* This catches a common bug where a service is accidentally made public by simply writing: `readonly prop: Foo`
|
||||
*/
|
||||
export = new class implements eslint.Rule.RuleModule {
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
function check(inNode: any) {
|
||||
const node: TSESTree.TSParameterProperty = inNode;
|
||||
|
||||
// For now, only apply to injected services
|
||||
const firstDecorator = node.decorators?.at(0);
|
||||
if (
|
||||
firstDecorator?.expression.type !== 'Identifier'
|
||||
|| !firstDecorator.expression.name.endsWith('Service')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node.accessibility) {
|
||||
context.report({
|
||||
node: inNode,
|
||||
message: 'Parameter properties must have an explicit access modifier.'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
['TSParameterProperty']: check,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { TSESTree } from '@typescript-eslint/experimental-utils';
|
||||
import { readFileSync } from 'fs';
|
||||
import { createImportRuleListener } from './utils';
|
||||
|
||||
|
||||
export = new class TranslationRemind implements eslint.Rule.RuleModule {
|
||||
|
||||
private static NLS_MODULE = 'vs/nls';
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
messages: {
|
||||
missing: 'Please add \'{{resource}}\' to ./build/lib/i18n.resources.json file to use translations here.'
|
||||
}
|
||||
};
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
return createImportRuleListener((node, path) => this._checkImport(context, node, path));
|
||||
}
|
||||
|
||||
private _checkImport(context: eslint.Rule.RuleContext, node: TSESTree.Node, path: string) {
|
||||
|
||||
if (path !== TranslationRemind.NLS_MODULE) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentFile = context.getFilename();
|
||||
const matchService = currentFile.match(/vs\/workbench\/services\/\w+/);
|
||||
const matchPart = currentFile.match(/vs\/workbench\/contrib\/\w+/);
|
||||
if (!matchService && !matchPart) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resource = matchService ? matchService[0] : matchPart![0];
|
||||
let resourceDefined = false;
|
||||
|
||||
let json;
|
||||
try {
|
||||
json = readFileSync('./build/lib/i18n.resources.json', 'utf8');
|
||||
} catch (e) {
|
||||
console.error('[translation-remind rule]: File with resources to pull from Transifex was not found. Aborting translation resource check for newly defined workbench part/service.');
|
||||
return;
|
||||
}
|
||||
const workbenchResources = JSON.parse(json).workbench;
|
||||
|
||||
workbenchResources.forEach((existingResource: any) => {
|
||||
if (existingResource.name === resource) {
|
||||
resourceDefined = true;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
if (!resourceDefined) {
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
messageId: 'missing',
|
||||
data: { resource }
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
const glob = require('glob');
|
||||
const path = require('path');
|
||||
|
||||
require('ts-node').register({ experimentalResolver: true, transpileOnly: true });
|
||||
|
||||
// Re-export all .ts files as rules
|
||||
const rules = {};
|
||||
glob.sync(`${__dirname}/*.ts`).forEach((file) => {
|
||||
rules[path.basename(file, '.ts')] = require(file);
|
||||
});
|
||||
|
||||
exports.rules = rules;
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"lib": [
|
||||
"ES2020"
|
||||
],
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"alwaysStrict": true,
|
||||
"allowJs": true,
|
||||
"strict": true,
|
||||
"exactOptionalPropertyTypes": false,
|
||||
"useUnknownInCatchVariables": false,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"newLine": "lf",
|
||||
"noEmit": true
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.js"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules/**"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { TSESTree } from '@typescript-eslint/experimental-utils';
|
||||
|
||||
export function createImportRuleListener(validateImport: (node: TSESTree.Literal, value: string) => any): eslint.Rule.RuleListener {
|
||||
|
||||
function _checkImport(node: TSESTree.Node | null) {
|
||||
if (node && node.type === 'Literal' && typeof node.value === 'string') {
|
||||
validateImport(node, node.value);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// import ??? from 'module'
|
||||
ImportDeclaration: (node: any) => {
|
||||
_checkImport((<TSESTree.ImportDeclaration>node).source);
|
||||
},
|
||||
// import('module').then(...) OR await import('module')
|
||||
['CallExpression[callee.type="Import"][arguments.length=1] > Literal']: (node: any) => {
|
||||
_checkImport(node);
|
||||
},
|
||||
// import foo = ...
|
||||
['TSImportEqualsDeclaration > TSExternalModuleReference > Literal']: (node: any) => {
|
||||
_checkImport(node);
|
||||
},
|
||||
// export ?? from 'module'
|
||||
ExportAllDeclaration: (node: any) => {
|
||||
_checkImport((<TSESTree.ExportAllDeclaration>node).source);
|
||||
},
|
||||
// export {foo} from 'module'
|
||||
ExportNamedDeclaration: (node: any) => {
|
||||
_checkImport((<TSESTree.ExportNamedDeclaration>node).source);
|
||||
},
|
||||
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/experimental-utils';
|
||||
|
||||
export = new class ApiProviderNaming implements eslint.Rule.RuleModule {
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
messages: {
|
||||
noToken: 'Function lacks a cancellation token, preferable as last argument',
|
||||
}
|
||||
};
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
|
||||
return {
|
||||
['TSInterfaceDeclaration[id.name=/.+Provider/] TSMethodSignature[key.name=/^(provide|resolve).+/]']: (node: any) => {
|
||||
|
||||
let found = false;
|
||||
for (const param of (<TSESTree.TSMethodSignature>node).params) {
|
||||
if (param.type === AST_NODE_TYPES.Identifier) {
|
||||
found = found || param.name === 'token';
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'noToken'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/experimental-utils';
|
||||
|
||||
export = new class ApiLiteralOrTypes implements eslint.Rule.RuleModule {
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
docs: { url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#creating-objects' },
|
||||
messages: { sync: '`createXYZ`-functions are constructor-replacements and therefore must return sync', }
|
||||
};
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
|
||||
return {
|
||||
['TSDeclareFunction Identifier[name=/create.*/]']: (node: any) => {
|
||||
|
||||
const decl = <TSESTree.FunctionDeclaration>(<TSESTree.Identifier>node).parent;
|
||||
|
||||
if (decl.returnType?.typeAnnotation.type !== AST_NODE_TYPES.TSTypeReference) {
|
||||
return;
|
||||
}
|
||||
if (decl.returnType.typeAnnotation.typeName.type !== AST_NODE_TYPES.Identifier) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ident = decl.returnType.typeAnnotation.typeName.name;
|
||||
if (ident === 'Promise' || ident === 'Thenable') {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'sync'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,97 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/experimental-utils';
|
||||
|
||||
export = new class ApiEventNaming implements eslint.Rule.RuleModule {
|
||||
|
||||
private static _nameRegExp = /on(Did|Will)([A-Z][a-z]+)([A-Z][a-z]+)?/;
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
docs: {
|
||||
url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#event-naming'
|
||||
},
|
||||
messages: {
|
||||
naming: 'Event names must follow this patten: `on[Did|Will]<Verb><Subject>`',
|
||||
verb: 'Unknown verb \'{{verb}}\' - is this really a verb? Iff so, then add this verb to the configuration',
|
||||
subject: 'Unknown subject \'{{subject}}\' - This subject has not been used before but it should refer to something in the API',
|
||||
unknown: 'UNKNOWN event declaration, lint-rule needs tweaking'
|
||||
}
|
||||
};
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
|
||||
const config = <{ allowed: string[]; verbs: string[] }>context.options[0];
|
||||
const allowed = new Set(config.allowed);
|
||||
const verbs = new Set(config.verbs);
|
||||
|
||||
return {
|
||||
['TSTypeAnnotation TSTypeReference Identifier[name="Event"]']: (node: any) => {
|
||||
|
||||
const def = (<TSESTree.Identifier>node).parent?.parent?.parent;
|
||||
const ident = this.getIdent(def);
|
||||
|
||||
if (!ident) {
|
||||
// event on unknown structure...
|
||||
return context.report({
|
||||
node,
|
||||
message: 'unknown'
|
||||
});
|
||||
}
|
||||
|
||||
if (allowed.has(ident.name)) {
|
||||
// configured exception
|
||||
return;
|
||||
}
|
||||
|
||||
const match = ApiEventNaming._nameRegExp.exec(ident.name);
|
||||
if (!match) {
|
||||
context.report({
|
||||
node: ident,
|
||||
messageId: 'naming'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// check that <verb> is spelled out (configured) as verb
|
||||
if (!verbs.has(match[2].toLowerCase())) {
|
||||
context.report({
|
||||
node: ident,
|
||||
messageId: 'verb',
|
||||
data: { verb: match[2] }
|
||||
});
|
||||
}
|
||||
|
||||
// check that a subject (if present) has occurred
|
||||
if (match[3]) {
|
||||
const regex = new RegExp(match[3], 'ig');
|
||||
const parts = context.getSourceCode().getText().split(regex);
|
||||
if (parts.length < 3) {
|
||||
context.report({
|
||||
node: ident,
|
||||
messageId: 'subject',
|
||||
data: { subject: match[3] }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private getIdent(def: TSESTree.Node | undefined): TSESTree.Identifier | undefined {
|
||||
if (!def) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (def.type === AST_NODE_TYPES.Identifier) {
|
||||
return def;
|
||||
} else if ((def.type === AST_NODE_TYPES.TSPropertySignature || def.type === AST_NODE_TYPES.PropertyDefinition) && def.key.type === AST_NODE_TYPES.Identifier) {
|
||||
return def.key;
|
||||
}
|
||||
|
||||
return this.getIdent(def.parent);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { TSESTree } from '@typescript-eslint/experimental-utils';
|
||||
|
||||
export = new class ApiInterfaceNaming implements eslint.Rule.RuleModule {
|
||||
|
||||
private static _nameRegExp = /^I[A-Z]/;
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
messages: {
|
||||
naming: 'Interfaces must not be prefixed with uppercase `I`',
|
||||
}
|
||||
};
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
|
||||
return {
|
||||
['TSInterfaceDeclaration Identifier']: (node: any) => {
|
||||
|
||||
const name = (<TSESTree.Identifier>node).name;
|
||||
if (ApiInterfaceNaming._nameRegExp.test(name)) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'naming'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { TSESTree } from '@typescript-eslint/experimental-utils';
|
||||
|
||||
export = new class ApiLiteralOrTypes implements eslint.Rule.RuleModule {
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
docs: { url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#enums' },
|
||||
messages: { useEnum: 'Use enums, not literal-or-types', }
|
||||
};
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
return {
|
||||
['TSTypeAnnotation TSUnionType']: (node: any) => {
|
||||
if ((<TSESTree.TSUnionType>node).types.every(value => value.type === 'TSLiteralType')) {
|
||||
context.report({
|
||||
node: node,
|
||||
messageId: 'useEnum'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { TSESTree } from '@typescript-eslint/experimental-utils';
|
||||
|
||||
export = new class ApiProviderNaming implements eslint.Rule.RuleModule {
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
messages: {
|
||||
naming: 'A provider should only have functions like provideXYZ or resolveXYZ',
|
||||
}
|
||||
};
|
||||
|
||||
private static _providerFunctionNames = /^(provide|resolve|prepare).+/;
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
|
||||
const config = <{ allowed: string[] }>context.options[0];
|
||||
const allowed = new Set(config.allowed);
|
||||
|
||||
return {
|
||||
['TSInterfaceDeclaration[id.name=/.+Provider/] TSMethodSignature']: (node: any) => {
|
||||
|
||||
|
||||
const interfaceName = (<TSESTree.TSInterfaceDeclaration>(<TSESTree.Identifier>node).parent?.parent).id.name;
|
||||
if (allowed.has(interfaceName)) {
|
||||
// allowed
|
||||
return;
|
||||
}
|
||||
|
||||
const methodName = (<any>(<TSESTree.TSMethodSignatureNonComputedName>node).key).name;
|
||||
|
||||
if (!ApiProviderNaming._providerFunctionNames.test(methodName)) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'naming'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
|
||||
export = new class ApiEventNaming implements eslint.Rule.RuleModule {
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
messages: {
|
||||
comment: 'region comments should start with a camel case identifier, `:`, then either a GH issue link or owner, e.g #region myProposalName: https://github.com/microsoft/vscode/issues/<number>',
|
||||
}
|
||||
};
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
return {
|
||||
['Program']: (_node: any) => {
|
||||
for (const comment of sourceCode.getAllComments()) {
|
||||
if (comment.type !== 'Line') {
|
||||
continue;
|
||||
}
|
||||
if (!/^\s*#region /.test(comment.value)) {
|
||||
continue;
|
||||
}
|
||||
if (!/^\s*#region ([a-z]+): (@[a-z]+|https:\/\/github.com\/microsoft\/vscode\/issues\/\d+)/i.test(comment.value)) {
|
||||
context.report({
|
||||
node: <any>comment,
|
||||
messageId: 'comment',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import { TSESTree } from '@typescript-eslint/experimental-utils';
|
||||
|
||||
export = new class ApiTypeDiscrimination implements eslint.Rule.RuleModule {
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
docs: { url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines' },
|
||||
messages: {
|
||||
noTypeDiscrimination: 'Do not use type descrimination properties'
|
||||
}
|
||||
};
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
return {
|
||||
['TSPropertySignature[optional=undefined] TSTypeAnnotation TSLiteralType Literal']: (node: any) => {
|
||||
|
||||
const raw = String((<TSESTree.Literal>node).raw)
|
||||
|
||||
if (/^('|").*\1$/.test(raw)) {
|
||||
|
||||
context.report({
|
||||
node: node,
|
||||
messageId: 'noTypeDiscrimination'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
|
||||
export = new class ApiEventNaming implements eslint.Rule.RuleModule {
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
messages: {
|
||||
usage: 'Use the Thenable-type instead of the Promise type',
|
||||
}
|
||||
};
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
|
||||
|
||||
|
||||
return {
|
||||
['TSTypeAnnotation TSTypeReference Identifier[name="Promise"]']: (node: any) => {
|
||||
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'usage',
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as eslint from 'eslint';
|
||||
import type * as estree from 'estree';
|
||||
|
||||
export = new class ApiVsCodeInComments implements eslint.Rule.RuleModule {
|
||||
|
||||
readonly meta: eslint.Rule.RuleMetaData = {
|
||||
messages: {
|
||||
comment: `Don't use the term 'vs code' in comments`
|
||||
}
|
||||
};
|
||||
|
||||
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
|
||||
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
return {
|
||||
['Program']: (_node: any) => {
|
||||
|
||||
for (const comment of sourceCode.getAllComments()) {
|
||||
if (comment.type !== 'Block') {
|
||||
continue;
|
||||
}
|
||||
if (!comment.range) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const startIndex = comment.range[0] + '/*'.length;
|
||||
const re = /vs code/ig;
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = re.exec(comment.value))) {
|
||||
// Allow using 'VS Code' in quotes
|
||||
if (comment.value[match.index - 1] === `'` && comment.value[match.index + match[0].length] === `'`) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Types for eslint seem incorrect
|
||||
const start = sourceCode.getLocFromIndex(startIndex + match.index) as any as estree.Position;
|
||||
const end = sourceCode.getLocFromIndex(startIndex + match.index + match[0].length) as any as estree.Position;
|
||||
context.report({
|
||||
messageId: 'comment',
|
||||
loc: { start, end }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
+1110
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
# https://git-scm.com/docs/git-blame#Documentation/git-blame.txt---ignore-revs-fileltfilegt
|
||||
# https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view
|
||||
|
||||
# mjbvz: Fix spacing
|
||||
13f4f052582bcec3d6c6c6a70d995c9dee2cac13
|
||||
|
||||
# mjbvz: Add script to run build with noImplicitOverride
|
||||
ae1452eea678f5266ef513f22dacebb90955d6c9
|
||||
|
||||
# alexdima: Revert "bump version"
|
||||
537ba0ef1791c090bb18bc68d727816c0451c117
|
||||
|
||||
# alexdima: bump version
|
||||
387a0dcb82df729e316ca2518a9ed81a75482b18
|
||||
|
||||
# joaomoreno: add ghooks dev dependency
|
||||
0dfc06e0f9de5925de792cdf9f0e6597bb25908f
|
||||
|
||||
# joaomoreno: line endings
|
||||
12ab70d329a13dd5b18d892cd40edd7138259bc3
|
||||
|
||||
# mjbvz: organize imports
|
||||
494cbbd02d67e87727ec885f98d19551aa33aad1
|
||||
a3cb14be7f2cceadb17adf843675b1a59537dbbd
|
||||
ee1655a82ebdfd38bf8792088a6602c69f7bbd94
|
||||
|
||||
# jrieken: new eslint-rule
|
||||
4a130c40ed876644ed8af2943809d08221375408
|
||||
@@ -0,0 +1,11 @@
|
||||
* text=auto
|
||||
|
||||
LICENSE.txt eol=crlf
|
||||
ThirdPartyNotices.txt eol=crlf
|
||||
|
||||
*.bat eol=crlf
|
||||
*.cmd eol=crlf
|
||||
*.ps1 eol=lf
|
||||
*.sh eol=lf
|
||||
*.rtf -text
|
||||
**/*.json linguist-language=jsonc
|
||||
@@ -0,0 +1,3 @@
|
||||
# ensure the API police is aware of changes to the vscode-dts file
|
||||
# this is only about the final API, not about proposed API changes
|
||||
src/vscode-dts/vscode.d.ts @jrieken @mjbvz
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
---
|
||||
<!-- ⚠️⚠️ Do Not Delete This! bug_report_template ⚠️⚠️ -->
|
||||
<!-- Please read our Rules of Conduct: https://opensource.microsoft.com/codeofconduct/ -->
|
||||
<!-- 🕮 Read our guide about submitting issues: https://github.com/microsoft/vscode/wiki/Submitting-Bugs-and-Suggestions -->
|
||||
<!-- 🔎 Search existing issues to avoid creating duplicates. -->
|
||||
<!-- 🧪 Test using the latest Insiders build to see if your issue has already been fixed: https://code.visualstudio.com/insiders/ -->
|
||||
<!-- 💡 Instead of creating your report here, use 'Report Issue' from the 'Help' menu in VS Code to pre-fill useful information. -->
|
||||
<!-- 🔧 Launch with `code --disable-extensions` to check. -->
|
||||
Does this issue occur when all extensions are disabled?: Yes/No
|
||||
|
||||
<!-- 🪓 If you answered No above, use 'Help: Start Extension Bisect' from Command Palette to try to identify the cause. -->
|
||||
<!-- 📣 Issues caused by an extension need to be reported directly to the extension publisher. The 'Help > Report Issue' dialog can assist with this. -->
|
||||
- VS Code Version:
|
||||
- OS Version:
|
||||
|
||||
Steps to Reproduce:
|
||||
|
||||
1.
|
||||
2.
|
||||
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Question
|
||||
url: https://stackoverflow.com/questions/tagged/visual-studio-code
|
||||
about: Please ask and answer questions here.
|
||||
- name: Extension Development
|
||||
url: https://github.com/microsoft/vscode-discussions/discussions
|
||||
about: Please use this for extension development questions and ideas.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
<!-- ⚠️⚠️ Do Not Delete This! feature_request_template ⚠️⚠️ -->
|
||||
<!-- Please read our Rules of Conduct: https://opensource.microsoft.com/codeofconduct/ -->
|
||||
<!-- Please search existing issues to avoid creating duplicates. -->
|
||||
|
||||
<!-- Describe the feature you'd like. -->
|
||||
@@ -0,0 +1,312 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/microsoft/vscode-github-triage-actions/stable/classifier-deep/apply/apply-labels/deep-classifier-config.schema.json",
|
||||
"vacation": [],
|
||||
"assignees": {
|
||||
"nameToOverrideAccuracyOf": {"accuracy": 0.8}
|
||||
},
|
||||
"labels": {
|
||||
"accessibility": { "assign": ["meganrogge"]},
|
||||
"api": {"assign": ["jrieken"]},
|
||||
"api-finalization": {"assign": []},
|
||||
"api-proposal": {"assign": ["jrieken"]},
|
||||
"authentication": {"assign": ["TylerLeonhardt"]},
|
||||
"bisect-ext": {"assign": ["jrieken"]},
|
||||
"bot-proposal": {"assign": ["lramos15"]},
|
||||
"bracket-pair-colorization": {"assign": ["hediet"]},
|
||||
"bracket-pair-guides": {"assign": ["hediet"]},
|
||||
"breadcrumbs": {"assign": ["jrieken"]},
|
||||
"callhierarchy": {"assign": ["jrieken"]},
|
||||
"chrome-devtools": {"assign": ["deepak1556"]},
|
||||
"cloud-changes": {"assign": ["joyceerhl"]},
|
||||
"code-cli": {"assign": ["connor4312"]},
|
||||
"code-lens": {"assign": ["jrieken"]},
|
||||
"code-server-web": {"assign": ["aeschli"]},
|
||||
"command-center": {"assign": ["jrieken"]},
|
||||
"comments": {"assign": ["alexr00"]},
|
||||
"config": {"assign": ["sandy081"]},
|
||||
"containers": {"assign": ["chrmarti"]},
|
||||
"context-keys": {"assign": ["ulugbekna"]},
|
||||
"continue-working-on": {"assign": ["joyceerhl"]},
|
||||
"css-less-scss": {"assign": ["aeschli"]},
|
||||
"custom-editors": {"assign": ["mjbvz"]},
|
||||
"debug": {"assign": ["roblourens"]},
|
||||
"debug-disassembly": {"assign": []},
|
||||
"dialogs": {"assign": ["sbatten"]},
|
||||
"diff-editor": {"assign": ["hediet"]},
|
||||
"dropdown": {"assign": ["lramos15"]},
|
||||
"editor-api": {"assign": ["alexdima"]},
|
||||
"editor-autoclosing": {"assign": ["alexdima"]},
|
||||
"editor-autoindent": {"assign": ["rebornix"]},
|
||||
"editor-bracket-matching": {"assign": ["hediet"]},
|
||||
"editor-clipboard": {"assign": ["alexdima", "rebornix"]},
|
||||
"editor-code-actions": {"assign": ["mjbvz", "justschen"]},
|
||||
"editor-color-picker": {"assign": ["aiday-mar"]},
|
||||
"editor-columnselect": {"assign": ["alexdima"]},
|
||||
"editor-commands": {"assign": ["alexdima"]},
|
||||
"editor-comments": {"assign": ["alexdima"]},
|
||||
"editor-contrib": {"assign": ["alexdima"]},
|
||||
"editor-core": {"assign": ["alexdima"]},
|
||||
"editor-drag-and-drop": {"assign": ["rebornix"]},
|
||||
"editor-error-widget": {"assign": ["sandy081"]},
|
||||
"editor-find": {"assign": ["rebornix"]},
|
||||
"editor-folding": {"assign": ["aeschli"]},
|
||||
"editor-highlight": {"assign": ["alexdima"]},
|
||||
"editor-hover": {"assign": ["aiday-mar"]},
|
||||
"editor-indent-detection": {"assign": ["alexdima"]},
|
||||
"editor-indent-guides": {"assign": ["hediet"]},
|
||||
"editor-input": {"assign": ["alexdima"]},
|
||||
"editor-input-IME": {"assign": ["alexdima"]},
|
||||
"editor-insets": {"assign": ["jrieken"]},
|
||||
"editor-minimap": {"assign": ["alexdima"]},
|
||||
"editor-multicursor": {"assign": ["alexdima"]},
|
||||
"editor-parameter-hints": {"assign": ["mjbvz"]},
|
||||
"editor-render-whitespace": {"assign": ["alexdima"]},
|
||||
"editor-rendering": {"assign": ["alexdima"]},
|
||||
"editor-RTL": {"assign": ["alexdima"]},
|
||||
"editor-scrollbar": {"assign": ["alexdima"]},
|
||||
"editor-sorting": {"assign": ["alexdima"]},
|
||||
"editor-sticky-scroll": {"assign": ["aiday-mar"]},
|
||||
"editor-symbols": {"assign": ["jrieken"]},
|
||||
"editor-synced-region": {"assign": ["aeschli"]},
|
||||
"editor-textbuffer": {"assign": ["alexdima", "rebornix"]},
|
||||
"editor-theming": {"assign": ["alexdima"]},
|
||||
"editor-wordnav": {"assign": ["alexdima"]},
|
||||
"editor-wrapping": {"assign": ["alexdima"]},
|
||||
"emmet": {"assign": ["rzhao271"]},
|
||||
"emmet-parse": {"assign": ["rzhao271"]},
|
||||
"error-list": {"assign": ["sandy081"]},
|
||||
"extension-activation": {"assign": ["joyceerhl", "alexdima"]},
|
||||
"extension-host": {"assign": ["alexdima"]},
|
||||
"extension-prerelease": {"assign": ["sandy081"]},
|
||||
"extension-recommendations": {"assign": ["sandy081"]},
|
||||
"extensions": {"assign": ["sandy081"]},
|
||||
"extensions-development": {"assign": []},
|
||||
"file-decorations": {"assign": ["jrieken"]},
|
||||
"file-encoding": {"assign": ["bpasero"]},
|
||||
"file-explorer": {"assign": ["lramos15"]},
|
||||
"file-glob": {"assign": ["bpasero"]},
|
||||
"file-io": {"assign": ["bpasero"]},
|
||||
"file-nesting": {"assign": ["lramos15"]},
|
||||
"file-watcher": {"assign": ["bpasero"]},
|
||||
"font-rendering": {"assign": ["rzhao271"]},
|
||||
"formatting": {"assign": ["jrieken"]},
|
||||
"getting-started": {"assign": ["bhavyaus"]},
|
||||
"ghost-text": {"assign": ["hediet"]},
|
||||
"git": {"assign": ["lszomoru"]},
|
||||
"github": {"assign": ["lszomoru"]},
|
||||
"github-authentication": {"assign": ["TylerLeonhardt"]},
|
||||
"github-repositories": {"assign": ["lszomoru"]},
|
||||
"gpu": {"assign": ["deepak1556"]},
|
||||
"grammar": {"assign": ["mjbvz"]},
|
||||
"grid-widget": {"assign": ["joaomoreno"]},
|
||||
"html": {"assign": ["aeschli"]},
|
||||
"icon-brand": {"assign": ["daviddossett"]},
|
||||
"icons-product": {"assign": ["daviddossett"]},
|
||||
"image-preview": {"assign": ["mjbvz"]},
|
||||
"inlay-hints": {"assign": ["jrieken", "hediet"]},
|
||||
"inline-completions": {"assign": ["hediet"]},
|
||||
"install-update": {"assign": ["joaomoreno"], "accuracy": 0.85},
|
||||
"intellisense-config": {"assign": ["rzhao271"]},
|
||||
"interactive-playground": {"assign": ["chrmarti"]},
|
||||
"interactive-window": {"assign": ["amunger", "rebornix"]},
|
||||
"ipc": {"assign": ["joaomoreno"]},
|
||||
"issue-bot": {"assign": ["chrmarti"]},
|
||||
"issue-reporter": {"assign": ["justschen"]},
|
||||
"javascript": {"assign": ["mjbvz"]},
|
||||
"json": {"assign": ["aeschli"]},
|
||||
"json-sorting": {"assign": ["aiday-mar"]},
|
||||
"keybindings": {"assign": ["ulugbekna"]},
|
||||
"keybindings-editor": {"assign": ["ulugbekna"]},
|
||||
"keyboard-layout": {"assign": ["ulugbekna"]},
|
||||
"L10N": {"assign": ["TylerLeonhardt", "csigs"]},
|
||||
"l10n-platform": {"assign": ["TylerLeonhardt"]},
|
||||
"label-provider": {"assign": ["lramos15"]},
|
||||
"languages-basic": {"assign": ["aeschli"]},
|
||||
"languages-diagnostics": {"assign": ["jrieken"]},
|
||||
"languages-guessing": {"assign": ["TylerLeonhardt"]},
|
||||
"layout": {"assign": ["sbatten"]},
|
||||
"lcd-text-rendering": {"assign": []},
|
||||
"list-widget": {"assign": ["joaomoreno"]},
|
||||
"live-preview": {"assign": ["andreamah"]},
|
||||
"log": {"assign": ["sandy081"]},
|
||||
"markdown": {"assign": ["mjbvz"]},
|
||||
"marketplace": {"assign": ["isidorn"]},
|
||||
"menus": {"assign": ["sbatten"]},
|
||||
"merge-conflict": {"assign": ["chrmarti"]},
|
||||
"merge-editor": {"assign": ["hediet"]},
|
||||
"merge-editor-workbench": {"assign": ["jrieken"]},
|
||||
"monaco-editor": {"assign": []},
|
||||
"native-file-dialog": {"assign": ["deepak1556"]},
|
||||
"network": {"assign": ["deepak1556"]},
|
||||
"notebook": {"assign": ["rebornix"]},
|
||||
"notebook-api": {"assign": []},
|
||||
"notebook-builtin-renderers": {"assign": []},
|
||||
"notebook-cell-editor": {"assign": []},
|
||||
"notebook-celltoolbar": {"assign": []},
|
||||
"notebook-clipboard": {"assign": []},
|
||||
"notebook-commenting": {"assign": []},
|
||||
"notebook-debugging": {"assign": []},
|
||||
"notebook-diff": {"assign": []},
|
||||
"notebook-dnd": {"assign": []},
|
||||
"notebook-execution": {"assign": []},
|
||||
"notebook-find": {"assign": []},
|
||||
"notebook-folding": {"assign": []},
|
||||
"notebook-getting-started": {"assign": []},
|
||||
"notebook-getting-globaltoolbar": {"assign": []},
|
||||
"notebook-ipynb": {"assign": []},
|
||||
"notebook-kernel": {"assign": []},
|
||||
"notebook-kernel-picker": {"assign": []},
|
||||
"notebook-keybinding": {"assign": []},
|
||||
"notebook-language": {"assign": []},
|
||||
"notebook-layout": {"assign": []},
|
||||
"notebook-markdown": {"assign": []},
|
||||
"notebook-math": {"assign": []},
|
||||
"notebook-minimap": {"assign": []},
|
||||
"notebook-multiselect": {"assign": []},
|
||||
"notebook-output": {"assign": []},
|
||||
"notebook-perf": {"assign": []},
|
||||
"notebook-remote": {"assign": []},
|
||||
"notebook-rendering": {"assign": []},
|
||||
"notebook-serialization": {"assign": []},
|
||||
"notebook-serverless-web": {"assign": []},
|
||||
"notebook-statusbar": {"assign": []},
|
||||
"notebook-toc-outline": {"assign": []},
|
||||
"notebook-undo-redo": {"assign": []},
|
||||
"notebook-variables": {"assign": []},
|
||||
"notebook-workbench-integration": {"assign": []},
|
||||
"notebook-workflow": {"assign": []},
|
||||
"open-editors": {"assign": ["lramos15"]},
|
||||
"opener": {"assign": ["mjbvz"]},
|
||||
"outline": {"assign": ["jrieken"]},
|
||||
"output": {"assign": ["sandy081"]},
|
||||
"perf": {"assign": []},
|
||||
"perf-bloat": {"assign": []},
|
||||
"perf-startup": {"assign": []},
|
||||
"php": {"assign": ["roblourens"]},
|
||||
"portable-mode": {"assign": ["joaomoreno"]},
|
||||
"proxy": {"assign": ["chrmarti"]},
|
||||
"quick-open": {"assign": ["TylerLeonhardt"]},
|
||||
"quick-pick": {"assign": ["TylerLeonhardt"]},
|
||||
"references-viewlet": {"assign": ["jrieken"]},
|
||||
"release-notes": {"assign": []},
|
||||
"remote": {"assign": []},
|
||||
"remote-connection": {"assign": ["alexdima"]},
|
||||
"remote-explorer": {"assign": ["alexr00"]},
|
||||
"remote-tunnel": {"assign": ["aeschli", "connor4312"]},
|
||||
"rename": {"assign": ["jrieken"]},
|
||||
"runCommands": {"assign": ["ulugbekna"]},
|
||||
"sandbox": {"assign": ["deepak1556"]},
|
||||
"sash-widget": {"assign": ["joaomoreno"]},
|
||||
"scm": {"assign": ["lszomoru"]},
|
||||
"screencast-mode": {"assign": ["joaomoreno"]},
|
||||
"search": {"assign": ["andreamah", "roblourens"]},
|
||||
"search-api": {"assign": ["andreamah", "roblourens"]},
|
||||
"search-editor": {"assign": ["andreamah", "roblourens"]},
|
||||
"search-replace": {"assign": ["sandy081"]},
|
||||
"semantic-tokens": {"assign": ["alexdima", "aeschli"]},
|
||||
"server": {"assign": ["alexdima"]},
|
||||
"settings-editor": {"assign": ["rzhao271"]},
|
||||
"settings-search": {"assign": ["rzhao271"]},
|
||||
"settings-sync": {"assign": ["sandy081"]},
|
||||
"settings-sync-server": {"assign": ["Tyriar", "lszomoru"]},
|
||||
"shared-process": {"assign": []},
|
||||
"simple-file-dialog": {"assign": ["alexr00"]},
|
||||
"smart-select": {"assign": ["jrieken"]},
|
||||
"snap": {"assign": ["deepak1556"]},
|
||||
"snippets": {"assign": ["jrieken"]},
|
||||
"splitview-widget": {"assign": ["joaomoreno"]},
|
||||
"ssh": {"assign": ["eleanorjboyd"]},
|
||||
"suggest": {"assign": ["jrieken"]},
|
||||
"table-widget": {"assign": ["joaomoreno"]},
|
||||
"tasks": {"assign": ["meganrogge"], "accuracy": 0.85},
|
||||
"telemetry": {"assign": ["lramos15"]},
|
||||
"terminal": {"assign": ["meganrogge"]},
|
||||
"terminal-conpty": {"assign": ["meganrogge"]},
|
||||
"terminal-editors": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-external": {"assign": ["meganrogge"]},
|
||||
"terminal-find": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-input": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-layout": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-links": {"assign": ["Tyriar"]},
|
||||
"terminal-local-echo": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-persistence": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-process": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-profiles": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-quick-fix": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-rendering": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-search": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-shell-bash": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-shell-cmd": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-shell-fish": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-shell-git-bash": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-shell-integration": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-shell-pwsh": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-shell-zsh": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"terminal-tabs": {"assign": ["meganrogge"]},
|
||||
"terminal-winpty": {"assign": ["Tyriar", "meganrogge"]},
|
||||
"testing": {"assign": ["connor4312"]},
|
||||
"themes": {"assign": ["aeschli"]},
|
||||
"timeline": {"assign": ["lramos15"]},
|
||||
"timeline-git": {"assign": ["lszomoru"]},
|
||||
"timeline-local-history": {"assign": ["bpasero"]},
|
||||
"titlebar": {"assign": ["sbatten"]},
|
||||
"tokenization": {"assign": ["alexdima"]},
|
||||
"touch/pointer": {"assign": []},
|
||||
"trackpad/scroll": {"assign": []},
|
||||
"tree-sticky-scroll": {"assign": ["benibenj"]},
|
||||
"tree-views": {"assign": ["alexr00"]},
|
||||
"tree-widget": {"assign": ["joaomoreno"]},
|
||||
"typehierarchy": {"assign": ["jrieken"]},
|
||||
"typescript": {"assign": ["mjbvz"]},
|
||||
"undo-redo": {"assign": ["alexdima"]},
|
||||
"unicode-highlight": {"assign": ["hediet"]},
|
||||
"uri": {"assign": ["jrieken"]},
|
||||
"user-profiles": {"assign": ["sandy081"]},
|
||||
"ux": {"assign": ["daviddossett"]},
|
||||
"variable-resolving": {"assign": ["alexr00"]},
|
||||
"VIM": {"assign": ["alexdima", "rebornix"]},
|
||||
"virtual-workspaces": {"assign": []},
|
||||
"vscode.dev": {"assign": []},
|
||||
"vscode-build": {"assign": []},
|
||||
"vscode-website": {"assign": ["rzhao271"]},
|
||||
"web": {"assign": []},
|
||||
"webview": {"assign": ["mjbvz"]},
|
||||
"webview-views": {"assign": ["mjbvz"]},
|
||||
"workbench-actions": {"assign": ["bpasero"]},
|
||||
"workbench-auxwindow": {"assign": ["bpasero"]},
|
||||
"workbench-banner": {"assign": ["lszomoru", "sbatten"]},
|
||||
"workbench-cli": {"assign": ["bpasero"]},
|
||||
"workbench-diagnostics": {"assign": ["Tyriar"]},
|
||||
"workbench-dnd": {"assign": ["bpasero"]},
|
||||
"workbench-editor-grid": {"assign": ["sbatten"]},
|
||||
"workbench-editor-groups": {"assign": ["bpasero"]},
|
||||
"workbench-editor-resolver": {"assign": ["lramos15"]},
|
||||
"workbench-editors": {"assign": ["bpasero"]},
|
||||
"workbench-electron": {"assign": ["deepak1556"]},
|
||||
"workbench-fonts": {"assign": []},
|
||||
"workbench-history": {"assign": ["bpasero"]},
|
||||
"workbench-hot-exit": {"assign": ["bpasero"]},
|
||||
"workbench-hover": {"assign": ["Tyriar"]},
|
||||
"workbench-launch": {"assign": []},
|
||||
"workbench-link": {"assign": []},
|
||||
"workbench-multiroot": {"assign": ["bpasero"]},
|
||||
"workbench-notifications": {"assign": ["bpasero"]},
|
||||
"workbench-os-integration": {"assign": ["bpasero"]},
|
||||
"workbench-rapid-render": {"assign": ["jrieken"]},
|
||||
"workbench-run-as-admin": {"assign": ["bpasero"]},
|
||||
"workbench-state": {"assign": ["bpasero"]},
|
||||
"workbench-status": {"assign": ["bpasero"]},
|
||||
"workbench-tabs": {"assign": ["benibenj"]},
|
||||
"workbench-touchbar": {"assign": ["bpasero"]},
|
||||
"workbench-untitled-editors": {"assign": ["bpasero"]},
|
||||
"workbench-views": {"assign": ["sbatten"]},
|
||||
"workbench-welcome": {"assign": ["lramos15"]},
|
||||
"workbench-window": {"assign": ["bpasero"]},
|
||||
"workbench-workspace": {"assign": []},
|
||||
"workbench-zen": {"assign": ["sbatten"]},
|
||||
"workspace-edit": {"assign": ["jrieken"]},
|
||||
"workspace-symbols": {"assign": []},
|
||||
"workspace-trust": {"assign": ["lszomoru", "sbatten"]},
|
||||
"zoom": {"assign": ["alexdima"] }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,542 @@
|
||||
[
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "question",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "updateLabels",
|
||||
"addLabel": "*question"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "dev-question",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "updateLabels",
|
||||
"addLabel": "*dev-question"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "*question",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "We closed this issue because it is a question about using VS Code rather than an issue or feature request. Please search for help on [StackOverflow](https://aka.ms/vscodestackoverflow), where the community has already answered thousands of similar questions. You may find their [guide on asking a new question](https://aka.ms/vscodestackoverflowquestion) helpful if your question has not already been asked. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "*dev-question",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "We have a great extension developer community over on [GitHub discussions](https://github.com/microsoft/vscode-discussions/discussions) and [Slack](https://vscode-dev-community.slack.com/) where extension authors help each other. This is a great place for you to ask questions and find support.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "*extension-candidate",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "We try to keep VS Code lean and we think the functionality you're asking for is great for a VS Code extension. Maybe you can already find one that suits you in the [VS Code Marketplace](https://aka.ms/vscodemarketplace). Just in case, in a few simple steps you can get started [writing your own extension](https://aka.ms/vscodewritingextensions). See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "*not-reproducible",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "We closed this issue because we are unable to reproduce the problem with the steps you describe. Chances are we've already fixed your problem in a recent version of VS Code. If not, please ask us to reopen the issue and provide us with more detail. Our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) might help you with that.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "*out-of-scope",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "We closed this issue because we [don't plan to address it](https://aka.ms/vscode-out-of-scope) in the foreseeable future. If you disagree and feel that this issue is crucial: we are happy to listen and to reconsider.\n\nIf you wonder what we are up to, please see our [roadmap](https://aka.ms/vscoderoadmap) and [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nThanks for your understanding, and happy coding!"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "wont-fix",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "We closed this issue because we [don't plan to address it](https://github.com/microsoft/vscode/wiki/Issue-Grooming#wont-fix-bugs).\n\nThanks for your understanding, and happy coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "causedByExtension",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "updateLabels",
|
||||
"addLabel": "*caused-by-extension"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "*caused-by-extension",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "This issue is caused by an extension, please file it with the repository (or contact) the extension has linked in its overview in VS Code or the [marketplace](https://aka.ms/vscodemarketplace) for VS Code. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting). If you don't know which extension is causing the problem, you can run `Help: Start extension bisect` from the command palette (F1) to help identify the problem extension.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "*as-designed",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "The described behavior is how it is expected to work. If you disagree, please explain what is expected and what is not in more detail. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "L10N",
|
||||
"assign": [
|
||||
"csigs",
|
||||
"TylerLeonhardt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "duplicate",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "updateLabels",
|
||||
"addLabel": "*duplicate"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "*duplicate",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "Thanks for creating this issue! We figured it's covering the same as another one we already have. Thus, we closed this one as a duplicate. You can search for [similar existing issues](${duplicateQuery}). See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "verified",
|
||||
"allowUsers": [
|
||||
"@author"
|
||||
],
|
||||
"action": "updateLabels",
|
||||
"addLabel": "verified",
|
||||
"removeLabel": "author-verification-requested",
|
||||
"requireLabel": "author-verification-requested",
|
||||
"disallowLabel": "unreleased"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "confirm",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "updateLabels",
|
||||
"addLabel": "confirmed",
|
||||
"removeLabel": "confirmation-pending"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "confirmationPending",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "updateLabels",
|
||||
"addLabel": "confirmation-pending",
|
||||
"removeLabel": "confirmed"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "needsMoreInfo",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "updateLabels",
|
||||
"addLabel": "~info-needed"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "needsPerfInfo",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"addLabel": "info-needed",
|
||||
"comment": "Thanks for creating this issue regarding performance! Please follow this guide to help us diagnose performance issues: https://github.com/microsoft/vscode/wiki/Performance-Issues \n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "jsDebugLogs",
|
||||
"action": "updateLabels",
|
||||
"addLabel": "info-needed",
|
||||
"comment": "Please collect trace logs using the following instructions:\n\n> If you're able to, add `\"trace\": true` to your `launch.json` and reproduce the issue. The location of the log file on your disk will be written to the Debug Console. Share that with us.\n>\n> ⚠️ This log file will not contain source code, but will contain file paths. You can drop it into https://microsoft.github.io/vscode-pwa-analyzer/index.html to see what it contains. If you'd rather not share the log publicly, you can email it to connor@xbox.com"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "closedWith",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "completed",
|
||||
"addLabel": "unreleased"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "spam",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "invalid"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "a11ymas",
|
||||
"allowUsers": [
|
||||
"AccessibilityTestingTeam-TCS",
|
||||
"dixitsonali95",
|
||||
"Mohini78",
|
||||
"ChitrarupaSharma",
|
||||
"mspatil110",
|
||||
"umasarath52",
|
||||
"v-umnaik"
|
||||
],
|
||||
"action": "updateLabels",
|
||||
"addLabel": "a11ymas"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "*off-topic",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "Thanks for creating this issue. We think this issue is unactionable or unrelated to the goals of this project. Please follow our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extPython",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the Python extension. Please file the issue to the [Python extension repository](https://github.com/microsoft/vscode-python). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extJupyter",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the Jupyter extension. Please file the issue to the [Jupyter extension repository](https://github.com/microsoft/vscode-jupyter). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extC",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the C extension. Please file the issue to the [C extension repository](https://github.com/microsoft/vscode-cpptools). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extC++",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the C++ extension. Please file the issue to the [C++ extension repository](https://github.com/microsoft/vscode-cpptools). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extCpp",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the C++ extension. Please file the issue to the [C++ extension repository](https://github.com/microsoft/vscode-cpptools). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extTS",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the TypeScript language service. Please file the issue to the [TypeScript repository](https://github.com/microsoft/TypeScript/). Make sure to check their [contributing guidelines](https://github.com/microsoft/TypeScript/blob/master/CONTRIBUTING.md) and provide relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extJS",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the TypeScript/JavaScript language service. Please file the issue to the [TypeScript repository](https://github.com/microsoft/TypeScript/). Make sure to check their [contributing guidelines](https://github.com/microsoft/TypeScript/blob/master/CONTRIBUTING.md) and provide relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extC#",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the C# extension. Please file the issue to the [C# extension repository](https://github.com/OmniSharp/omnisharp-vscode.git). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extGo",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the Go extension. Please file the issue to the [Go extension repository](https://github.com/golang/vscode-go). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extPowershell",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the PowerShell extension. Please file the issue to the [PowerShell extension repository](https://github.com/PowerShell/vscode-powershell). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extLiveShare",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the LiveShare extension. Please file the issue to the [LiveShare repository](https://github.com/MicrosoftDocs/live-share). Make sure to check their [contributing guidelines](https://github.com/MicrosoftDocs/live-share/blob/master/CONTRIBUTING.md) and provide relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extDocker",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the Docker extension. Please file the issue to the [Docker extension repository](https://github.com/microsoft/vscode-docker). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extJava",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the Java extension. Please file the issue to the [Java extension repository](https://github.com/redhat-developer/vscode-java). Make sure to check their [troubleshooting instructions](https://github.com/redhat-developer/vscode-java/wiki/Troubleshooting) and provide relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extJavaDebug",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the Java Debugger extension. Please file the issue to the [Java Debugger repository](https://github.com/microsoft/vscode-java-debug). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extCodespaces",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the Codespaces extension. Please file the issue in the [Codespaces Discussion Forum](http://aka.ms/ghcs-feedback). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "extCopilot",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"addLabel": "*caused-by-extension",
|
||||
"comment": "It looks like this is caused by the Copilot extension. Please file the issue in the [Copilot Discussion Forum](https://github.com/community/community/discussions/categories/copilot). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "gifPlease",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "comment",
|
||||
"addLabel": "info-needed",
|
||||
"comment": "Thanks for reporting this issue! Unfortunately, it's hard for us to understand what issue you're seeing. Please help us out by providing a screen recording showing exactly what isn't working as expected. While we can work with most standard formats, `.gif` files are preferred as they are displayed inline on GitHub. You may find https://gifcap.dev helpful as a browser-based gif recording tool.\n\nIf the issue depends on keyboard input, you can help us by enabling screencast mode for the recording (`Developer: Toggle Screencast Mode` in the command palette). Lastly, please attach this file via the GitHub web interface as emailed responses will strip files out from the issue.\n\nHappy coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "confirmPlease",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"action": "comment",
|
||||
"addLabel": "info-needed",
|
||||
"comment": "Please diagnose the root cause of the issue by running the command `F1 > Help: Troubleshoot Issue` and following the instructions. Once you have done that, please update the issue with the results.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"__comment__": "Allows folks on the team to label issues by commenting: `\\label My-Label` ",
|
||||
"type": "comment",
|
||||
"name": "label",
|
||||
"allowUsers": []
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "assign",
|
||||
"allowUsers": [
|
||||
"cleidigh",
|
||||
"usernamehw",
|
||||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "*workspace-trust-docs",
|
||||
"action": "close",
|
||||
"reason": "not_planned",
|
||||
"comment": "This issue appears to be the result of the new workspace trust feature shipped in June 2021. This security-focused feature has major impact on the functionality of VS Code. Due to the volume of issues, we ask that you take some time to review our [comprehensive documentation](https://aka.ms/vscode-workspace-trust) on the feature. If your issue is still not resolved, please let us know."
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "~verification-steps-needed",
|
||||
"action": "updateLabels",
|
||||
"addLabel": "verification-steps-needed",
|
||||
"removeLabel": "~verification-steps-needed",
|
||||
"comment": "Friendly ping! Looks like this issue requires some further steps to be verified. Please provide us with the steps necessary to verify this issue."
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "~info-needed",
|
||||
"action": "updateLabels",
|
||||
"addLabel": "info-needed",
|
||||
"removeLabel": "~info-needed",
|
||||
"comment": "Thanks for creating this issue! We figured it's missing some basic information or in some other way doesn't follow our [issue reporting guidelines](https://aka.ms/vscodeissuereporting). Please take the time to review these and update the issue.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "~version-info-needed",
|
||||
"action": "updateLabels",
|
||||
"addLabel": "info-needed",
|
||||
"removeLabel": "~version-info-needed",
|
||||
"comment": "Thanks for creating this issue! We figured it's missing some basic information, such as a version number, or in some other way doesn't follow our [issue reporting guidelines](https://aka.ms/vscodeissuereporting). Please take the time to review these and update the issue.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "~confirmation-needed",
|
||||
"action": "updateLabels",
|
||||
"addLabel": "info-needed",
|
||||
"removeLabel": "~confirmation-needed",
|
||||
"comment": "Please diagnose the root cause of the issue by running the command `F1 > Help: Troubleshoot Issue` and following the instructions. Once you have done that, please update the issue with the results.\n\nHappy Coding!"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
perform: true,
|
||||
commands:
|
||||
[
|
||||
{
|
||||
type: "comment",
|
||||
name: "findDuplicates",
|
||||
allowUsers: ["cleidigh", "usernamehw", "gjsjohnmurray", "IllusionMH"],
|
||||
action: "comment",
|
||||
comment: "Potential duplicates:\n${potentialDuplicates}",
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# Learn more about the syntax here:
|
||||
# https://docs.github.com/en/early-access/github/save-time-with-slash-commands/syntax-for-user-defined-slash-commands
|
||||
---
|
||||
trigger: codespaces_issue
|
||||
title: Codespaces Issue
|
||||
description: Report downstream
|
||||
|
||||
steps:
|
||||
- type: fill
|
||||
template: |-
|
||||
This looks like an issue with the Codespaces service which we don't track in this repository. You can report this to the Codespaces team at https://github.com/orgs/community/discussions/categories/codespaces
|
||||
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
insidersLabel: "insiders",
|
||||
insidersColor: "006b75",
|
||||
action: "add",
|
||||
perform: true,
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
insidersLabel: "insiders",
|
||||
insidersColor: "006b75",
|
||||
action: "remove",
|
||||
perform: true,
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<!-- Thank you for submitting a Pull Request. Please:
|
||||
* Read our Pull Request guidelines:
|
||||
https://github.com/microsoft/vscode/wiki/How-to-Contribute#pull-requests
|
||||
* Associate an issue with the Pull Request.
|
||||
* Ensure that the code is up-to-date with the `main` branch.
|
||||
* Include a description of the proposed changes and how to test them.
|
||||
-->
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
perform: true,
|
||||
whenCreatedByTeam: false,
|
||||
comment: "(Experimental duplicate detection)\nThanks for submitting this issue. Please also check if it is already covered by an existing one, like:\n${potentialDuplicates}",
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
name: Basic checks
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
# on:
|
||||
# push:
|
||||
# branches:
|
||||
# - main
|
||||
# pull_request:
|
||||
# branches:
|
||||
# - main
|
||||
|
||||
jobs:
|
||||
main:
|
||||
if: github.ref != 'refs/heads/main'
|
||||
name: Compilation, Unit and Integration Tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# TODO: rename azure-pipelines/linux/xvfb.init to github-actions
|
||||
- name: Setup Build Environment
|
||||
run: |
|
||||
sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb
|
||||
sudo chmod +x /etc/init.d/xvfb
|
||||
sudo update-rc.d xvfb defaults
|
||||
sudo service xvfb start
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Compute node modules cache key
|
||||
id: nodeModulesCacheKey
|
||||
run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT
|
||||
- name: Cache node modules
|
||||
id: cacheNodeModules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
- name: Get yarn cache directory path
|
||||
id: yarnCacheDirPath
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
- name: Cache yarn directory
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.yarnCacheDirPath.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
restore-keys: ${{ runner.os }}-yarnCacheDir-
|
||||
- name: Execute yarn
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
|
||||
run: yarn --frozen-lockfile --network-timeout 180000
|
||||
|
||||
- name: Compile and Download
|
||||
run: yarn npm-run-all --max-old-space-size=4095 -lp compile "electron x64"
|
||||
|
||||
- name: Run Unit Tests
|
||||
id: electron-unit-tests
|
||||
run: DISPLAY=:10 ./scripts/test.sh
|
||||
|
||||
- name: Run Integration Tests (Electron)
|
||||
id: electron-integration-tests
|
||||
run: DISPLAY=:10 ./scripts/test-integration.sh
|
||||
|
||||
hygiene:
|
||||
if: github.ref != 'refs/heads/main'
|
||||
name: Hygiene and Layering
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Compute node modules cache key
|
||||
id: nodeModulesCacheKey
|
||||
run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT
|
||||
- name: Cache node modules
|
||||
id: cacheNodeModules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
- name: Get yarn cache directory path
|
||||
id: yarnCacheDirPath
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
- name: Cache yarn directory
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.yarnCacheDirPath.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
restore-keys: ${{ runner.os }}-yarnCacheDir-
|
||||
- name: Execute yarn
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
|
||||
run: yarn --frozen-lockfile --network-timeout 180000
|
||||
|
||||
- name: Run Hygiene Checks
|
||||
run: yarn gulp hygiene
|
||||
|
||||
- name: Run Valid Layers Checks
|
||||
run: yarn valid-layers-check
|
||||
|
||||
- name: Compile /build/
|
||||
run: yarn --cwd build compile
|
||||
|
||||
- name: Check clean git state
|
||||
run: ./.github/workflows/check-clean-git-state.sh
|
||||
|
||||
- name: Run eslint
|
||||
run: yarn eslint
|
||||
|
||||
- name: Run vscode-dts Compile Checks
|
||||
run: yarn vscode-dts-compile-check
|
||||
|
||||
- name: Run Trusted Types Checks
|
||||
run: yarn tsec-compile-check
|
||||
|
||||
warm-cache:
|
||||
name: Warm up node modules cache
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Compute node modules cache key
|
||||
id: nodeModulesCacheKey
|
||||
run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT
|
||||
- name: Cache node modules
|
||||
id: cacheNodeModules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
- name: Get yarn cache directory path
|
||||
id: yarnCacheDirPath
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
- name: Cache yarn directory
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.yarnCacheDirPath.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
restore-keys: ${{ runner.os }}-yarnCacheDir-
|
||||
- name: Execute yarn
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
|
||||
run: yarn --frozen-lockfile --network-timeout 180000
|
||||
@@ -0,0 +1,6 @@
|
||||
R=`git status --porcelain | wc -l`
|
||||
if [ "$R" -ne "0" ]; then
|
||||
echo "The git repo is not clean after compiling the /build/ folder. Did you forget to commit .js output for .ts files?";
|
||||
git status --porcelain
|
||||
exit 1;
|
||||
fi
|
||||
@@ -0,0 +1,312 @@
|
||||
name: CI
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
# on:
|
||||
# push:
|
||||
# branches:
|
||||
# - main
|
||||
# - release/*
|
||||
# pull_request:
|
||||
# branches:
|
||||
# - main
|
||||
# - release/*
|
||||
|
||||
jobs:
|
||||
windows:
|
||||
name: Windows
|
||||
runs-on: windows-2022
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CHILD_CONCURRENCY: "1"
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "2.x"
|
||||
|
||||
- name: Compute node modules cache key
|
||||
id: nodeModulesCacheKey
|
||||
run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT
|
||||
- name: Cache node_modules archive
|
||||
id: cacheNodeModules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ".build/node_modules_cache"
|
||||
key: "${{ runner.os }}-cacheNodeModulesArchive-${{ steps.nodeModulesCacheKey.outputs.value }}"
|
||||
- name: Extract node_modules archive
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit == 'true' }}
|
||||
run: 7z.exe x .build/node_modules_cache/cache.7z -aos
|
||||
- name: Get yarn cache directory path
|
||||
id: yarnCacheDirPath
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
- name: Cache yarn directory
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.yarnCacheDirPath.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
restore-keys: ${{ runner.os }}-yarnCacheDir-
|
||||
- name: Execute yarn
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
|
||||
run: yarn --frozen-lockfile --network-timeout 180000
|
||||
- name: Create node_modules archive
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
run: |
|
||||
mkdir -Force .build
|
||||
node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt
|
||||
mkdir -Force .build/node_modules_cache
|
||||
7z.exe a .build/node_modules_cache/cache.7z -mx3 `@.build/node_modules_list.txt
|
||||
|
||||
- name: Compile and Download
|
||||
run: yarn npm-run-all --max-old-space-size=4095 -lp compile "electron x64" playwright-install download-builtin-extensions
|
||||
|
||||
- name: Compile Integration Tests
|
||||
run: yarn --cwd test/integration/browser compile
|
||||
|
||||
- name: Run Unit Tests (Electron)
|
||||
run: .\scripts\test.bat
|
||||
|
||||
- name: Run Unit Tests (node.js)
|
||||
run: yarn test-node
|
||||
|
||||
- name: Run Unit Tests (Browser, Chromium)
|
||||
run: yarn test-browser-no-install --browser chromium
|
||||
|
||||
- name: Run Integration Tests (Electron)
|
||||
run: .\scripts\test-integration.bat
|
||||
|
||||
- name: Run Integration Tests (Browser, Firefox)
|
||||
timeout-minutes: 20
|
||||
run: .\scripts\test-web-integration.bat --browser firefox
|
||||
|
||||
- name: Run Integration Tests (Remote)
|
||||
timeout-minutes: 20
|
||||
run: .\scripts\test-remote-integration.bat
|
||||
|
||||
linux:
|
||||
name: Linux
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# TODO: rename azure-pipelines/linux/xvfb.init to github-actions
|
||||
- name: Setup Build Environment
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxkbfile-dev pkg-config libkrb5-dev libxss1 dbus xvfb libgtk-3-0 libgbm1
|
||||
sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb
|
||||
sudo chmod +x /etc/init.d/xvfb
|
||||
sudo update-rc.d xvfb defaults
|
||||
sudo service xvfb start
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Compute node modules cache key
|
||||
id: nodeModulesCacheKey
|
||||
run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT
|
||||
- name: Cache node modules
|
||||
id: cacheNodeModules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
- name: Get yarn cache directory path
|
||||
id: yarnCacheDirPath
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
- name: Cache yarn directory
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.yarnCacheDirPath.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
restore-keys: ${{ runner.os }}-yarnCacheDir-
|
||||
- name: Execute yarn
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
|
||||
run: yarn --frozen-lockfile --network-timeout 180000
|
||||
|
||||
- name: Compile and Download
|
||||
run: yarn npm-run-all --max-old-space-size=4095 -lp compile "electron x64" playwright-install download-builtin-extensions
|
||||
|
||||
- name: Compile Integration Tests
|
||||
run: yarn --cwd test/integration/browser compile
|
||||
|
||||
- name: Run Unit Tests (Electron)
|
||||
id: electron-unit-tests
|
||||
run: DISPLAY=:10 ./scripts/test.sh
|
||||
|
||||
- name: Run Unit Tests (node.js)
|
||||
id: nodejs-unit-tests
|
||||
run: yarn test-node
|
||||
|
||||
- name: Run Unit Tests (Browser, Chromium)
|
||||
id: browser-unit-tests
|
||||
run: DISPLAY=:10 yarn test-browser-no-install --browser chromium
|
||||
|
||||
- name: Run Integration Tests (Electron)
|
||||
id: electron-integration-tests
|
||||
run: DISPLAY=:10 ./scripts/test-integration.sh
|
||||
|
||||
- name: Run Integration Tests (Browser, Chromium)
|
||||
id: browser-integration-tests
|
||||
run: DISPLAY=:10 ./scripts/test-web-integration.sh --browser chromium
|
||||
|
||||
- name: Run Integration Tests (Remote)
|
||||
id: electron-remote-integration-tests
|
||||
timeout-minutes: 15
|
||||
run: DISPLAY=:10 ./scripts/test-remote-integration.sh
|
||||
|
||||
darwin:
|
||||
name: macOS
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Compute node modules cache key
|
||||
id: nodeModulesCacheKey
|
||||
run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT
|
||||
- name: Cache node modules
|
||||
id: cacheNodeModules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-cacheNodeModulesMacOS-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
- name: Get yarn cache directory path
|
||||
id: yarnCacheDirPath
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
- name: Cache yarn directory
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.yarnCacheDirPath.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
restore-keys: ${{ runner.os }}-yarnCacheDir-
|
||||
- name: Execute yarn
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
|
||||
run: yarn --frozen-lockfile --network-timeout 180000
|
||||
|
||||
- name: Compile and Download
|
||||
run: yarn npm-run-all --max-old-space-size=4095 -lp compile "electron x64" playwright-install download-builtin-extensions
|
||||
|
||||
- name: Compile Integration Tests
|
||||
run: yarn --cwd test/integration/browser compile
|
||||
|
||||
# This is required for SecretStorage unittests
|
||||
- name: Create temporary keychain
|
||||
run: |
|
||||
security create-keychain -p pwd $RUNNER_TEMP/buildagent.keychain
|
||||
security default-keychain -s $RUNNER_TEMP/buildagent.keychain
|
||||
security unlock-keychain -p pwd $RUNNER_TEMP/buildagent.keychain
|
||||
|
||||
- name: Run Unit Tests (Electron)
|
||||
run: DISPLAY=:10 ./scripts/test.sh
|
||||
|
||||
- name: Run Unit Tests (node.js)
|
||||
run: yarn test-node
|
||||
|
||||
- name: Run Unit Tests (Browser, Chromium)
|
||||
run: DISPLAY=:10 yarn test-browser-no-install --browser chromium
|
||||
|
||||
- name: Run Integration Tests (Electron)
|
||||
run: DISPLAY=:10 ./scripts/test-integration.sh
|
||||
|
||||
- name: Run Integration Tests (Browser, Webkit)
|
||||
run: DISPLAY=:10 ./scripts/test-web-integration.sh --browser webkit
|
||||
|
||||
- name: Run Integration Tests (Remote)
|
||||
timeout-minutes: 15
|
||||
run: DISPLAY=:10 ./scripts/test-remote-integration.sh
|
||||
|
||||
hygiene:
|
||||
name: Hygiene and Layering
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Compute node modules cache key
|
||||
id: nodeModulesCacheKey
|
||||
run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT
|
||||
- name: Cache node modules
|
||||
id: cacheNodeModules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
- name: Get yarn cache directory path
|
||||
id: yarnCacheDirPath
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
- name: Cache yarn directory
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.yarnCacheDirPath.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
restore-keys: ${{ runner.os }}-yarnCacheDir-
|
||||
- name: Execute yarn
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
|
||||
run: yarn --frozen-lockfile --network-timeout 180000
|
||||
|
||||
- name: Download Playwright
|
||||
run: yarn playwright-install
|
||||
|
||||
- name: Run Hygiene Checks
|
||||
run: yarn gulp hygiene
|
||||
|
||||
- name: Run Valid Layers Checks
|
||||
run: yarn valid-layers-check
|
||||
|
||||
- name: Compile /build/
|
||||
run: yarn --cwd build compile
|
||||
|
||||
- name: Check clean git state
|
||||
run: ./.github/workflows/check-clean-git-state.sh
|
||||
|
||||
- name: Run eslint
|
||||
run: yarn eslint
|
||||
|
||||
- name: Run vscode-dts Compile Checks
|
||||
run: yarn vscode-dts-compile-check
|
||||
|
||||
- name: Run Trusted Types Checks
|
||||
run: yarn tsec-compile-check
|
||||
@@ -0,0 +1,98 @@
|
||||
name: Monaco Editor checks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release/*
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- release/*
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Monaco Editor checks
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Compute node modules cache key
|
||||
id: nodeModulesCacheKey
|
||||
run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT
|
||||
- name: Cache node modules
|
||||
id: cacheNodeModules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-cacheNodeModules20-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
restore-keys: ${{ runner.os }}-cacheNodeModules20-
|
||||
- name: Get yarn cache directory path
|
||||
id: yarnCacheDirPath
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
- name: Cache yarn directory
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.yarnCacheDirPath.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
|
||||
restore-keys: ${{ runner.os }}-yarnCacheDir-
|
||||
- name: Install libkrb5-dev
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y libkrb5-dev
|
||||
- name: Execute yarn
|
||||
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
|
||||
run: |
|
||||
npm i -g node-gyp@9.4.0
|
||||
yarn --frozen-lockfile --network-timeout 180000
|
||||
|
||||
- name: Download Playwright
|
||||
run: yarn playwright-install
|
||||
|
||||
- name: Run Monaco Editor Checks
|
||||
run: yarn monaco-compile-check
|
||||
|
||||
- name: Editor Distro & ESM
|
||||
run: yarn gulp editor-esm
|
||||
|
||||
- name: Editor ESM sources check
|
||||
working-directory: ./test/monaco
|
||||
run: yarn run esm-check
|
||||
|
||||
- name: Typings validation prep
|
||||
run: |
|
||||
mkdir typings-test
|
||||
|
||||
- name: Typings validation
|
||||
working-directory: ./typings-test
|
||||
run: |
|
||||
yarn init -yp
|
||||
../node_modules/.bin/tsc --init
|
||||
echo "import '../out-monaco-editor-core';" > a.ts
|
||||
../node_modules/.bin/tsc --noEmit
|
||||
|
||||
- name: Package Editor with Webpack
|
||||
working-directory: ./test/monaco
|
||||
run: yarn run bundle-webpack
|
||||
|
||||
- name: Compile Editor Tests
|
||||
working-directory: ./test/monaco
|
||||
run: yarn run compile
|
||||
|
||||
- name: Run Editor Tests
|
||||
timeout-minutes: 5
|
||||
working-directory: ./test/monaco
|
||||
run: yarn test
|
||||
@@ -0,0 +1,31 @@
|
||||
name: Prevent yarn.lock changes in PRs
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Prevent yarn.lock changes in PRs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: octokit/request-action@v2.x
|
||||
id: get_permissions
|
||||
with:
|
||||
route: GET /repos/microsoft/vscode/collaborators/{username}/permission
|
||||
username: ${{ github.event.pull_request.user.login }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set control output variable
|
||||
id: control
|
||||
run: |
|
||||
echo "user: ${{ github.event.pull_request.user.login }}"
|
||||
echo "role: ${{ fromJson(steps.get_permissions.outputs.data).permission }}"
|
||||
echo "is dependabot: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }}"
|
||||
echo "should_run: ${{ !contains(fromJson('["admin", "maintain", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) }}"
|
||||
echo "should_run=${{ !contains(fromJson('["admin", "maintain", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) && github.event.pull_request.user.login != 'dependabot[bot]' }}" >> $GITHUB_OUTPUT
|
||||
- name: Get file changes
|
||||
uses: trilom/file-changes-action@ce38c8ce2459ca3c303415eec8cb0409857b4272
|
||||
if: ${{ steps.control.outputs.should_run == 'true' }}
|
||||
- name: Check for lockfile changes
|
||||
if: ${{ steps.control.outputs.should_run == 'true' }}
|
||||
run: |
|
||||
cat $HOME/files.json | jq -e 'any(test("yarn\\.lock$|Cargo\\.lock$")) | not' \
|
||||
|| (echo "Changes to yarn.lock/Cargo.lock files aren't allowed in PRs." && exit 1)
|
||||
@@ -0,0 +1,39 @@
|
||||
name: "Rich Navigation Indexing"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
richnav:
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/cache@v3
|
||||
id: caching-stage
|
||||
name: Cache VS Code dependencies
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-dependencies-${{ hashfiles('yarn.lock') }}
|
||||
restore-keys: ${{ runner.os }}-dependencies-
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.caching-stage.outputs.cache-hit != 'true'
|
||||
run: yarn --frozen-lockfile
|
||||
env:
|
||||
CHILD_CONCURRENCY: 1
|
||||
|
||||
- uses: microsoft/RichCodeNavIndexer@v0.1
|
||||
with:
|
||||
languages: typescript
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
configFiles: .lsifrc.json
|
||||
continue-on-error: true
|
||||
@@ -0,0 +1,19 @@
|
||||
name: 'Telemetry'
|
||||
on:
|
||||
pull_request:
|
||||
jobs:
|
||||
check-metdata:
|
||||
name: 'Check metadata'
|
||||
runs-on: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- uses: 'actions/checkout@v4'
|
||||
|
||||
- uses: 'actions/setup-node@v4'
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
|
||||
- name: 'Run vscode-telemetry-extractor'
|
||||
run: 'npx --package=@vscode/telemetry-extractor --yes vscode-telemetry-extractor -s .'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
.DS_Store
|
||||
.cache
|
||||
npm-debug.log
|
||||
Thumbs.db
|
||||
node_modules/
|
||||
.build/
|
||||
.vscode/extensions/**/out/
|
||||
extensions/**/dist/
|
||||
/out*/
|
||||
/extensions/**/out/
|
||||
build/node_modules
|
||||
coverage/
|
||||
test_data/
|
||||
test-results/
|
||||
yarn-error.log
|
||||
vscode.lsif
|
||||
vscode.db
|
||||
/.profile-oss
|
||||
/cli/target
|
||||
/cli/openssl
|
||||
product.overrides.json
|
||||
*.snap.actual
|
||||
.vscode-test
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"project": "src/tsconfig.json",
|
||||
"source": "./package.json",
|
||||
"package": "package.json",
|
||||
"out": "vscode.lsif"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
Daniel Imms <daimms@microsoft.com> Daniel Imms <tyriar@tyriar.com>
|
||||
Raymond Zhao <raymondzhao@microsoft.com>
|
||||
Tyler Leonhardt <tyleonha@microsoft.com> Tyler Leonhardt <me@tylerleonhardt.com>
|
||||
João Moreno <joao.moreno@microsoft.com> João Moreno <mail@joaomoreno.com>
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"maxReviewers": 2,
|
||||
"requiredOrgs": ["Microsoft"],
|
||||
"skipAlreadyAssignedPR": true,
|
||||
"skipAlreadyMentionedPR": true,
|
||||
"skipCollaboratorPR": true
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
|
||||
const path = require('path');
|
||||
const { defineConfig } = require('@vscode/test-cli');
|
||||
const os = require('os');
|
||||
|
||||
/**
|
||||
* A list of extension folders who have opted into tests, or configuration objects.
|
||||
* Edit me to add more!
|
||||
*
|
||||
* @type {Array<string | (Partial<import("@vscode/test-cli").TestConfiguration> & { label: string })>}
|
||||
*/
|
||||
const extensions = [
|
||||
{
|
||||
label: 'markdown-language-features',
|
||||
workspaceFolder: `extensions/markdown-language-features/test-workspace`,
|
||||
mocha: { timeout: 60_000 }
|
||||
},
|
||||
{
|
||||
label: 'ipynb',
|
||||
workspaceFolder: path.join(os.tmpdir(), `ipynb-${Math.floor(Math.random() * 100000)}`),
|
||||
mocha: { timeout: 60_000 }
|
||||
},
|
||||
{
|
||||
label: 'notebook-renderers',
|
||||
workspaceFolder: path.join(os.tmpdir(), `nbout-${Math.floor(Math.random() * 100000)}`),
|
||||
mocha: { timeout: 60_000 }
|
||||
},
|
||||
{
|
||||
label: 'vscode-colorize-tests',
|
||||
workspaceFolder: `extensions/vscode-colorize-tests/test`,
|
||||
mocha: { timeout: 60_000 }
|
||||
},
|
||||
{
|
||||
label: 'configuration-editing',
|
||||
workspaceFolder: path.join(os.tmpdir(), `confeditout-${Math.floor(Math.random() * 100000)}`),
|
||||
mocha: { timeout: 60_000 }
|
||||
},
|
||||
{
|
||||
label: 'github-authentication',
|
||||
workspaceFolder: path.join(os.tmpdir(), `msft-auth-${Math.floor(Math.random() * 100000)}`),
|
||||
mocha: { timeout: 60_000 }
|
||||
},
|
||||
{
|
||||
label: 'microsoft-authentication',
|
||||
mocha: { timeout: 60_000 }
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
const defaultLaunchArgs = process.env.API_TESTS_EXTRA_ARGS?.split(' ') || [
|
||||
'--disable-telemetry', '--skip-welcome', '--skip-release-notes', `--crash-reporter-directory=${__dirname}/.build/crashes`, `--logsPath=${__dirname}/.build/logs/integration-tests`, '--no-cached-data', '--disable-updates', '--use-inmemory-secretstorage', '--disable-extensions', '--disable-workspace-trust'
|
||||
];
|
||||
|
||||
module.exports = defineConfig(extensions.map(extension => {
|
||||
/** @type {import('@vscode/test-cli').TestConfiguration} */
|
||||
const config = typeof extension === 'object'
|
||||
? { files: `extensions/${extension.label}/out/**/*.test.js`, ...extension }
|
||||
: { files: `extensions/${extension}/out/**/*.test.js`, label: extension };
|
||||
|
||||
config.mocha ??= {};
|
||||
if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
|
||||
let suite = '';
|
||||
if (process.env.VSCODE_BROWSER) {
|
||||
suite = `${process.env.VSCODE_BROWSER} Browser Integration ${config.label} tests`;
|
||||
} else if (process.env.REMOTE_VSCODE) {
|
||||
suite = `Remote Integration ${config.label} tests`;
|
||||
} else {
|
||||
suite = `Integration ${config.label} tests`;
|
||||
}
|
||||
|
||||
config.mocha.reporter = 'mocha-multi-reporters';
|
||||
config.mocha.reporterOptions = {
|
||||
reporterEnabled: 'spec, mocha-junit-reporter',
|
||||
mochaJunitReporterReporterOptions: {
|
||||
testsuitesTitle: `${suite} ${process.platform}`,
|
||||
mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (!config.platform || config.platform === 'desktop') {
|
||||
config.launchArgs = defaultLaunchArgs;
|
||||
config.useInstallation = {
|
||||
fromPath: process.env.INTEGRATION_TEST_ELECTRON_PATH || `${__dirname}/scripts/code.${process.platform === 'win32' ? 'bat' : 'sh'}`,
|
||||
};
|
||||
config.env = {
|
||||
...config.env,
|
||||
VSCODE_SKIP_PRELAUNCH: '1',
|
||||
};
|
||||
} else {
|
||||
// web configs not supported, yet
|
||||
}
|
||||
|
||||
return config;
|
||||
}));
|
||||
Vendored
+86
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"prependLicenseText"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the dependency"
|
||||
},
|
||||
"fullLicenseText": {
|
||||
"type": "array",
|
||||
"description": "The complete license text of the dependency",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"prependLicenseText": {
|
||||
"type": "array",
|
||||
"description": "A piece of text to prepend to the auto-detected license text of the dependency",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"fullLicenseText"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the dependency"
|
||||
},
|
||||
"fullLicenseText": {
|
||||
"type": "array",
|
||||
"description": "The complete license text of the dependency",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"prependLicenseText": {
|
||||
"type": "array",
|
||||
"description": "A piece of text to prepend to the auto-detected license text of the dependency",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"fullLicenseTextUri"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the dependency"
|
||||
},
|
||||
"fullLicenseTextUri": {
|
||||
"type": "string",
|
||||
"description": "The URI to the license text of this repository",
|
||||
"format": "uri"
|
||||
},
|
||||
"prependLicenseText": {
|
||||
"type": "array",
|
||||
"description": "A piece of text to prepend to the auto-detected license text of the dependency",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"editorconfig.editorconfig",
|
||||
"github.vscode-pull-request-github",
|
||||
"ms-vscode.vscode-github-issue-notebooks",
|
||||
"ms-vscode.extension-test-runner",
|
||||
"jrieken.vscode-pr-pinger"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"args": ["--extensionDevelopmentPath=${workspaceFolder}", "--enable-proposed-api=ms-vscode.vscode-selfhost-test-provider"],
|
||||
"name": "Launch Extension",
|
||||
"outFiles": ["${workspaceFolder}/out/**/*.js"],
|
||||
"request": "launch",
|
||||
"type": "extensionHost"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "vscode.typescript-language-features",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "always"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"name": "vscode-selfhost-test-provider",
|
||||
"displayName": "VS Code Selfhost Test Provider",
|
||||
"description": "Test provider for the VS Code project",
|
||||
"enabledApiProposals": [
|
||||
"testObserver",
|
||||
"testRelatedCode",
|
||||
"attributableCoverage"
|
||||
],
|
||||
"engines": {
|
||||
"vscode": "^1.88.0"
|
||||
},
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "selfhost-test-provider.updateSnapshot",
|
||||
"title": "Update Snapshot",
|
||||
"category": "Testing",
|
||||
"icon": "$(merge)"
|
||||
},
|
||||
{
|
||||
"command": "selfhost-test-provider.openFailureLog",
|
||||
"title": "Open Selfhost Failure Logs",
|
||||
"category": "Testing",
|
||||
"icon": "$(merge)"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "selfhost-test-provider.updateSnapshot",
|
||||
"when": "false"
|
||||
}
|
||||
],
|
||||
"testing/message/context": [
|
||||
{
|
||||
"command": "selfhost-test-provider.updateSnapshot",
|
||||
"group": "inline@1",
|
||||
"when": "testMessage == isSelfhostSnapshotMessage && !testResultOutdated"
|
||||
}
|
||||
],
|
||||
"testing/message/content": [
|
||||
{
|
||||
"command": "selfhost-test-provider.updateSnapshot",
|
||||
"when": "testMessage == isSelfhostSnapshotMessage && !testResultOutdated"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"icon": "icon.png",
|
||||
"version": "0.4.0",
|
||||
"publisher": "ms-vscode",
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"workspaceContains:src/vs/loader.js"
|
||||
],
|
||||
"workspaceTrust": {
|
||||
"request": "onDemand",
|
||||
"description": "Trust is required to execute tests in the workspace."
|
||||
},
|
||||
"main": "./out/extension.js",
|
||||
"prettier": {
|
||||
"printWidth": 100,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"arrowParens": "avoid"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/microsoft/vscode.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"compile": "gulp compile-extension:vscode-selfhost-test-provider",
|
||||
"watch": "gulp watch-extension:vscode-selfhost-test-provider",
|
||||
"test": "npx mocha --ui tdd 'out/*.test.js'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^10.0.6",
|
||||
"@types/node": "20.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"ansi-styles": "^5.2.0",
|
||||
"cockatiel": "^3.1.3",
|
||||
"istanbul-to-vscode": "^2.0.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IstanbulCoverageContext } from 'istanbul-to-vscode';
|
||||
import * as vscode from 'vscode';
|
||||
import { SearchStrategy, SourceLocationMapper, SourceMapStore } from './testOutputScanner';
|
||||
import { IScriptCoverage, OffsetToPosition, RangeCoverageTracker } from './v8CoverageWrangling';
|
||||
|
||||
export const istanbulCoverageContext = new IstanbulCoverageContext();
|
||||
|
||||
/**
|
||||
* Tracks coverage in per-script coverage mode. There are two modes of coverage
|
||||
* in this extension: generic istanbul reports, and reports from the runtime
|
||||
* sent before and after each test case executes. This handles the latter.
|
||||
*/
|
||||
export class PerTestCoverageTracker {
|
||||
private readonly scripts = new Map</* script ID */ string, Script>();
|
||||
|
||||
constructor(private readonly maps: SourceMapStore) { }
|
||||
|
||||
public add(coverage: IScriptCoverage, test?: vscode.TestItem) {
|
||||
const script = this.scripts.get(coverage.scriptId);
|
||||
if (script) {
|
||||
return script.add(coverage, test);
|
||||
}
|
||||
// ignore internals and node_modules
|
||||
if (!coverage.url.startsWith('file://') || coverage.url.includes('node_modules')) {
|
||||
return;
|
||||
}
|
||||
if (!coverage.source) {
|
||||
throw new Error('expected to have source the first time a script is seen');
|
||||
}
|
||||
|
||||
const src = new Script(vscode.Uri.parse(coverage.url), coverage.source, this.maps);
|
||||
this.scripts.set(coverage.scriptId, src);
|
||||
src.add(coverage, test);
|
||||
}
|
||||
|
||||
public async report(run: vscode.TestRun) {
|
||||
await Promise.all(Array.from(this.scripts.values()).map(s => s.report(run)));
|
||||
}
|
||||
}
|
||||
|
||||
class Script {
|
||||
private converter: OffsetToPosition;
|
||||
|
||||
/** Tracking the overall coverage for the file */
|
||||
private overall = new ScriptCoverageTracker();
|
||||
/** Range tracking per-test item */
|
||||
private readonly perItem = new Map<vscode.TestItem, ScriptCoverageTracker>();
|
||||
|
||||
constructor(
|
||||
public readonly uri: vscode.Uri,
|
||||
source: string,
|
||||
private readonly maps: SourceMapStore
|
||||
) {
|
||||
this.converter = new OffsetToPosition(source);
|
||||
}
|
||||
|
||||
public add(coverage: IScriptCoverage, test?: vscode.TestItem) {
|
||||
this.overall.add(coverage);
|
||||
if (test) {
|
||||
const p = new ScriptCoverageTracker();
|
||||
p.add(coverage);
|
||||
this.perItem.set(test, p);
|
||||
}
|
||||
}
|
||||
|
||||
public async report(run: vscode.TestRun) {
|
||||
const mapper = await this.maps.getSourceLocationMapper(this.uri.toString());
|
||||
const originalUri = (await this.maps.getSourceFile(this.uri.toString())) || this.uri;
|
||||
run.addCoverage(this.overall.report(originalUri, this.converter, mapper, this.perItem));
|
||||
}
|
||||
}
|
||||
|
||||
class ScriptCoverageTracker {
|
||||
private coverage = new RangeCoverageTracker();
|
||||
|
||||
public add(coverage: IScriptCoverage) {
|
||||
for (const range of RangeCoverageTracker.initializeBlocks(coverage.functions)) {
|
||||
this.coverage.setCovered(range.start, range.end, range.covered);
|
||||
}
|
||||
}
|
||||
|
||||
public *toDetails(
|
||||
uri: vscode.Uri,
|
||||
convert: OffsetToPosition,
|
||||
mapper: SourceLocationMapper | undefined,
|
||||
) {
|
||||
for (const range of this.coverage) {
|
||||
if (range.start === range.end) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const startCov = convert.toLineColumn(range.start);
|
||||
let start = new vscode.Position(startCov.line, startCov.column);
|
||||
|
||||
const endCov = convert.toLineColumn(range.end);
|
||||
let end = new vscode.Position(endCov.line, endCov.column);
|
||||
if (mapper) {
|
||||
const startMap = mapper(start.line, start.character, SearchStrategy.FirstAfter);
|
||||
const endMap = startMap && mapper(end.line, end.character, SearchStrategy.FirstBefore);
|
||||
if (!endMap || uri.toString().toLowerCase() !== endMap.uri.toString().toLowerCase()) {
|
||||
continue;
|
||||
}
|
||||
start = startMap.range.start;
|
||||
end = endMap.range.end;
|
||||
}
|
||||
|
||||
for (let i = start.line; i <= end.line; i++) {
|
||||
yield new vscode.StatementCoverage(
|
||||
range.covered,
|
||||
new vscode.Range(
|
||||
new vscode.Position(i, i === start.line ? start.character : 0),
|
||||
new vscode.Position(i, i === end.line ? end.character : Number.MAX_SAFE_INTEGER)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the script's coverage for the test run.
|
||||
*
|
||||
* If a source location mapper is given, it assumes the `uri` is the mapped
|
||||
* URI, and that any unmapped locations/outside the URI should be ignored.
|
||||
*/
|
||||
public report(
|
||||
uri: vscode.Uri,
|
||||
convert: OffsetToPosition,
|
||||
mapper: SourceLocationMapper | undefined,
|
||||
items: Map<vscode.TestItem, ScriptCoverageTracker>,
|
||||
): V8CoverageFile {
|
||||
const file = new V8CoverageFile(uri, items, convert, mapper);
|
||||
for (const detail of this.toDetails(uri, convert, mapper)) {
|
||||
file.add(detail);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
export class V8CoverageFile extends vscode.FileCoverage2 {
|
||||
public details: vscode.StatementCoverage[] = [];
|
||||
|
||||
constructor(
|
||||
uri: vscode.Uri,
|
||||
private readonly perTest: Map<vscode.TestItem, ScriptCoverageTracker>,
|
||||
private readonly convert: OffsetToPosition,
|
||||
private readonly mapper: SourceLocationMapper | undefined,
|
||||
) {
|
||||
super(uri, { covered: 0, total: 0 }, undefined, undefined, [...perTest.keys()]);
|
||||
}
|
||||
|
||||
public add(detail: vscode.StatementCoverage) {
|
||||
this.details.push(detail);
|
||||
this.statementCoverage.total++;
|
||||
if (detail.executed) {
|
||||
this.statementCoverage.covered++;
|
||||
}
|
||||
}
|
||||
|
||||
public testDetails(test: vscode.TestItem): vscode.FileCoverageDetail[] {
|
||||
const t = this.perTest.get(test);
|
||||
return t ? [...t.toDetails(this.uri, this.convert, this.mapper)] : [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Debounces the function call for an interval.
|
||||
*/
|
||||
export function debounce(duration: number, fn: () => void): (() => void) & { clear: () => void } {
|
||||
let timeout: NodeJS.Timeout | void;
|
||||
const debounced = () => {
|
||||
if (timeout !== undefined) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
timeout = undefined;
|
||||
fn();
|
||||
}, duration);
|
||||
};
|
||||
|
||||
debounced.clear = () => {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
return debounced;
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { randomBytes } from 'crypto';
|
||||
import { tmpdir } from 'os';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { V8CoverageFile } from './coverageProvider';
|
||||
import { FailingDeepStrictEqualAssertFixer } from './failingDeepStrictEqualAssertFixer';
|
||||
import { FailureTracker } from './failureTracker';
|
||||
import { registerSnapshotUpdate } from './snapshot';
|
||||
import { scanTestOutput } from './testOutputScanner';
|
||||
import {
|
||||
TestCase,
|
||||
TestFile,
|
||||
clearFileDiagnostics,
|
||||
guessWorkspaceFolder,
|
||||
itemData,
|
||||
} from './testTree';
|
||||
import { BrowserTestRunner, PlatformTestRunner, VSCodeTestRunner } from './vscodeTestRunner';
|
||||
import { ImportGraph } from './importGraph';
|
||||
|
||||
const TEST_FILE_PATTERN = 'src/vs/**/*.{test,integrationTest}.ts';
|
||||
|
||||
const getWorkspaceFolderForTestFile = (uri: vscode.Uri) =>
|
||||
(uri.path.endsWith('.test.ts') || uri.path.endsWith('.integrationTest.ts')) &&
|
||||
uri.path.includes('/src/vs/')
|
||||
? vscode.workspace.getWorkspaceFolder(uri)
|
||||
: undefined;
|
||||
|
||||
const browserArgs: [name: string, arg: string][] = [
|
||||
['Chrome', 'chromium'],
|
||||
['Firefox', 'firefox'],
|
||||
['Webkit', 'webkit'],
|
||||
];
|
||||
|
||||
type FileChangeEvent = { uri: vscode.Uri; removed: boolean };
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
const ctrl = vscode.tests.createTestController('selfhost-test-controller', 'VS Code Tests');
|
||||
const fileChangedEmitter = new vscode.EventEmitter<FileChangeEvent>();
|
||||
|
||||
context.subscriptions.push(vscode.tests.registerTestFollowupProvider({
|
||||
async provideFollowup(_result, test, taskIndex, messageIndex, _token) {
|
||||
return [{
|
||||
title: '$(sparkle) Fix with Copilot',
|
||||
command: 'github.copilot.tests.fixTestFailure',
|
||||
arguments: [{ source: 'peekFollowup', test, message: test.taskStates[taskIndex].messages[messageIndex] }]
|
||||
}];
|
||||
},
|
||||
}));
|
||||
|
||||
let initialWatchPromise: Promise<vscode.Disposable> | undefined;
|
||||
const resolveHandler = async (test?: vscode.TestItem) => {
|
||||
if (!test) {
|
||||
if (!initialWatchPromise) {
|
||||
initialWatchPromise = startWatchingWorkspace(ctrl, fileChangedEmitter);
|
||||
context.subscriptions.push(await initialWatchPromise);
|
||||
} else {
|
||||
await initialWatchPromise;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const data = itemData.get(test);
|
||||
if (data instanceof TestFile) {
|
||||
// No need to watch this, updates will be triggered on file changes
|
||||
// either by the text document or file watcher.
|
||||
await data.updateFromDisk(ctrl, test);
|
||||
}
|
||||
};
|
||||
|
||||
ctrl.resolveHandler = resolveHandler;
|
||||
|
||||
guessWorkspaceFolder().then(folder => {
|
||||
if (!folder) {
|
||||
return;
|
||||
}
|
||||
|
||||
const graph = new ImportGraph(
|
||||
folder.uri, async () => {
|
||||
await resolveHandler();
|
||||
return [...ctrl.items].map(([, item]) => item);
|
||||
}, uri => ctrl.items.get(uri.toString().toLowerCase()));
|
||||
ctrl.relatedCodeProvider = graph;
|
||||
|
||||
context.subscriptions.push(
|
||||
new FailureTracker(context, folder.uri.fsPath),
|
||||
fileChangedEmitter.event(e => graph.didChange(e.uri, e.removed)),
|
||||
);
|
||||
});
|
||||
|
||||
const createRunHandler = (
|
||||
runnerCtor: { new(folder: vscode.WorkspaceFolder): VSCodeTestRunner },
|
||||
kind: vscode.TestRunProfileKind,
|
||||
args: string[] = []
|
||||
) => {
|
||||
const doTestRun = async (
|
||||
req: vscode.TestRunRequest,
|
||||
cancellationToken: vscode.CancellationToken
|
||||
) => {
|
||||
const folder = await guessWorkspaceFolder();
|
||||
if (!folder) {
|
||||
return;
|
||||
}
|
||||
|
||||
const runner = new runnerCtor(folder);
|
||||
const map = await getPendingTestMap(ctrl, req.include ?? gatherTestItems(ctrl.items));
|
||||
const task = ctrl.createTestRun(req);
|
||||
for (const test of map.values()) {
|
||||
task.enqueued(test);
|
||||
}
|
||||
|
||||
let coverageDir: string | undefined;
|
||||
let currentArgs = args;
|
||||
if (kind === vscode.TestRunProfileKind.Coverage) {
|
||||
// todo: browser runs currently don't support per-test coverage
|
||||
if (args.includes('--browser')) {
|
||||
coverageDir = path.join(
|
||||
tmpdir(),
|
||||
`vscode-test-coverage-${randomBytes(8).toString('hex')}`
|
||||
);
|
||||
currentArgs = [
|
||||
...currentArgs,
|
||||
'--coverage',
|
||||
'--coveragePath',
|
||||
coverageDir,
|
||||
'--coverageFormats',
|
||||
'json',
|
||||
];
|
||||
} else {
|
||||
currentArgs = [...currentArgs, '--per-test-coverage'];
|
||||
}
|
||||
}
|
||||
|
||||
return await scanTestOutput(
|
||||
map,
|
||||
task,
|
||||
kind === vscode.TestRunProfileKind.Debug
|
||||
? await runner.debug(task, currentArgs, req.include)
|
||||
: await runner.run(currentArgs, req.include),
|
||||
coverageDir,
|
||||
cancellationToken
|
||||
);
|
||||
};
|
||||
|
||||
return async (req: vscode.TestRunRequest, cancellationToken: vscode.CancellationToken) => {
|
||||
if (!req.continuous) {
|
||||
return doTestRun(req, cancellationToken);
|
||||
}
|
||||
|
||||
const queuedFiles = new Set<string>();
|
||||
let debounced: NodeJS.Timeout | undefined;
|
||||
|
||||
const listener = fileChangedEmitter.event(({ uri, removed }) => {
|
||||
clearTimeout(debounced);
|
||||
|
||||
if (req.include && !req.include.some(i => i.uri?.toString() === uri.toString())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (removed) {
|
||||
queuedFiles.delete(uri.toString());
|
||||
} else {
|
||||
queuedFiles.add(uri.toString());
|
||||
}
|
||||
|
||||
debounced = setTimeout(() => {
|
||||
const include =
|
||||
req.include?.filter(t => t.uri && queuedFiles.has(t.uri?.toString())) ??
|
||||
[...queuedFiles]
|
||||
.map(f => getOrCreateFile(ctrl, vscode.Uri.parse(f)))
|
||||
.filter((f): f is vscode.TestItem => !!f);
|
||||
queuedFiles.clear();
|
||||
|
||||
doTestRun(
|
||||
new vscode.TestRunRequest(include, req.exclude, req.profile, true),
|
||||
cancellationToken
|
||||
);
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
cancellationToken.onCancellationRequested(() => {
|
||||
clearTimeout(debounced);
|
||||
listener.dispose();
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
ctrl.createRunProfile(
|
||||
'Run in Electron',
|
||||
vscode.TestRunProfileKind.Run,
|
||||
createRunHandler(PlatformTestRunner, vscode.TestRunProfileKind.Run),
|
||||
true,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
|
||||
ctrl.createRunProfile(
|
||||
'Debug in Electron',
|
||||
vscode.TestRunProfileKind.Debug,
|
||||
createRunHandler(PlatformTestRunner, vscode.TestRunProfileKind.Debug),
|
||||
true,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
|
||||
const coverage = ctrl.createRunProfile(
|
||||
'Coverage in Electron',
|
||||
vscode.TestRunProfileKind.Coverage,
|
||||
createRunHandler(PlatformTestRunner, vscode.TestRunProfileKind.Coverage),
|
||||
true,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
|
||||
coverage.loadDetailedCoverage = async (_run, coverage) => coverage instanceof V8CoverageFile ? coverage.details : [];
|
||||
coverage.loadDetailedCoverageForTest = async (_run, coverage, test) => coverage instanceof V8CoverageFile ? coverage.testDetails(test) : [];
|
||||
|
||||
for (const [name, arg] of browserArgs) {
|
||||
const cfg = ctrl.createRunProfile(
|
||||
`Run in ${name}`,
|
||||
vscode.TestRunProfileKind.Run,
|
||||
createRunHandler(BrowserTestRunner, vscode.TestRunProfileKind.Run, [' --browser', arg]),
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
|
||||
cfg.configureHandler = () => vscode.window.showInformationMessage(`Configuring ${name}`);
|
||||
|
||||
ctrl.createRunProfile(
|
||||
`Debug in ${name}`,
|
||||
vscode.TestRunProfileKind.Debug,
|
||||
createRunHandler(BrowserTestRunner, vscode.TestRunProfileKind.Debug, [
|
||||
'--browser',
|
||||
arg,
|
||||
'--debug-browser',
|
||||
]),
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
function updateNodeForDocument(e: vscode.TextDocument) {
|
||||
const node = getOrCreateFile(ctrl, e.uri);
|
||||
const data = node && itemData.get(node);
|
||||
if (data instanceof TestFile) {
|
||||
data.updateFromContents(ctrl, e.getText(), node!);
|
||||
}
|
||||
}
|
||||
|
||||
for (const document of vscode.workspace.textDocuments) {
|
||||
updateNodeForDocument(document);
|
||||
}
|
||||
|
||||
context.subscriptions.push(
|
||||
ctrl,
|
||||
fileChangedEmitter.event(({ uri, removed }) => {
|
||||
if (!removed) {
|
||||
const node = getOrCreateFile(ctrl, uri);
|
||||
if (node) {
|
||||
ctrl.invalidateTestResults();
|
||||
}
|
||||
}
|
||||
}),
|
||||
vscode.workspace.onDidOpenTextDocument(updateNodeForDocument),
|
||||
vscode.workspace.onDidChangeTextDocument(e => updateNodeForDocument(e.document)),
|
||||
registerSnapshotUpdate(ctrl),
|
||||
new FailingDeepStrictEqualAssertFixer()
|
||||
);
|
||||
}
|
||||
|
||||
export function deactivate() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
function getOrCreateFile(
|
||||
controller: vscode.TestController,
|
||||
uri: vscode.Uri
|
||||
): vscode.TestItem | undefined {
|
||||
const folder = getWorkspaceFolderForTestFile(uri);
|
||||
if (!folder) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const data = new TestFile(uri, folder);
|
||||
const existing = controller.items.get(data.getId());
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const file = controller.createTestItem(data.getId(), data.getLabel(), uri);
|
||||
controller.items.add(file);
|
||||
file.canResolveChildren = true;
|
||||
itemData.set(file, data);
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
function gatherTestItems(collection: vscode.TestItemCollection) {
|
||||
const items: vscode.TestItem[] = [];
|
||||
collection.forEach(item => items.push(item));
|
||||
return items;
|
||||
}
|
||||
|
||||
async function startWatchingWorkspace(
|
||||
controller: vscode.TestController,
|
||||
fileChangedEmitter: vscode.EventEmitter<FileChangeEvent>
|
||||
) {
|
||||
const workspaceFolder = await guessWorkspaceFolder();
|
||||
if (!workspaceFolder) {
|
||||
return new vscode.Disposable(() => undefined);
|
||||
}
|
||||
|
||||
const pattern = new vscode.RelativePattern(workspaceFolder, TEST_FILE_PATTERN);
|
||||
const watcher = vscode.workspace.createFileSystemWatcher(pattern);
|
||||
|
||||
watcher.onDidCreate(uri => {
|
||||
getOrCreateFile(controller, uri);
|
||||
fileChangedEmitter.fire({ removed: false, uri });
|
||||
});
|
||||
watcher.onDidChange(uri => fileChangedEmitter.fire({ removed: false, uri }));
|
||||
watcher.onDidDelete(uri => {
|
||||
fileChangedEmitter.fire({ removed: true, uri });
|
||||
clearFileDiagnostics(uri);
|
||||
controller.items.delete(uri.toString());
|
||||
});
|
||||
|
||||
for (const file of await vscode.workspace.findFiles(pattern)) {
|
||||
getOrCreateFile(controller, file);
|
||||
}
|
||||
|
||||
return watcher;
|
||||
}
|
||||
|
||||
async function getPendingTestMap(ctrl: vscode.TestController, tests: Iterable<vscode.TestItem>) {
|
||||
const queue = [tests];
|
||||
const titleMap = new Map<string, vscode.TestItem>();
|
||||
while (queue.length) {
|
||||
for (const item of queue.pop()!) {
|
||||
const data = itemData.get(item);
|
||||
if (data instanceof TestFile) {
|
||||
if (!data.hasBeenRead) {
|
||||
await data.updateFromDisk(ctrl, item);
|
||||
}
|
||||
queue.push(gatherTestItems(item.children));
|
||||
} else if (data instanceof TestCase) {
|
||||
titleMap.set(data.fullName, item);
|
||||
} else {
|
||||
queue.push(gatherTestItems(item.children));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return titleMap;
|
||||
}
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
import {
|
||||
commands,
|
||||
Disposable,
|
||||
languages,
|
||||
Position,
|
||||
Range,
|
||||
TestMessage,
|
||||
TestResultSnapshot,
|
||||
TestRunResult,
|
||||
tests,
|
||||
TextDocument,
|
||||
Uri,
|
||||
workspace,
|
||||
WorkspaceEdit,
|
||||
} from 'vscode';
|
||||
import { memoizeLast } from './memoize';
|
||||
import { getTestMessageMetadata } from './metadata';
|
||||
|
||||
const enum Constants {
|
||||
FixCommandId = 'selfhost-test.fix-test',
|
||||
}
|
||||
|
||||
export class FailingDeepStrictEqualAssertFixer {
|
||||
private disposables: Disposable[] = [];
|
||||
|
||||
constructor() {
|
||||
this.disposables.push(
|
||||
commands.registerCommand(Constants.FixCommandId, async (uri: Uri, position: Position) => {
|
||||
const document = await workspace.openTextDocument(uri);
|
||||
|
||||
const failingAssertion = detectFailingDeepStrictEqualAssertion(document, position);
|
||||
if (!failingAssertion) {
|
||||
return;
|
||||
}
|
||||
|
||||
const expectedValueNode = failingAssertion.assertion.expectedValue;
|
||||
if (!expectedValueNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const start = document.positionAt(expectedValueNode.getStart());
|
||||
const end = document.positionAt(expectedValueNode.getEnd());
|
||||
|
||||
const edit = new WorkspaceEdit();
|
||||
edit.replace(uri, new Range(start, end), formatJsonValue(failingAssertion.actualJSONValue));
|
||||
await workspace.applyEdit(edit);
|
||||
})
|
||||
);
|
||||
|
||||
this.disposables.push(
|
||||
languages.registerCodeActionsProvider('typescript', {
|
||||
provideCodeActions: (document, range) => {
|
||||
const failingAssertion = detectFailingDeepStrictEqualAssertion(document, range.start);
|
||||
if (!failingAssertion) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
title: 'Fix Expected Value',
|
||||
command: Constants.FixCommandId,
|
||||
arguments: [document.uri, range.start],
|
||||
},
|
||||
];
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
for (const d of this.disposables) {
|
||||
d.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const identifierLikeRe = /^[$a-z_][a-z0-9_$]*$/i;
|
||||
|
||||
const tsPrinter = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
||||
|
||||
const formatJsonValue = (value: unknown) => {
|
||||
if (typeof value !== 'object') {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
const src = ts.createSourceFile('', `(${JSON.stringify(value)})`, ts.ScriptTarget.ES5, true);
|
||||
const outerExpression = src.statements[0] as ts.ExpressionStatement;
|
||||
const parenExpression = outerExpression.expression as ts.ParenthesizedExpression;
|
||||
|
||||
const unquoted = ts.transform(parenExpression, [
|
||||
context => (node: ts.Node) => {
|
||||
const visitor = (node: ts.Node): ts.Node =>
|
||||
ts.isPropertyAssignment(node) &&
|
||||
ts.isStringLiteralLike(node.name) &&
|
||||
identifierLikeRe.test(node.name.text)
|
||||
? ts.factory.createPropertyAssignment(
|
||||
ts.factory.createIdentifier(node.name.text),
|
||||
ts.visitNode(node.initializer, visitor) as ts.Expression
|
||||
)
|
||||
: ts.isStringLiteralLike(node) && node.text === '[undefined]'
|
||||
? ts.factory.createIdentifier('undefined')
|
||||
: ts.visitEachChild(node, visitor, context);
|
||||
|
||||
return ts.visitNode(node, visitor);
|
||||
},
|
||||
]);
|
||||
|
||||
return tsPrinter.printNode(ts.EmitHint.Expression, unquoted.transformed[0], src);
|
||||
};
|
||||
|
||||
/** Parses the source file, memoizing the last document so cursor moves are efficient */
|
||||
const parseSourceFile = memoizeLast((text: string) =>
|
||||
ts.createSourceFile('', text, ts.ScriptTarget.ES5, true)
|
||||
);
|
||||
|
||||
const assertionFailureMessageRe = /^Expected values to be strictly (deep-)?equal:/;
|
||||
|
||||
/** Gets information about the failing assertion at the poisition, if any. */
|
||||
function detectFailingDeepStrictEqualAssertion(
|
||||
document: TextDocument,
|
||||
position: Position
|
||||
): { assertion: StrictEqualAssertion; actualJSONValue: unknown } | undefined {
|
||||
const sf = parseSourceFile(document.getText());
|
||||
const offset = document.offsetAt(position);
|
||||
const assertion = StrictEqualAssertion.atPosition(sf, offset);
|
||||
if (!assertion) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const startLine = document.positionAt(assertion.offsetStart).line;
|
||||
const messages = getAllTestStatusMessagesAt(document.uri, startLine);
|
||||
const strictDeepEqualMessage = messages.find(m =>
|
||||
assertionFailureMessageRe.test(typeof m.message === 'string' ? m.message : m.message.value)
|
||||
);
|
||||
|
||||
if (!strictDeepEqualMessage) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const metadata = getTestMessageMetadata(strictDeepEqualMessage);
|
||||
if (!metadata) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
assertion: assertion,
|
||||
actualJSONValue: metadata.actualValue,
|
||||
};
|
||||
}
|
||||
|
||||
class StrictEqualAssertion {
|
||||
/**
|
||||
* Extracts the assertion at the current node, if it is one.
|
||||
*/
|
||||
public static fromNode(node: ts.Node): StrictEqualAssertion | undefined {
|
||||
if (!ts.isCallExpression(node)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const expr = node.expression.getText();
|
||||
if (expr !== 'assert.deepStrictEqual' && expr !== 'assert.strictEqual') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return new StrictEqualAssertion(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the equals assertion at the given offset in the file.
|
||||
*/
|
||||
public static atPosition(sf: ts.SourceFile, offset: number): StrictEqualAssertion | undefined {
|
||||
let node = findNodeAt(sf, offset);
|
||||
|
||||
while (node.parent) {
|
||||
const obj = StrictEqualAssertion.fromNode(node);
|
||||
if (obj) {
|
||||
return obj;
|
||||
}
|
||||
node = node.parent;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
constructor(private readonly expression: ts.CallExpression) { }
|
||||
|
||||
/** Gets the expected value */
|
||||
public get expectedValue(): ts.Expression | undefined {
|
||||
return this.expression.arguments[1];
|
||||
}
|
||||
|
||||
/** Gets the position of the assertion expression. */
|
||||
public get offsetStart(): number {
|
||||
return this.expression.getStart();
|
||||
}
|
||||
}
|
||||
|
||||
function findNodeAt(parent: ts.Node, offset: number): ts.Node {
|
||||
for (const child of parent.getChildren()) {
|
||||
if (child.getStart() <= offset && offset <= child.getEnd()) {
|
||||
return findNodeAt(child, offset);
|
||||
}
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
function getAllTestStatusMessagesAt(uri: Uri, lineNumber: number): TestMessage[] {
|
||||
if (tests.testResults.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const run = tests.testResults[0];
|
||||
const snapshots = getTestResultsWithUri(run, uri);
|
||||
const result: TestMessage[] = [];
|
||||
|
||||
for (const snapshot of snapshots) {
|
||||
for (const m of snapshot.taskStates[0].messages) {
|
||||
if (
|
||||
m.location &&
|
||||
m.location.range.start.line <= lineNumber &&
|
||||
lineNumber <= m.location.range.end.line
|
||||
) {
|
||||
result.push(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function getTestResultsWithUri(testRun: TestRunResult, uri: Uri): TestResultSnapshot[] {
|
||||
const results: TestResultSnapshot[] = [];
|
||||
|
||||
const walk = (r: TestResultSnapshot) => {
|
||||
for (const c of r.children) {
|
||||
walk(c);
|
||||
}
|
||||
if (r.uri?.toString() === uri.toString()) {
|
||||
results.push(r);
|
||||
}
|
||||
};
|
||||
|
||||
for (const r of testRun.results) {
|
||||
walk(r);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { spawn } from 'child_process';
|
||||
import { existsSync, mkdirSync, renameSync } from 'fs';
|
||||
import { readFile, writeFile } from 'fs/promises';
|
||||
import { dirname, join } from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
interface IGitState {
|
||||
commitId: string;
|
||||
tracked: string;
|
||||
untracked: Record<string, string>;
|
||||
}
|
||||
|
||||
interface ITrackedRemediation {
|
||||
snapshot: vscode.TestResultSnapshot;
|
||||
failing: IGitState;
|
||||
passing: IGitState;
|
||||
}
|
||||
|
||||
const MAX_FAILURES = 10;
|
||||
|
||||
export class FailureTracker {
|
||||
private readonly disposables: vscode.Disposable[] = [];
|
||||
private readonly lastFailed = new Map<
|
||||
string,
|
||||
{ snapshot: vscode.TestResultSnapshot; failing: IGitState }
|
||||
>();
|
||||
|
||||
private readonly logFile: string;
|
||||
private logs?: ITrackedRemediation[];
|
||||
|
||||
constructor(context: vscode.ExtensionContext, private readonly rootDir: string) {
|
||||
this.logFile = join(context.globalStorageUri.fsPath, '.build/vscode-test-failures.json');
|
||||
mkdirSync(dirname(this.logFile), { recursive: true });
|
||||
|
||||
const oldLogFile = join(rootDir, '.build/vscode-test-failures.json');
|
||||
if (existsSync(oldLogFile)) {
|
||||
try {
|
||||
renameSync(oldLogFile, this.logFile);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
this.disposables.push(
|
||||
vscode.commands.registerCommand('selfhost-test-provider.openFailureLog', async () => {
|
||||
const doc = await vscode.workspace.openTextDocument(this.logFile);
|
||||
await vscode.window.showTextDocument(doc);
|
||||
})
|
||||
);
|
||||
|
||||
this.disposables.push(
|
||||
vscode.tests.onDidChangeTestResults(() => {
|
||||
const last = vscode.tests.testResults[0];
|
||||
if (!last) {
|
||||
return;
|
||||
}
|
||||
|
||||
let gitState: Promise<IGitState> | undefined;
|
||||
const getGitState = () => gitState ?? (gitState = this.captureGitState());
|
||||
|
||||
const queue = [last.results];
|
||||
for (let i = 0; i < queue.length; i++) {
|
||||
for (const snapshot of queue[i]) {
|
||||
// only interested in states of leaf tests
|
||||
if (snapshot.children.length) {
|
||||
queue.push(snapshot.children);
|
||||
continue;
|
||||
}
|
||||
|
||||
const key = `${snapshot.uri}/${snapshot.id}`;
|
||||
const prev = this.lastFailed.get(key);
|
||||
if (snapshot.taskStates.some(s => s.state === vscode.TestResultState.Failed)) {
|
||||
// unset the parent to avoid a circular JSON structure:
|
||||
getGitState().then(s =>
|
||||
this.lastFailed.set(key, {
|
||||
snapshot: { ...snapshot, parent: undefined },
|
||||
failing: s,
|
||||
})
|
||||
);
|
||||
} else if (prev) {
|
||||
this.lastFailed.delete(key);
|
||||
getGitState().then(s => this.append({ ...prev, passing: s }));
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private async append(log: ITrackedRemediation) {
|
||||
if (!this.logs) {
|
||||
try {
|
||||
this.logs = JSON.parse(await readFile(this.logFile, 'utf-8'));
|
||||
} catch {
|
||||
this.logs = [];
|
||||
}
|
||||
}
|
||||
|
||||
const logs = this.logs!;
|
||||
logs.push(log);
|
||||
if (logs.length > MAX_FAILURES) {
|
||||
logs.splice(0, logs.length - MAX_FAILURES);
|
||||
}
|
||||
|
||||
await writeFile(this.logFile, JSON.stringify(logs, undefined, 2));
|
||||
}
|
||||
|
||||
private async captureGitState() {
|
||||
const [commitId, tracked, untracked] = await Promise.all([
|
||||
this.exec('git', ['rev-parse', 'HEAD']),
|
||||
this.exec('git', ['diff', 'HEAD']),
|
||||
this.exec('git', ['ls-files', '--others', '--exclude-standard']).then(async output => {
|
||||
const mapping: Record<string, string> = {};
|
||||
await Promise.all(
|
||||
output
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map(async f => {
|
||||
mapping[f] = await readFile(join(this.rootDir, f), 'utf-8');
|
||||
})
|
||||
);
|
||||
return mapping;
|
||||
}),
|
||||
]);
|
||||
return { commitId, tracked, untracked };
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.disposables.forEach(d => d.dispose());
|
||||
}
|
||||
|
||||
private exec(command: string, args: string[]): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(command, args, { stdio: 'pipe', cwd: this.rootDir });
|
||||
let output = '';
|
||||
child.stdout.setEncoding('utf-8').on('data', b => (output += b));
|
||||
child.stderr.setEncoding('utf-8').on('data', b => (output += b));
|
||||
child.on('error', reject);
|
||||
child.on('exit', code =>
|
||||
code === 0
|
||||
? resolve(output)
|
||||
: reject(new Error(`Failed with error code ${code}\n${output}`))
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { join } from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { bulkhead } from 'cockatiel';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
const maxInt32 = 2 ** 31 - 1;
|
||||
|
||||
// limit concurrency to avoid overwhelming the filesystem during discovery
|
||||
const discoverLimiter = bulkhead(8, Infinity);
|
||||
|
||||
// Max import distance when listing related code to improve relevancy.
|
||||
const defaultMaxDistance = 3;
|
||||
|
||||
/**
|
||||
* Maintains a graph of imports in the codebase. This works lazily resolving
|
||||
* imports and re-parsing files only on request.
|
||||
*
|
||||
* This is a rough, file-level graph derived from simple regex matching on
|
||||
* source files to avoid having to parse the AST of every file in the codebase,
|
||||
* which is possible but more intensive. (See: all the years of work from the
|
||||
* TS language server.)
|
||||
*
|
||||
* A more advanced implementation could use references from the language server.
|
||||
*/
|
||||
export class ImportGraph implements vscode.TestRelatedCodeProvider {
|
||||
private graph = new Map<string, FileNode>();
|
||||
|
||||
constructor(
|
||||
private readonly root: vscode.Uri,
|
||||
private readonly discoverWorkspaceTests: () => Thenable<vscode.TestItem[]>,
|
||||
private readonly getTestNodeForDoc: (uri: vscode.Uri) => vscode.TestItem | undefined,
|
||||
) { }
|
||||
|
||||
/** @inheritdoc */
|
||||
public async provideRelatedCode(test: vscode.TestItem, token: vscode.CancellationToken): Promise<vscode.Location[]> {
|
||||
// this is kind of a stub for this implementation. Naive following imports
|
||||
// isn't that useful for finding a test's related code.
|
||||
const node = await this.discoverOutwards(test.uri, new Set(), defaultMaxDistance, token);
|
||||
if (!node) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const imports = new Set<string>();
|
||||
const queue = [{ distance: 0, next: node.imports }];
|
||||
while (queue.length) {
|
||||
const { distance, next } = queue.shift()!;
|
||||
for (const imp of next) {
|
||||
if (imports.has(imp.path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
imports.add(imp.path);
|
||||
if (distance < defaultMaxDistance) {
|
||||
queue.push({ next: imp.imports, distance: distance + 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [...imports].map(importPath =>
|
||||
new vscode.Location(
|
||||
vscode.Uri.file(join(this.root.fsPath, 'src', `${importPath}.ts`)),
|
||||
new vscode.Range(0, 0, maxInt32, 0),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public async provideRelatedTests(document: vscode.TextDocument, _position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.TestItem[]> {
|
||||
// Expand all known tests to ensure imports of this file are realized.
|
||||
const rootTests = await this.discoverWorkspaceTests();
|
||||
const seen = new Set<string>();
|
||||
await Promise.all(rootTests.map(v => v.uri && this.discoverOutwards(v.uri, seen, defaultMaxDistance, token)));
|
||||
|
||||
const node = this.getNode(document.uri);
|
||||
if (!node) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const tests: vscode.TestItem[] = [];
|
||||
const queue: { next: FileNode; distance: number }[] = [{ next: node, distance: 0 }];
|
||||
const visited = new Set<FileNode>();
|
||||
let maxDistance = Infinity;
|
||||
|
||||
while (queue.length) {
|
||||
const { next, distance } = queue.shift()!;
|
||||
if (visited.has(next)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
visited.add(next);
|
||||
const testForDoc = this.getTestNodeForDoc(next.uri);
|
||||
if (testForDoc) {
|
||||
tests.push(testForDoc);
|
||||
// only look for tests half again as far away as the closest test to keep things relevant
|
||||
if (!Number.isFinite(maxDistance)) {
|
||||
maxDistance = distance * 3 / 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (distance < maxDistance) {
|
||||
for (const importedByNode of next.importedBy) {
|
||||
queue.push({ next: importedByNode, distance: distance + 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tests;
|
||||
}
|
||||
|
||||
public didChange(uri: vscode.Uri, deleted: boolean) {
|
||||
const rel = this.uriToImportPath(uri);
|
||||
const node = rel && this.graph.get(rel);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (deleted) {
|
||||
this.graph.delete(rel);
|
||||
for (const imp of node.imports) {
|
||||
imp.importedBy.delete(node);
|
||||
}
|
||||
} else {
|
||||
node.isSynced = false;
|
||||
}
|
||||
}
|
||||
|
||||
private getNode(uri: vscode.Uri | undefined): FileNode | undefined {
|
||||
const rel = this.uriToImportPath(uri);
|
||||
return rel ? this.graph.get(rel) : undefined;
|
||||
}
|
||||
|
||||
/** Discover all nodes that import the file */
|
||||
private async discoverOutwards(uri: vscode.Uri | undefined, seen: Set<string>, maxDistance: number, token: vscode.CancellationToken): Promise<FileNode | undefined> {
|
||||
const rel = this.uriToImportPath(uri);
|
||||
if (!rel) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let node = this.graph.get(rel);
|
||||
if (!node) {
|
||||
node = new FileNode(uri!, rel);
|
||||
this.graph.set(rel, node);
|
||||
}
|
||||
|
||||
await this.discoverOutwardsInner(node, seen, maxDistance, token);
|
||||
return node;
|
||||
}
|
||||
|
||||
private async discoverOutwardsInner(node: FileNode, seen: Set<string>, maxDistance: number, token: vscode.CancellationToken) {
|
||||
if (seen.has(node.path) || maxDistance === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
seen.add(node.path);
|
||||
if (node.isSynced === false) {
|
||||
await this.syncNode(node);
|
||||
} else if (node.isSynced instanceof Promise) {
|
||||
await node.isSynced;
|
||||
}
|
||||
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
await Promise.all([...node.imports].map(i => this.discoverOutwardsInner(i, seen, maxDistance - 1, token)));
|
||||
}
|
||||
|
||||
private async syncNode(node: FileNode) {
|
||||
node.isSynced = discoverLimiter.execute(async () => {
|
||||
const doc = vscode.workspace.textDocuments.find(d => d.uri.toString() === node.uri.toString());
|
||||
|
||||
let text: string;
|
||||
if (doc) {
|
||||
text = doc.getText();
|
||||
} else {
|
||||
try {
|
||||
text = await fs.readFile(node.uri.fsPath, 'utf8');
|
||||
} catch {
|
||||
text = '';
|
||||
}
|
||||
}
|
||||
|
||||
for (const imp of node.imports) {
|
||||
imp.importedBy.delete(node);
|
||||
}
|
||||
node.imports.clear();
|
||||
|
||||
for (const [, importPath] of text.matchAll(IMPORT_RE)) {
|
||||
let imp = this.graph.get(importPath);
|
||||
if (!imp) {
|
||||
imp = new FileNode(this.importPathToUri(importPath), importPath);
|
||||
this.graph.set(importPath, imp);
|
||||
}
|
||||
|
||||
imp.importedBy.add(node);
|
||||
node.imports.add(imp);
|
||||
}
|
||||
|
||||
node.isSynced = true;
|
||||
});
|
||||
|
||||
await node.isSynced;
|
||||
}
|
||||
|
||||
private uriToImportPath(uri: vscode.Uri | undefined) {
|
||||
if (!uri) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const relativePath = vscode.workspace.asRelativePath(uri).replaceAll('\\', '/');
|
||||
if (!relativePath.startsWith('src/vs/') || !relativePath.endsWith('.ts')) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return relativePath.slice('src/'.length, -'.ts'.length);
|
||||
}
|
||||
|
||||
private importPathToUri(importPath: string) {
|
||||
return vscode.Uri.file(join(this.root.fsPath, 'src', `${importPath}.ts`));
|
||||
}
|
||||
}
|
||||
|
||||
const IMPORT_RE = /import .*? from ["'](vs\/[^"']+)/g;
|
||||
|
||||
class FileNode {
|
||||
public imports = new Set<FileNode>();
|
||||
public importedBy = new Set<FileNode>();
|
||||
public isSynced: boolean | Promise<void> = false;
|
||||
|
||||
// Path is the *import path* starting with `vs/`
|
||||
constructor(
|
||||
public readonly uri: vscode.Uri,
|
||||
public readonly path: string,
|
||||
) { }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export const memoizeLast = <A, T>(fn: (args: A) => T): ((args: A) => T) => {
|
||||
let last: { arg: A; result: T } | undefined;
|
||||
return arg => {
|
||||
if (last && last.arg === arg) {
|
||||
return last.result;
|
||||
}
|
||||
|
||||
const result = fn(arg);
|
||||
last = { arg, result };
|
||||
return result;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { TestMessage } from 'vscode';
|
||||
|
||||
export interface TestMessageMetadata {
|
||||
expectedValue: unknown;
|
||||
actualValue: unknown;
|
||||
}
|
||||
|
||||
const cache = new Array<{ id: string; metadata: TestMessageMetadata }>();
|
||||
|
||||
let id = 0;
|
||||
|
||||
function getId(): string {
|
||||
return `msg:${id++}:`;
|
||||
}
|
||||
|
||||
const regexp = /msg:\d+:/;
|
||||
|
||||
export function attachTestMessageMetadata(
|
||||
message: TestMessage,
|
||||
metadata: TestMessageMetadata
|
||||
): void {
|
||||
const existingMetadata = getTestMessageMetadata(message);
|
||||
if (existingMetadata) {
|
||||
Object.assign(existingMetadata, metadata);
|
||||
return;
|
||||
}
|
||||
|
||||
const id = getId();
|
||||
|
||||
if (typeof message.message === 'string') {
|
||||
message.message = `${message.message}\n${id}`;
|
||||
} else {
|
||||
message.message.appendText(`\n${id}`);
|
||||
}
|
||||
|
||||
cache.push({ id, metadata });
|
||||
while (cache.length > 100) {
|
||||
cache.shift();
|
||||
}
|
||||
}
|
||||
|
||||
export function getTestMessageMetadata(message: TestMessage): TestMessageMetadata | undefined {
|
||||
let value: string;
|
||||
if (typeof message.message === 'string') {
|
||||
value = message.message;
|
||||
} else {
|
||||
value = message.message.value;
|
||||
}
|
||||
|
||||
const result = regexp.exec(value);
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const id = result[0];
|
||||
return cache.find(c => c.id === id)?.metadata;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { promises as fs } from 'fs';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export const snapshotComment = '\n\n// Snapshot file: ';
|
||||
|
||||
export const registerSnapshotUpdate = (ctrl: vscode.TestController) =>
|
||||
vscode.commands.registerCommand('selfhost-test-provider.updateSnapshot', async args => {
|
||||
const message: vscode.TestMessage = args.message;
|
||||
const index = message.expectedOutput?.indexOf(snapshotComment);
|
||||
if (!message.expectedOutput || !message.actualOutput || !index || index === -1) {
|
||||
vscode.window.showErrorMessage('Could not find snapshot comment in message');
|
||||
return;
|
||||
}
|
||||
|
||||
const file = message.expectedOutput.slice(index + snapshotComment.length);
|
||||
await fs.writeFile(file, message.actualOutput);
|
||||
ctrl.invalidateTestResults(args.test);
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
import * as vscode from 'vscode';
|
||||
import { TestCase, TestConstruct, TestSuite, VSCodeTest } from './testTree';
|
||||
|
||||
const suiteNames = new Set(['suite', 'flakySuite']);
|
||||
|
||||
export const enum Action {
|
||||
Skip,
|
||||
Recurse,
|
||||
}
|
||||
|
||||
export const extractTestFromNode = (src: ts.SourceFile, node: ts.Node, parent: VSCodeTest) => {
|
||||
if (!ts.isCallExpression(node)) {
|
||||
return Action.Recurse;
|
||||
}
|
||||
|
||||
let lhs = node.expression;
|
||||
if (isSkipCall(lhs)) {
|
||||
return Action.Skip;
|
||||
}
|
||||
|
||||
if (isPropertyCall(lhs) && lhs.name.text === 'only') {
|
||||
lhs = lhs.expression;
|
||||
}
|
||||
|
||||
const name = node.arguments[0];
|
||||
const func = node.arguments[1];
|
||||
if (!name || !ts.isIdentifier(lhs) || !ts.isStringLiteralLike(name)) {
|
||||
return Action.Recurse;
|
||||
}
|
||||
|
||||
if (!func) {
|
||||
return Action.Recurse;
|
||||
}
|
||||
|
||||
const start = src.getLineAndCharacterOfPosition(name.pos);
|
||||
const end = src.getLineAndCharacterOfPosition(func.end);
|
||||
const range = new vscode.Range(
|
||||
new vscode.Position(start.line, start.character),
|
||||
new vscode.Position(end.line, end.character)
|
||||
);
|
||||
|
||||
const cparent = parent instanceof TestConstruct ? parent : undefined;
|
||||
if (lhs.escapedText === 'test') {
|
||||
return new TestCase(name.text, range, cparent);
|
||||
}
|
||||
|
||||
if (suiteNames.has(lhs.escapedText.toString())) {
|
||||
return new TestSuite(name.text, range, cparent);
|
||||
}
|
||||
|
||||
return Action.Recurse;
|
||||
};
|
||||
|
||||
const isPropertyCall = (
|
||||
lhs: ts.LeftHandSideExpression
|
||||
): lhs is ts.PropertyAccessExpression & { expression: ts.Identifier; name: ts.Identifier } =>
|
||||
ts.isPropertyAccessExpression(lhs) &&
|
||||
ts.isIdentifier(lhs.expression) &&
|
||||
ts.isIdentifier(lhs.name);
|
||||
|
||||
const isSkipCall = (lhs: ts.LeftHandSideExpression) =>
|
||||
isPropertyCall(lhs) && suiteNames.has(lhs.expression.text) && lhs.name.text === 'skip';
|
||||
@@ -0,0 +1,60 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// Copied from https://github.com/microsoft/vscode-js-debug/blob/1d104b5184736677ab5cc280c70bbd227403850c/src/common/stackTraceParser.ts#L18
|
||||
|
||||
// Either match lines like
|
||||
// " at fulfilled (/Users/roblou/code/testapp-node2/out/app.js:5:58)"
|
||||
// or
|
||||
// " at /Users/roblou/code/testapp-node2/out/app.js:60:23"
|
||||
// and replace the path in them
|
||||
const re1 = /^(\W*at .*\()(.*):(\d+):(\d+)(\))$/;
|
||||
const re2 = /^(\W*at )(.*):(\d+):(\d+)$/;
|
||||
|
||||
const getLabelRe = /^\W*at (.*) \($/;
|
||||
|
||||
/**
|
||||
* Parses a textual stack trace.
|
||||
*/
|
||||
export class StackTraceParser {
|
||||
/** Gets whether the stacktrace has any locations in it. */
|
||||
public static isStackLike(str: string) {
|
||||
return re1.test(str) || re2.test(str);
|
||||
}
|
||||
constructor(private readonly stack: string) { }
|
||||
|
||||
/** Iterates over segments of text and locations in the stack. */
|
||||
*[Symbol.iterator]() {
|
||||
for (const line of this.stack.split('\n')) {
|
||||
const match = re1.exec(line) || re2.exec(line);
|
||||
if (!match) {
|
||||
yield line + '\n';
|
||||
continue;
|
||||
}
|
||||
|
||||
const [, prefix, url, lineNo, columnNo, suffix] = match;
|
||||
if (prefix) {
|
||||
yield prefix;
|
||||
}
|
||||
|
||||
yield new StackTraceLocation(getLabelRe.exec(prefix)?.[1], url, Number(lineNo), Number(columnNo));
|
||||
|
||||
if (suffix) {
|
||||
yield suffix;
|
||||
}
|
||||
|
||||
yield '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class StackTraceLocation {
|
||||
constructor(
|
||||
public readonly label: string | undefined,
|
||||
public readonly path: string,
|
||||
public readonly lineBase1: number,
|
||||
public readonly columnBase1: number,
|
||||
) { }
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// DO NOT EDIT DIRECTLY: copied from src/vs/base/node/nodeStreams.ts
|
||||
|
||||
import { Transform } from 'stream';
|
||||
|
||||
/**
|
||||
* A Transform stream that splits the input on the "splitter" substring.
|
||||
* The resulting chunks will contain (and trail with) the splitter match.
|
||||
* The last chunk when the stream ends will be emitted even if a splitter
|
||||
* is not encountered.
|
||||
*/
|
||||
export class StreamSplitter extends Transform {
|
||||
private buffer: Buffer | undefined;
|
||||
private readonly splitter: number;
|
||||
private readonly spitterLen: number;
|
||||
|
||||
constructor(splitter: string | number | Buffer) {
|
||||
super();
|
||||
if (typeof splitter === 'number') {
|
||||
this.splitter = splitter;
|
||||
this.spitterLen = 1;
|
||||
} else {
|
||||
throw new Error('not implemented here');
|
||||
}
|
||||
}
|
||||
|
||||
override _transform(
|
||||
chunk: Buffer,
|
||||
_encoding: string,
|
||||
callback: (error?: Error | null, data?: any) => void
|
||||
): void {
|
||||
if (!this.buffer) {
|
||||
this.buffer = chunk;
|
||||
} else {
|
||||
this.buffer = Buffer.concat([this.buffer, chunk]);
|
||||
}
|
||||
|
||||
let offset = 0;
|
||||
while (offset < this.buffer.length) {
|
||||
const index = this.buffer.indexOf(this.splitter, offset);
|
||||
if (index === -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
this.push(this.buffer.slice(offset, index + this.spitterLen));
|
||||
offset = index + this.spitterLen;
|
||||
}
|
||||
|
||||
this.buffer = offset === this.buffer.length ? undefined : this.buffer.slice(offset);
|
||||
callback();
|
||||
}
|
||||
|
||||
override _flush(callback: (error?: Error | null, data?: any) => void): void {
|
||||
if (this.buffer) {
|
||||
this.push(this.buffer);
|
||||
}
|
||||
|
||||
callback();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,660 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import {
|
||||
decodedMappings,
|
||||
GREATEST_LOWER_BOUND,
|
||||
LEAST_UPPER_BOUND,
|
||||
originalPositionFor,
|
||||
TraceMap,
|
||||
} from '@jridgewell/trace-mapping';
|
||||
import * as styles from 'ansi-styles';
|
||||
import { ChildProcessWithoutNullStreams } from 'child_process';
|
||||
import * as vscode from 'vscode';
|
||||
import { istanbulCoverageContext, PerTestCoverageTracker } from './coverageProvider';
|
||||
import { attachTestMessageMetadata } from './metadata';
|
||||
import { snapshotComment } from './snapshot';
|
||||
import { StackTraceLocation, StackTraceParser } from './stackTraceParser';
|
||||
import { StreamSplitter } from './streamSplitter';
|
||||
import { getContentFromFilesystem } from './testTree';
|
||||
import { IScriptCoverage } from './v8CoverageWrangling';
|
||||
|
||||
export const enum MochaEvent {
|
||||
Start = 'start',
|
||||
TestStart = 'testStart',
|
||||
Pass = 'pass',
|
||||
Fail = 'fail',
|
||||
End = 'end',
|
||||
|
||||
// custom events:
|
||||
CoverageInit = 'coverageInit',
|
||||
CoverageIncrement = 'coverageIncrement',
|
||||
}
|
||||
|
||||
export interface IStartEvent {
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface ITestStartEvent {
|
||||
title: string;
|
||||
fullTitle: string;
|
||||
file: string;
|
||||
currentRetry: number;
|
||||
speed: string;
|
||||
}
|
||||
|
||||
export interface IPassEvent extends ITestStartEvent {
|
||||
duration: number;
|
||||
}
|
||||
|
||||
export interface IFailEvent extends IPassEvent {
|
||||
err: string;
|
||||
stack: string | null;
|
||||
expected?: string;
|
||||
actual?: string;
|
||||
expectedJSON?: unknown;
|
||||
actualJSON?: unknown;
|
||||
snapshotPath?: string;
|
||||
}
|
||||
|
||||
export interface IEndEvent {
|
||||
suites: number;
|
||||
tests: number;
|
||||
passes: number;
|
||||
pending: number;
|
||||
failures: number;
|
||||
start: string /* ISO date */;
|
||||
end: string /* ISO date */;
|
||||
}
|
||||
|
||||
export interface ITestCoverageCoverage {
|
||||
file: string;
|
||||
fullTitle: string;
|
||||
coverage: { result: IScriptCoverage[] };
|
||||
}
|
||||
|
||||
export type MochaEventTuple =
|
||||
| [MochaEvent.Start, IStartEvent]
|
||||
| [MochaEvent.TestStart, ITestStartEvent]
|
||||
| [MochaEvent.Pass, IPassEvent]
|
||||
| [MochaEvent.Fail, IFailEvent]
|
||||
| [MochaEvent.End, IEndEvent]
|
||||
| [MochaEvent.CoverageInit, { result: IScriptCoverage[] }]
|
||||
| [MochaEvent.CoverageIncrement, ITestCoverageCoverage];
|
||||
|
||||
const LF = '\n'.charCodeAt(0);
|
||||
|
||||
export class TestOutputScanner implements vscode.Disposable {
|
||||
protected mochaEventEmitter = new vscode.EventEmitter<MochaEventTuple>();
|
||||
protected outputEventEmitter = new vscode.EventEmitter<string>();
|
||||
protected onExitEmitter = new vscode.EventEmitter<string | undefined>();
|
||||
|
||||
/**
|
||||
* Fired when a mocha event comes in.
|
||||
*/
|
||||
public readonly onMochaEvent = this.mochaEventEmitter.event;
|
||||
|
||||
/**
|
||||
* Fired when other output from the process comes in.
|
||||
*/
|
||||
public readonly onOtherOutput = this.outputEventEmitter.event;
|
||||
|
||||
/**
|
||||
* Fired when the process encounters an error, or exits.
|
||||
*/
|
||||
public readonly onRunnerExit = this.onExitEmitter.event;
|
||||
|
||||
constructor(private readonly process: ChildProcessWithoutNullStreams, private args?: string[]) {
|
||||
process.stdout.pipe(new StreamSplitter(LF)).on('data', this.processData);
|
||||
process.stderr.pipe(new StreamSplitter(LF)).on('data', this.processData);
|
||||
process.on('error', e => this.onExitEmitter.fire(e.message));
|
||||
process.on('exit', code =>
|
||||
this.onExitEmitter.fire(code ? `Test process exited with code ${code}` : undefined)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public dispose() {
|
||||
try {
|
||||
this.process.kill();
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
protected readonly processData = (data: string | Buffer) => {
|
||||
if (this.args) {
|
||||
this.outputEventEmitter.fire(`./scripts/test ${this.args.join(' ')}`);
|
||||
this.args = undefined;
|
||||
}
|
||||
|
||||
data = data.toString();
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(data.trim()) as unknown;
|
||||
if (parsed instanceof Array && parsed.length === 2 && typeof parsed[0] === 'string') {
|
||||
this.mochaEventEmitter.fire(parsed as MochaEventTuple);
|
||||
} else {
|
||||
this.outputEventEmitter.fire(data);
|
||||
}
|
||||
} catch {
|
||||
this.outputEventEmitter.fire(data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
type QueuedOutput = string | [string, vscode.Location | undefined, vscode.TestItem | undefined];
|
||||
|
||||
export async function scanTestOutput(
|
||||
tests: Map<string, vscode.TestItem>,
|
||||
task: vscode.TestRun,
|
||||
scanner: TestOutputScanner,
|
||||
coverageDir: string | undefined,
|
||||
cancellation: vscode.CancellationToken
|
||||
): Promise<void> {
|
||||
const exitBlockers: Set<Promise<unknown>> = new Set();
|
||||
const skippedTests = new Set(tests.values());
|
||||
const store = new SourceMapStore();
|
||||
let outputQueue = Promise.resolve();
|
||||
const enqueueOutput = (fn: QueuedOutput | (() => Promise<QueuedOutput>)) => {
|
||||
exitBlockers.delete(outputQueue);
|
||||
outputQueue = outputQueue.finally(async () => {
|
||||
const r = typeof fn === 'function' ? await fn() : fn;
|
||||
typeof r === 'string' ? task.appendOutput(r) : task.appendOutput(...r);
|
||||
});
|
||||
exitBlockers.add(outputQueue);
|
||||
return outputQueue;
|
||||
};
|
||||
const enqueueExitBlocker = <T>(prom: Promise<T>): Promise<T> => {
|
||||
exitBlockers.add(prom);
|
||||
prom.finally(() => exitBlockers.delete(prom));
|
||||
return prom;
|
||||
};
|
||||
|
||||
let perTestCoverage: PerTestCoverageTracker | undefined;
|
||||
let lastTest: vscode.TestItem | undefined;
|
||||
let ranAnyTest = false;
|
||||
|
||||
try {
|
||||
if (cancellation.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
await new Promise<void>(resolve => {
|
||||
cancellation.onCancellationRequested(() => {
|
||||
resolve();
|
||||
});
|
||||
|
||||
let currentTest: vscode.TestItem | undefined;
|
||||
|
||||
scanner.onRunnerExit(err => {
|
||||
if (err) {
|
||||
enqueueOutput(err + crlf);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
|
||||
scanner.onOtherOutput(str => {
|
||||
const match = spdlogRe.exec(str);
|
||||
if (!match) {
|
||||
enqueueOutput(str + crlf);
|
||||
return;
|
||||
}
|
||||
|
||||
const logLocation = store.getSourceLocation(match[2], Number(match[3]) - 1);
|
||||
const logContents = replaceAllLocations(store, match[1]);
|
||||
const test = currentTest;
|
||||
|
||||
enqueueOutput(() =>
|
||||
Promise.all([logLocation, logContents]).then(([location, contents]) => [
|
||||
contents + crlf,
|
||||
location,
|
||||
test,
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
scanner.onMochaEvent(evt => {
|
||||
switch (evt[0]) {
|
||||
case MochaEvent.Start:
|
||||
break; // no-op
|
||||
case MochaEvent.TestStart:
|
||||
currentTest = tests.get(evt[1].fullTitle);
|
||||
if (!currentTest) {
|
||||
console.warn(`Could not find test ${evt[1].fullTitle}`);
|
||||
return;
|
||||
}
|
||||
skippedTests.delete(currentTest);
|
||||
task.started(currentTest);
|
||||
ranAnyTest = true;
|
||||
break;
|
||||
case MochaEvent.Pass:
|
||||
{
|
||||
const title = evt[1].fullTitle;
|
||||
const tcase = tests.get(title);
|
||||
enqueueOutput(` ${styles.green.open}√${styles.green.close} ${title}\r\n`);
|
||||
if (tcase) {
|
||||
lastTest = tcase;
|
||||
task.passed(tcase, evt[1].duration);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MochaEvent.Fail:
|
||||
{
|
||||
const {
|
||||
err,
|
||||
stack,
|
||||
duration,
|
||||
expected,
|
||||
expectedJSON,
|
||||
actual,
|
||||
actualJSON,
|
||||
snapshotPath,
|
||||
fullTitle: id,
|
||||
} = evt[1];
|
||||
let tcase = tests.get(id);
|
||||
// report failures on hook to the last-seen test, or first test if none run yet
|
||||
if (!tcase && (id.includes('hook for') || id.includes('hook in'))) {
|
||||
tcase = lastTest ?? tests.values().next().value;
|
||||
}
|
||||
|
||||
enqueueOutput(`${styles.red.open} x ${id}${styles.red.close}\r\n`);
|
||||
const rawErr = stack || err;
|
||||
const locationsReplaced = replaceAllLocations(store, forceCRLF(rawErr));
|
||||
if (rawErr) {
|
||||
enqueueOutput(async () => [await locationsReplaced, undefined, tcase]);
|
||||
}
|
||||
|
||||
if (!tcase) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasDiff =
|
||||
actual !== undefined &&
|
||||
expected !== undefined &&
|
||||
(expected !== '[undefined]' || actual !== '[undefined]');
|
||||
const testFirstLine =
|
||||
tcase.range &&
|
||||
new vscode.Location(
|
||||
tcase.uri!,
|
||||
new vscode.Range(
|
||||
tcase.range.start,
|
||||
new vscode.Position(tcase.range.start.line, 100)
|
||||
)
|
||||
);
|
||||
|
||||
enqueueExitBlocker(
|
||||
(async () => {
|
||||
const stackInfo = await deriveStackLocations(store, rawErr, tcase!);
|
||||
let message: vscode.TestMessage;
|
||||
|
||||
if (hasDiff) {
|
||||
message = new vscode.TestMessage(tryMakeMarkdown(err));
|
||||
message.actualOutput = outputToString(actual);
|
||||
message.expectedOutput = outputToString(expected);
|
||||
if (snapshotPath) {
|
||||
message.contextValue = 'isSelfhostSnapshotMessage';
|
||||
message.expectedOutput += snapshotComment + snapshotPath;
|
||||
}
|
||||
|
||||
attachTestMessageMetadata(message, {
|
||||
expectedValue: expectedJSON,
|
||||
actualValue: actualJSON,
|
||||
});
|
||||
} else {
|
||||
message = new vscode.TestMessage(
|
||||
stack ? await sourcemapStack(store, stack) : await locationsReplaced
|
||||
);
|
||||
}
|
||||
|
||||
message.location = stackInfo.primary ?? testFirstLine;
|
||||
message.stackTrace = stackInfo.stack;
|
||||
task.failed(tcase!, message, duration);
|
||||
})()
|
||||
);
|
||||
}
|
||||
break;
|
||||
case MochaEvent.End:
|
||||
// no-op, we wait until the process exits to ensure coverage is written out
|
||||
break;
|
||||
case MochaEvent.CoverageInit:
|
||||
perTestCoverage ??= new PerTestCoverageTracker(store);
|
||||
for (const result of evt[1].result) {
|
||||
perTestCoverage.add(result);
|
||||
}
|
||||
break;
|
||||
case MochaEvent.CoverageIncrement: {
|
||||
const { fullTitle, coverage } = evt[1];
|
||||
const tcase = tests.get(fullTitle);
|
||||
if (tcase) {
|
||||
perTestCoverage ??= new PerTestCoverageTracker(store);
|
||||
for (const result of coverage.result) {
|
||||
perTestCoverage.add(result, tcase);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (perTestCoverage) {
|
||||
enqueueExitBlocker(perTestCoverage.report(task));
|
||||
}
|
||||
|
||||
await Promise.all([...exitBlockers]);
|
||||
|
||||
if (coverageDir) {
|
||||
try {
|
||||
await istanbulCoverageContext.apply(task, coverageDir, {
|
||||
mapFileUri: uri => store.getSourceFile(uri.toString()),
|
||||
mapLocation: (uri, position) =>
|
||||
store.getSourceLocation(uri.toString(), position.line, position.character),
|
||||
});
|
||||
} catch (e) {
|
||||
const msg = `Error loading coverage:\n\n${e}\n`;
|
||||
task.appendOutput(msg.replace(/\n/g, crlf));
|
||||
}
|
||||
}
|
||||
|
||||
// no tests? Possible crash, show output:
|
||||
if (!ranAnyTest) {
|
||||
await vscode.commands.executeCommand('testing.showMostRecentOutput');
|
||||
}
|
||||
} catch (e) {
|
||||
task.appendOutput((e as Error).stack || (e as Error).message);
|
||||
} finally {
|
||||
scanner.dispose();
|
||||
for (const test of skippedTests) {
|
||||
task.skipped(test);
|
||||
}
|
||||
task.end();
|
||||
}
|
||||
}
|
||||
|
||||
const spdlogRe = /"(.+)", source: (file:\/\/\/.*?)+ \(([0-9]+)\)/;
|
||||
const crlf = '\r\n';
|
||||
|
||||
const forceCRLF = (str: string) => str.replace(/(?<!\r)\n/gm, '\r\n');
|
||||
|
||||
const sourcemapStack = async (store: SourceMapStore, str: string) => {
|
||||
locationRe.lastIndex = 0;
|
||||
|
||||
const replacements = await Promise.all(
|
||||
[...str.matchAll(locationRe)].map(async match => {
|
||||
const location = await deriveSourceLocation(store, match);
|
||||
if (!location) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
from: match[0],
|
||||
to: location?.uri.with({
|
||||
fragment: `L${location.range.start.line + 1}:${location.range.start.character + 1}`,
|
||||
}),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
for (const replacement of replacements) {
|
||||
if (replacement) {
|
||||
str = str.replace(replacement.from, replacement.to.toString(true));
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
};
|
||||
|
||||
const outputToString = (output: unknown) =>
|
||||
typeof output === 'object' ? JSON.stringify(output, null, 2) : String(output);
|
||||
|
||||
const tryMakeMarkdown = (message: string) => {
|
||||
const lines = message.split('\n');
|
||||
const start = lines.findIndex(l => l.includes('+ actual'));
|
||||
if (start === -1) {
|
||||
return message;
|
||||
}
|
||||
|
||||
lines.splice(start, 1, '```diff');
|
||||
lines.push('```');
|
||||
return new vscode.MarkdownString(lines.join('\n'));
|
||||
};
|
||||
|
||||
const inlineSourcemapRe = /^\/\/# sourceMappingURL=data:application\/json;base64,(.+)/m;
|
||||
const sourceMapBiases = [GREATEST_LOWER_BOUND, LEAST_UPPER_BOUND] as const;
|
||||
|
||||
export const enum SearchStrategy {
|
||||
FirstBefore = -1,
|
||||
FirstAfter = 1,
|
||||
}
|
||||
|
||||
export type SourceLocationMapper = (line: number, col: number, strategy: SearchStrategy) => vscode.Location | undefined;
|
||||
|
||||
export class SourceMapStore {
|
||||
private readonly cache = new Map</* file uri */ string, Promise<TraceMap | undefined>>();
|
||||
|
||||
async getSourceLocationMapper(fileUri: string): Promise<SourceLocationMapper> {
|
||||
const sourceMap = await this.loadSourceMap(fileUri);
|
||||
return (line, col, strategy) => {
|
||||
if (!sourceMap) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 1. Look for the ideal position on this line if it exists
|
||||
const idealPosition = originalPositionFor(sourceMap, { column: col, line: line + 1, bias: SearchStrategy.FirstAfter ? GREATEST_LOWER_BOUND : LEAST_UPPER_BOUND });
|
||||
if (idealPosition.line !== null && idealPosition.column !== null && idealPosition.source !== null) {
|
||||
return new vscode.Location(
|
||||
this.completeSourceMapUrl(sourceMap, idealPosition.source),
|
||||
new vscode.Position(idealPosition.line - 1, idealPosition.column)
|
||||
);
|
||||
}
|
||||
|
||||
// Otherwise get the first/last valid mapping on another line.
|
||||
const decoded = decodedMappings(sourceMap);
|
||||
const enum MapField {
|
||||
COLUMN = 0,
|
||||
SOURCES_INDEX = 1,
|
||||
SOURCE_LINE = 2,
|
||||
SOURCE_COLUMN = 3,
|
||||
}
|
||||
|
||||
do {
|
||||
line += strategy;
|
||||
const segments = decoded[line];
|
||||
if (!segments?.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const index = strategy === SearchStrategy.FirstBefore
|
||||
? findLastIndex(segments, s => s.length !== 1)
|
||||
: segments.findIndex(s => s.length !== 1);
|
||||
const segment = segments[index];
|
||||
|
||||
if (!segment || segment.length === 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return new vscode.Location(
|
||||
this.completeSourceMapUrl(sourceMap, sourceMap.sources[segment[MapField.SOURCES_INDEX]]!),
|
||||
new vscode.Position(segment[MapField.SOURCE_LINE] - 1, segment[MapField.SOURCE_COLUMN])
|
||||
);
|
||||
} while (strategy === SearchStrategy.FirstBefore ? line > 0 : line < decoded.length);
|
||||
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
|
||||
/** Gets an original location from a base 0 line and column */
|
||||
async getSourceLocation(fileUri: string, line: number, col = 0) {
|
||||
const sourceMap = await this.loadSourceMap(fileUri);
|
||||
if (!sourceMap) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let smLine = line + 1;
|
||||
|
||||
// if the range is after the end of mappings, adjust it to the last mapped line
|
||||
const decoded = decodedMappings(sourceMap);
|
||||
if (decoded.length <= line) {
|
||||
smLine = decoded.length; // base 1, no -1 needed
|
||||
col = Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
|
||||
for (const bias of sourceMapBiases) {
|
||||
const position = originalPositionFor(sourceMap, { column: col, line: smLine, bias });
|
||||
if (position.line !== null && position.column !== null && position.source !== null) {
|
||||
return new vscode.Location(
|
||||
this.completeSourceMapUrl(sourceMap, position.source),
|
||||
new vscode.Position(position.line - 1, position.column)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async getSourceFile(compiledUri: string) {
|
||||
const sourceMap = await this.loadSourceMap(compiledUri);
|
||||
if (!sourceMap) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (sourceMap.sources[0]) {
|
||||
return this.completeSourceMapUrl(sourceMap, sourceMap.sources[0]);
|
||||
}
|
||||
|
||||
for (const bias of sourceMapBiases) {
|
||||
const position = originalPositionFor(sourceMap, { column: 0, line: 1, bias });
|
||||
if (position.source !== null) {
|
||||
return this.completeSourceMapUrl(sourceMap, position.source);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private completeSourceMapUrl(sm: TraceMap, source: string) {
|
||||
if (sm.sourceRoot) {
|
||||
try {
|
||||
return vscode.Uri.parse(new URL(source, sm.sourceRoot).toString());
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
return vscode.Uri.parse(source);
|
||||
}
|
||||
|
||||
private loadSourceMap(fileUri: string) {
|
||||
const existing = this.cache.get(fileUri);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const promise = (async () => {
|
||||
try {
|
||||
const contents = await getContentFromFilesystem(vscode.Uri.parse(fileUri));
|
||||
const sourcemapMatch = inlineSourcemapRe.exec(contents);
|
||||
if (!sourcemapMatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
const decoded = Buffer.from(sourcemapMatch[1], 'base64').toString();
|
||||
return new TraceMap(decoded, fileUri);
|
||||
} catch (e) {
|
||||
console.warn(`Error parsing sourcemap for ${fileUri}: ${(e as Error).stack}`);
|
||||
return;
|
||||
}
|
||||
})();
|
||||
|
||||
this.cache.set(fileUri, promise);
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
|
||||
const locationRe = /(file:\/{3}.+):([0-9]+):([0-9]+)/g;
|
||||
|
||||
async function replaceAllLocations(store: SourceMapStore, str: string) {
|
||||
const output: (string | Promise<string>)[] = [];
|
||||
let lastIndex = 0;
|
||||
|
||||
for (const match of str.matchAll(locationRe)) {
|
||||
const locationPromise = deriveSourceLocation(store, match);
|
||||
const startIndex = match.index || 0;
|
||||
const endIndex = startIndex + match[0].length;
|
||||
|
||||
if (startIndex > lastIndex) {
|
||||
output.push(str.substring(lastIndex, startIndex));
|
||||
}
|
||||
|
||||
output.push(
|
||||
locationPromise.then(location =>
|
||||
location
|
||||
? `${location.uri}:${location.range.start.line + 1}:${location.range.start.character + 1}`
|
||||
: match[0]
|
||||
)
|
||||
);
|
||||
|
||||
lastIndex = endIndex;
|
||||
}
|
||||
|
||||
// Preserve the remaining string after the last match
|
||||
if (lastIndex < str.length) {
|
||||
output.push(str.substring(lastIndex));
|
||||
}
|
||||
|
||||
const values = await Promise.all(output);
|
||||
return values.join('');
|
||||
}
|
||||
|
||||
async function deriveStackLocations(
|
||||
store: SourceMapStore,
|
||||
stack: string,
|
||||
tcase: vscode.TestItem
|
||||
) {
|
||||
locationRe.lastIndex = 0;
|
||||
|
||||
const locationsRaw = [...new StackTraceParser(stack)].filter(t => t instanceof StackTraceLocation);
|
||||
const locationsMapped = await Promise.all(locationsRaw.map(async location => {
|
||||
const mapped = location.path.startsWith('file:') ? await store.getSourceLocation(location.path, location.lineBase1 - 1, location.columnBase1 - 1) : undefined;
|
||||
const stack = new vscode.TestMessageStackFrame(location.label || '<anonymous>', mapped?.uri, mapped?.range.start || new vscode.Position(location.lineBase1 - 1, location.columnBase1 - 1));
|
||||
return { location: mapped, stack };
|
||||
}));
|
||||
|
||||
let best: undefined | { location: vscode.Location; score: number };
|
||||
for (const { location } of locationsMapped) {
|
||||
if (!location) {
|
||||
continue;
|
||||
}
|
||||
let score = 0;
|
||||
if (tcase.uri && tcase.uri.toString() === location.uri.toString()) {
|
||||
score = 1;
|
||||
if (tcase.range && tcase.range.contains(location?.range)) {
|
||||
score = 2;
|
||||
}
|
||||
}
|
||||
if (!best || score > best.score) {
|
||||
best = { location, score };
|
||||
}
|
||||
}
|
||||
|
||||
return { stack: locationsMapped.map(s => s.stack), primary: best?.location };
|
||||
}
|
||||
|
||||
async function deriveSourceLocation(store: SourceMapStore, parts: RegExpMatchArray) {
|
||||
const [, fileUri, line, col] = parts;
|
||||
return store.getSourceLocation(fileUri, Number(line) - 1, Number(col));
|
||||
}
|
||||
|
||||
function findLastIndex<T>(arr: T[], predicate: (value: T) => boolean) {
|
||||
for (let i = arr.length - 1; i >= 0; i--) {
|
||||
if (predicate(arr[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { join, relative } from 'path';
|
||||
import * as ts from 'typescript';
|
||||
import { TextDecoder } from 'util';
|
||||
import * as vscode from 'vscode';
|
||||
import { Action, extractTestFromNode } from './sourceUtils';
|
||||
|
||||
const textDecoder = new TextDecoder('utf-8');
|
||||
const diagnosticCollection = vscode.languages.createDiagnosticCollection('selfhostTestProvider');
|
||||
|
||||
type ContentGetter = (uri: vscode.Uri) => Promise<string>;
|
||||
|
||||
export const itemData = new WeakMap<vscode.TestItem, VSCodeTest>();
|
||||
|
||||
export const clearFileDiagnostics = (uri: vscode.Uri) => diagnosticCollection.delete(uri);
|
||||
|
||||
/**
|
||||
* Tries to guess which workspace folder VS Code is in.
|
||||
*/
|
||||
export const guessWorkspaceFolder = async () => {
|
||||
if (!vscode.workspace.workspaceFolders) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (vscode.workspace.workspaceFolders.length < 2) {
|
||||
return vscode.workspace.workspaceFolders[0];
|
||||
}
|
||||
|
||||
for (const folder of vscode.workspace.workspaceFolders) {
|
||||
try {
|
||||
await vscode.workspace.fs.stat(vscode.Uri.joinPath(folder.uri, 'src/vs/loader.js'));
|
||||
return folder;
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const getContentFromFilesystem: ContentGetter = async uri => {
|
||||
try {
|
||||
const rawContent = await vscode.workspace.fs.readFile(uri);
|
||||
return textDecoder.decode(rawContent);
|
||||
} catch (e) {
|
||||
console.warn(`Error providing tests for ${uri.fsPath}`, e);
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export class TestFile {
|
||||
public hasBeenRead = false;
|
||||
|
||||
constructor(
|
||||
public readonly uri: vscode.Uri,
|
||||
public readonly workspaceFolder: vscode.WorkspaceFolder
|
||||
) {}
|
||||
|
||||
public getId() {
|
||||
return this.uri.toString().toLowerCase();
|
||||
}
|
||||
|
||||
public getLabel() {
|
||||
return relative(join(this.workspaceFolder.uri.fsPath, 'src'), this.uri.fsPath);
|
||||
}
|
||||
|
||||
public async updateFromDisk(controller: vscode.TestController, item: vscode.TestItem) {
|
||||
try {
|
||||
const content = await getContentFromFilesystem(item.uri!);
|
||||
item.error = undefined;
|
||||
this.updateFromContents(controller, content, item);
|
||||
} catch (e) {
|
||||
item.error = (e as Error).stack;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes all tests in this file, `sourceReader` provided by the root.
|
||||
*/
|
||||
public updateFromContents(
|
||||
controller: vscode.TestController,
|
||||
content: string,
|
||||
file: vscode.TestItem
|
||||
) {
|
||||
try {
|
||||
const diagnostics: vscode.Diagnostic[] = [];
|
||||
const ast = ts.createSourceFile(
|
||||
this.uri.path.split('/').pop()!,
|
||||
content,
|
||||
ts.ScriptTarget.ESNext,
|
||||
false,
|
||||
ts.ScriptKind.TS
|
||||
);
|
||||
|
||||
const parents: { item: vscode.TestItem; children: vscode.TestItem[] }[] = [
|
||||
{ item: file, children: [] },
|
||||
];
|
||||
const traverse = (node: ts.Node) => {
|
||||
const parent = parents[parents.length - 1];
|
||||
const childData = extractTestFromNode(ast, node, itemData.get(parent.item)!);
|
||||
if (childData === Action.Skip) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (childData === Action.Recurse) {
|
||||
ts.forEachChild(node, traverse);
|
||||
return;
|
||||
}
|
||||
|
||||
const id = `${file.uri}/${childData.fullName}`.toLowerCase();
|
||||
|
||||
// Skip duplicated tests. They won't run correctly with the way
|
||||
// mocha reports them, and will error if we try to insert them.
|
||||
const existing = parent.children.find(c => c.id === id);
|
||||
if (existing) {
|
||||
const diagnostic = new vscode.Diagnostic(
|
||||
childData.range,
|
||||
'Duplicate tests cannot be run individually and will not be reported correctly by the test framework. Please rename them.',
|
||||
vscode.DiagnosticSeverity.Warning
|
||||
);
|
||||
|
||||
diagnostic.relatedInformation = [
|
||||
new vscode.DiagnosticRelatedInformation(
|
||||
new vscode.Location(existing.uri!, existing.range!),
|
||||
'First declared here'
|
||||
),
|
||||
];
|
||||
|
||||
diagnostics.push(diagnostic);
|
||||
return;
|
||||
}
|
||||
|
||||
const item = controller.createTestItem(id, childData.name, file.uri);
|
||||
itemData.set(item, childData);
|
||||
item.range = childData.range;
|
||||
parent.children.push(item);
|
||||
|
||||
if (childData instanceof TestSuite) {
|
||||
parents.push({ item: item, children: [] });
|
||||
ts.forEachChild(node, traverse);
|
||||
item.children.replace(parents.pop()!.children);
|
||||
}
|
||||
};
|
||||
|
||||
ts.forEachChild(ast, traverse);
|
||||
file.error = undefined;
|
||||
file.children.replace(parents[0].children);
|
||||
diagnosticCollection.set(this.uri, diagnostics.length ? diagnostics : undefined);
|
||||
this.hasBeenRead = true;
|
||||
} catch (e) {
|
||||
file.error = String((e as Error).stack || (e as Error).message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class TestConstruct {
|
||||
public fullName: string;
|
||||
|
||||
constructor(
|
||||
public readonly name: string,
|
||||
public readonly range: vscode.Range,
|
||||
parent?: TestConstruct
|
||||
) {
|
||||
this.fullName = parent ? `${parent.fullName} ${name}` : name;
|
||||
}
|
||||
}
|
||||
|
||||
export class TestSuite extends TestConstruct {}
|
||||
|
||||
export class TestCase extends TestConstruct {}
|
||||
|
||||
export type VSCodeTest = TestFile | TestSuite | TestCase;
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { RangeCoverageTracker } from './v8CoverageWrangling';
|
||||
|
||||
suite('v8CoverageWrangling', () => {
|
||||
suite('RangeCoverageTracker', () => {
|
||||
test('covers new range', () => {
|
||||
const rt = new RangeCoverageTracker();
|
||||
rt.cover(5, 10);
|
||||
assert.deepStrictEqual([...rt], [{ start: 5, end: 10, covered: true }]);
|
||||
});
|
||||
|
||||
test('non overlapping ranges', () => {
|
||||
const rt = new RangeCoverageTracker();
|
||||
rt.cover(5, 10);
|
||||
rt.cover(15, 20);
|
||||
rt.cover(12, 13);
|
||||
assert.deepStrictEqual(
|
||||
[...rt],
|
||||
[
|
||||
{ start: 5, end: 10, covered: true },
|
||||
{ start: 12, end: 13, covered: true },
|
||||
{ start: 15, end: 20, covered: true },
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('covers exact', () => {
|
||||
const rt = new RangeCoverageTracker();
|
||||
rt.uncovered(5, 10);
|
||||
rt.cover(5, 10);
|
||||
assert.deepStrictEqual([...rt], [{ start: 5, end: 10, covered: true }]);
|
||||
});
|
||||
|
||||
test('overlap at start', () => {
|
||||
const rt = new RangeCoverageTracker();
|
||||
rt.uncovered(5, 10);
|
||||
rt.cover(2, 7);
|
||||
assert.deepStrictEqual(
|
||||
[...rt],
|
||||
[
|
||||
{ start: 2, end: 7, covered: true },
|
||||
{ start: 7, end: 10, covered: false },
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('overlap at end', () => {
|
||||
const rt = new RangeCoverageTracker();
|
||||
rt.cover(5, 10);
|
||||
rt.uncovered(2, 7);
|
||||
assert.deepStrictEqual(
|
||||
[...rt],
|
||||
[
|
||||
{ start: 2, end: 5, covered: false },
|
||||
{ start: 5, end: 10, covered: true },
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('inner contained', () => {
|
||||
const rt = new RangeCoverageTracker();
|
||||
rt.cover(5, 10);
|
||||
rt.uncovered(2, 12);
|
||||
assert.deepStrictEqual(
|
||||
[...rt],
|
||||
[
|
||||
{ start: 2, end: 5, covered: false },
|
||||
{ start: 5, end: 10, covered: true },
|
||||
{ start: 10, end: 12, covered: false },
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('outer contained', () => {
|
||||
const rt = new RangeCoverageTracker();
|
||||
rt.uncovered(5, 10);
|
||||
rt.cover(7, 9);
|
||||
assert.deepStrictEqual(
|
||||
[...rt],
|
||||
[
|
||||
{ start: 5, end: 7, covered: false },
|
||||
{ start: 7, end: 9, covered: true },
|
||||
{ start: 9, end: 10, covered: false },
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('boundary touching', () => {
|
||||
const rt = new RangeCoverageTracker();
|
||||
rt.uncovered(5, 10);
|
||||
rt.cover(10, 15);
|
||||
rt.uncovered(15, 20);
|
||||
assert.deepStrictEqual(
|
||||
[...rt],
|
||||
[
|
||||
{ start: 5, end: 10, covered: false },
|
||||
{ start: 10, end: 15, covered: true },
|
||||
{ start: 15, end: 20, covered: false },
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
suite('initializeBlock', () => {
|
||||
test('simple tree', () => {
|
||||
const rt = RangeCoverageTracker.initializeBlocks([
|
||||
{
|
||||
functionName: 'outer',
|
||||
isBlockCoverage: true,
|
||||
ranges: [
|
||||
{ count: 1, startOffset: 5, endOffset: 30 },
|
||||
{ count: 1, startOffset: 8, endOffset: 10 },
|
||||
{ count: 0, startOffset: 15, endOffset: 20 },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
[...rt],
|
||||
[
|
||||
{ start: 5, end: 15, covered: true },
|
||||
{ start: 15, end: 20, covered: false },
|
||||
{ start: 20, end: 30, covered: true },
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('separate branches', () => {
|
||||
const rt = RangeCoverageTracker.initializeBlocks([
|
||||
{
|
||||
functionName: 'outer',
|
||||
isBlockCoverage: true,
|
||||
ranges: [
|
||||
{ count: 1, startOffset: 5, endOffset: 8 },
|
||||
{ count: 1, startOffset: 10, endOffset: 12 },
|
||||
{ count: 0, startOffset: 15, endOffset: 20 },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
[...rt],
|
||||
[
|
||||
{ start: 5, end: 8, covered: true },
|
||||
{ start: 10, end: 12, covered: true },
|
||||
{ start: 15, end: 20, covered: false },
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,211 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export interface ICoverageRange {
|
||||
start: number;
|
||||
end: number;
|
||||
covered: boolean;
|
||||
}
|
||||
|
||||
export interface IV8FunctionCoverage {
|
||||
functionName: string;
|
||||
isBlockCoverage: boolean;
|
||||
ranges: IV8CoverageRange[];
|
||||
}
|
||||
|
||||
export interface IV8CoverageRange {
|
||||
startOffset: number;
|
||||
endOffset: number;
|
||||
count: number;
|
||||
}
|
||||
|
||||
/** V8 Script coverage data */
|
||||
export interface IScriptCoverage {
|
||||
scriptId: string;
|
||||
url: string;
|
||||
// Script source added by the runner the first time the script is emitted.
|
||||
source?: string;
|
||||
functions: IV8FunctionCoverage[];
|
||||
}
|
||||
|
||||
export class RangeCoverageTracker implements Iterable<ICoverageRange> {
|
||||
/**
|
||||
* A noncontiguous, non-overlapping, ordered set of ranges and whether
|
||||
* that range has been covered.
|
||||
*/
|
||||
private ranges: readonly ICoverageRange[] = [];
|
||||
|
||||
/**
|
||||
* Adds a coverage tracker initialized for a function with {@link isBlockCoverage} set to true.
|
||||
*/
|
||||
public static initializeBlocks(fns: IV8FunctionCoverage[]) {
|
||||
const rt = new RangeCoverageTracker();
|
||||
|
||||
let start = 0;
|
||||
const stack: IV8CoverageRange[] = [];
|
||||
|
||||
// note: comes pre-sorted from V8
|
||||
for (const { ranges } of fns) {
|
||||
for (const range of ranges) {
|
||||
while (stack.length && stack[stack.length - 1].endOffset < range.startOffset) {
|
||||
const last = stack.pop()!;
|
||||
rt.setCovered(start, last.endOffset, last.count > 0);
|
||||
start = last.endOffset;
|
||||
}
|
||||
|
||||
if (range.startOffset > start && stack.length) {
|
||||
rt.setCovered(start, range.startOffset, !!stack[stack.length - 1].count);
|
||||
}
|
||||
|
||||
start = range.startOffset;
|
||||
stack.push(range);
|
||||
}
|
||||
}
|
||||
|
||||
while (stack.length) {
|
||||
const last = stack.pop()!;
|
||||
rt.setCovered(start, last.endOffset, last.count > 0);
|
||||
start = last.endOffset;
|
||||
}
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
/** Makes a copy of the range tracker. */
|
||||
public clone() {
|
||||
const rt = new RangeCoverageTracker();
|
||||
rt.ranges = this.ranges.slice();
|
||||
return rt;
|
||||
}
|
||||
|
||||
/** Marks a range covered */
|
||||
public cover(start: number, end: number) {
|
||||
this.setCovered(start, end, true);
|
||||
}
|
||||
|
||||
/** Marks a range as uncovered */
|
||||
public uncovered(start: number, end: number) {
|
||||
this.setCovered(start, end, false);
|
||||
}
|
||||
|
||||
/** Iterates over coverage ranges */
|
||||
[Symbol.iterator]() {
|
||||
return this.ranges[Symbol.iterator]();
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the given character range as being covered or uncovered.
|
||||
*
|
||||
* todo@connor4312: this is a hot path is could probably be optimized to
|
||||
* avoid rebuilding the array. Maybe with a nice tree structure?
|
||||
*/
|
||||
public setCovered(start: number, end: number, covered: boolean) {
|
||||
const newRanges: ICoverageRange[] = [];
|
||||
let i = 0;
|
||||
for (; i < this.ranges.length && this.ranges[i].end <= start; i++) {
|
||||
newRanges.push(this.ranges[i]);
|
||||
}
|
||||
|
||||
const push = (range: ICoverageRange) => {
|
||||
const last = newRanges.length && newRanges[newRanges.length - 1];
|
||||
if (last && last.end === range.start && last.covered === range.covered) {
|
||||
last.end = range.end;
|
||||
} else {
|
||||
newRanges.push(range);
|
||||
}
|
||||
};
|
||||
|
||||
push({ start, end, covered });
|
||||
|
||||
for (; i < this.ranges.length; i++) {
|
||||
const range = this.ranges[i];
|
||||
const last = newRanges[newRanges.length - 1];
|
||||
|
||||
if (range.start === last.start && range.end === last.end) {
|
||||
// ranges are equal:
|
||||
last.covered ||= range.covered;
|
||||
} else if (range.end < last.start || range.start > last.end) {
|
||||
// ranges don't overlap
|
||||
push(range);
|
||||
} else if (range.start < last.start && range.end > last.end) {
|
||||
// range contains last:
|
||||
newRanges.pop();
|
||||
push({ start: range.start, end: last.start, covered: range.covered });
|
||||
push({ start: last.start, end: last.end, covered: range.covered || last.covered });
|
||||
push({ start: last.end, end: range.end, covered: range.covered });
|
||||
} else if (range.start >= last.start && range.end <= last.end) {
|
||||
// last contains range:
|
||||
newRanges.pop();
|
||||
push({ start: last.start, end: range.start, covered: last.covered });
|
||||
push({ start: range.start, end: range.end, covered: range.covered || last.covered });
|
||||
push({ start: range.end, end: last.end, covered: last.covered });
|
||||
} else if (range.start < last.start && range.end <= last.end) {
|
||||
// range overlaps start of last:
|
||||
newRanges.pop();
|
||||
push({ start: range.start, end: last.start, covered: range.covered });
|
||||
push({ start: last.start, end: range.end, covered: range.covered || last.covered });
|
||||
push({ start: range.end, end: last.end, covered: last.covered });
|
||||
} else if (range.start >= last.start && range.end > last.end) {
|
||||
// range overlaps end of last:
|
||||
newRanges.pop();
|
||||
push({ start: last.start, end: range.start, covered: last.covered });
|
||||
push({ start: range.start, end: last.end, covered: range.covered || last.covered });
|
||||
push({ start: last.end, end: range.end, covered: range.covered });
|
||||
} else {
|
||||
throw new Error('unreachable');
|
||||
}
|
||||
}
|
||||
|
||||
this.ranges = newRanges;
|
||||
}
|
||||
}
|
||||
|
||||
export class OffsetToPosition {
|
||||
/** Line numbers to byte offsets. */
|
||||
public readonly lines: number[] = [];
|
||||
|
||||
public readonly totalLength: number;
|
||||
|
||||
constructor(source: string) {
|
||||
this.lines.push(0);
|
||||
for (let i = source.indexOf('\n'); i !== -1; i = source.indexOf('\n', i + 1)) {
|
||||
this.lines.push(i + 1);
|
||||
}
|
||||
this.totalLength = source.length;
|
||||
}
|
||||
|
||||
public getLineLength(lineNumber: number): number {
|
||||
return (
|
||||
(lineNumber < this.lines.length - 1 ? this.lines[lineNumber + 1] - 1 : this.totalLength) -
|
||||
this.lines[lineNumber]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line the offset appears on.
|
||||
*/
|
||||
public getLineOfOffset(offset: number): number {
|
||||
let low = 0;
|
||||
let high = this.lines.length;
|
||||
while (low < high) {
|
||||
const mid = Math.floor((low + high) / 2);
|
||||
if (this.lines[mid] > offset) {
|
||||
high = mid;
|
||||
} else {
|
||||
low = mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return low - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts from a file offset to a base 0 line/column .
|
||||
*/
|
||||
public toLineColumn(offset: number): { line: number; column: number } {
|
||||
const line = this.getLineOfOffset(offset);
|
||||
return { line: line, column: offset - this.lines[line] };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { spawn } from 'child_process';
|
||||
import { promises as fs } from 'fs';
|
||||
import { AddressInfo, createServer } from 'net';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { TestOutputScanner } from './testOutputScanner';
|
||||
import { TestCase, TestFile, TestSuite, itemData } from './testTree';
|
||||
|
||||
/**
|
||||
* From MDN
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping
|
||||
*/
|
||||
const escapeRe = (s: string) => s.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
|
||||
|
||||
const TEST_ELECTRON_SCRIPT_PATH = 'test/unit/electron/index.js';
|
||||
const TEST_BROWSER_SCRIPT_PATH = 'test/unit/browser/index.js';
|
||||
|
||||
const ATTACH_CONFIG_NAME = 'Attach to VS Code';
|
||||
const DEBUG_TYPE = 'pwa-chrome';
|
||||
|
||||
export abstract class VSCodeTestRunner {
|
||||
constructor(protected readonly repoLocation: vscode.WorkspaceFolder) { }
|
||||
|
||||
public async run(baseArgs: ReadonlyArray<string>, filter?: ReadonlyArray<vscode.TestItem>) {
|
||||
const args = this.prepareArguments(baseArgs, filter);
|
||||
const cp = spawn(await this.binaryPath(), args, {
|
||||
cwd: this.repoLocation.uri.fsPath,
|
||||
stdio: 'pipe',
|
||||
env: this.getEnvironment(),
|
||||
});
|
||||
|
||||
return new TestOutputScanner(cp, args);
|
||||
}
|
||||
|
||||
public async debug(testRun: vscode.TestRun, baseArgs: ReadonlyArray<string>, filter?: ReadonlyArray<vscode.TestItem>) {
|
||||
const port = await this.findOpenPort();
|
||||
const baseConfiguration = vscode.workspace
|
||||
.getConfiguration('launch', this.repoLocation)
|
||||
.get<vscode.DebugConfiguration[]>('configurations', [])
|
||||
.find(c => c.name === ATTACH_CONFIG_NAME);
|
||||
|
||||
if (!baseConfiguration) {
|
||||
throw new Error(`Could not find launch configuration ${ATTACH_CONFIG_NAME}`);
|
||||
}
|
||||
|
||||
const server = this.createWaitServer();
|
||||
const args = [
|
||||
...this.prepareArguments(baseArgs, filter),
|
||||
`--remote-debugging-port=${port}`,
|
||||
// for breakpoint freeze: https://github.com/microsoft/vscode/issues/122225#issuecomment-885377304
|
||||
'--js-flags="--regexp_interpret_all"',
|
||||
// for general runtime freezes: https://github.com/microsoft/vscode/issues/127861#issuecomment-904144910
|
||||
'--disable-features=CalculateNativeWinOcclusion',
|
||||
'--timeout=0',
|
||||
`--waitServer=${server.port}`,
|
||||
];
|
||||
|
||||
const cp = spawn(await this.binaryPath(), args, {
|
||||
cwd: this.repoLocation.uri.fsPath,
|
||||
stdio: 'pipe',
|
||||
env: this.getEnvironment(),
|
||||
});
|
||||
|
||||
// Register a descriptor factory that signals the server when any
|
||||
// breakpoint set requests on the debugee have been completed.
|
||||
const factory = vscode.debug.registerDebugAdapterTrackerFactory(DEBUG_TYPE, {
|
||||
createDebugAdapterTracker(session) {
|
||||
if (!session.parentSession || session.parentSession !== rootSession) {
|
||||
return;
|
||||
}
|
||||
|
||||
let initRequestId: number | undefined;
|
||||
|
||||
return {
|
||||
onDidSendMessage(message) {
|
||||
if (message.type === 'response' && message.request_seq === initRequestId) {
|
||||
server.ready();
|
||||
}
|
||||
},
|
||||
onWillReceiveMessage(message) {
|
||||
if (initRequestId !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.command === 'launch' || message.command === 'attach') {
|
||||
initRequestId = message.seq;
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
vscode.debug.startDebugging(this.repoLocation, { ...baseConfiguration, port }, { testRun });
|
||||
|
||||
let exited = false;
|
||||
let rootSession: vscode.DebugSession | undefined;
|
||||
cp.once('exit', () => {
|
||||
exited = true;
|
||||
server.dispose();
|
||||
listener.dispose();
|
||||
factory.dispose();
|
||||
|
||||
if (rootSession) {
|
||||
vscode.debug.stopDebugging(rootSession);
|
||||
}
|
||||
});
|
||||
|
||||
const listener = vscode.debug.onDidStartDebugSession(s => {
|
||||
if (s.name === ATTACH_CONFIG_NAME && !rootSession) {
|
||||
if (exited) {
|
||||
vscode.debug.stopDebugging(rootSession);
|
||||
} else {
|
||||
rootSession = s;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return new TestOutputScanner(cp, args);
|
||||
}
|
||||
|
||||
private findOpenPort(): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const server = createServer();
|
||||
server.listen(0, () => {
|
||||
const address = server.address() as AddressInfo;
|
||||
const port = address.port;
|
||||
server.close(() => {
|
||||
resolve(port);
|
||||
});
|
||||
});
|
||||
server.on('error', (error: Error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected getEnvironment(): NodeJS.ProcessEnv {
|
||||
return {
|
||||
...process.env,
|
||||
ELECTRON_RUN_AS_NODE: undefined,
|
||||
ELECTRON_ENABLE_LOGGING: '1',
|
||||
};
|
||||
}
|
||||
|
||||
private prepareArguments(
|
||||
baseArgs: ReadonlyArray<string>,
|
||||
filter?: ReadonlyArray<vscode.TestItem>
|
||||
) {
|
||||
const args = [...this.getDefaultArgs(), ...baseArgs, '--reporter', 'full-json-stream'];
|
||||
if (!filter) {
|
||||
return args;
|
||||
}
|
||||
|
||||
const grepRe: string[] = [];
|
||||
const runPaths = new Set<string>();
|
||||
const addTestFileRunPath = (data: TestFile) =>
|
||||
runPaths.add(
|
||||
path.relative(data.workspaceFolder.uri.fsPath, data.uri.fsPath).replace(/\\/g, '/')
|
||||
);
|
||||
|
||||
const itemDatas = filter.map(f => itemData.get(f));
|
||||
/** If true, we have to be careful with greps, as a grep for one test file affects the run of the other test file. */
|
||||
const hasBothTestCaseOrTestSuiteAndTestFileFilters =
|
||||
itemDatas.some(d => (d instanceof TestCase) || (d instanceof TestSuite)) &&
|
||||
itemDatas.some(d => d instanceof TestFile);
|
||||
|
||||
function addTestCaseOrSuite(data: TestCase | TestSuite, test: vscode.TestItem): void {
|
||||
grepRe.push(escapeRe(data.fullName) + (data instanceof TestCase ? '$' : ' '));
|
||||
for (let p = test.parent; p; p = p.parent) {
|
||||
const parentData = itemData.get(p);
|
||||
if (parentData instanceof TestFile) {
|
||||
addTestFileRunPath(parentData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const test of filter) {
|
||||
const data = itemData.get(test);
|
||||
if (data instanceof TestCase || data instanceof TestSuite) {
|
||||
addTestCaseOrSuite(data, test);
|
||||
} else if (data instanceof TestFile) {
|
||||
if (!hasBothTestCaseOrTestSuiteAndTestFileFilters) {
|
||||
addTestFileRunPath(data);
|
||||
} else {
|
||||
// We add all the items individually so they get their own grep expressions.
|
||||
for (const [_id, nestedTest] of test.children) {
|
||||
const childData = itemData.get(nestedTest);
|
||||
if (childData instanceof TestCase || childData instanceof TestSuite) {
|
||||
addTestCaseOrSuite(childData, nestedTest);
|
||||
} else {
|
||||
console.error('Unexpected test item in test file', nestedTest.id, nestedTest.label);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (grepRe.length) {
|
||||
args.push('--grep', `/^(${grepRe.join('|')})/`);
|
||||
}
|
||||
|
||||
if (runPaths.size) {
|
||||
args.push(...[...runPaths].flatMap(p => ['--run', p]));
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
protected abstract getDefaultArgs(): string[];
|
||||
|
||||
protected abstract binaryPath(): Promise<string>;
|
||||
|
||||
protected async readProductJson() {
|
||||
const projectJson = await fs.readFile(
|
||||
path.join(this.repoLocation.uri.fsPath, 'product.json'),
|
||||
'utf-8'
|
||||
);
|
||||
try {
|
||||
return JSON.parse(projectJson);
|
||||
} catch (e) {
|
||||
throw new Error(`Error parsing product.json: ${(e as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private createWaitServer() {
|
||||
const onReady = new vscode.EventEmitter<void>();
|
||||
let ready = false;
|
||||
|
||||
const server = createServer(socket => {
|
||||
if (ready) {
|
||||
socket.end();
|
||||
} else {
|
||||
onReady.event(() => socket.end());
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(0);
|
||||
|
||||
return {
|
||||
port: (server.address() as AddressInfo).port,
|
||||
ready: () => {
|
||||
ready = true;
|
||||
onReady.fire();
|
||||
},
|
||||
dispose: () => {
|
||||
server.close();
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class BrowserTestRunner extends VSCodeTestRunner {
|
||||
/** @override */
|
||||
protected binaryPath(): Promise<string> {
|
||||
return Promise.resolve(process.execPath);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
protected override getEnvironment() {
|
||||
return {
|
||||
...super.getEnvironment(),
|
||||
ELECTRON_RUN_AS_NODE: '1',
|
||||
};
|
||||
}
|
||||
|
||||
/** @override */
|
||||
protected getDefaultArgs() {
|
||||
return [TEST_BROWSER_SCRIPT_PATH];
|
||||
}
|
||||
}
|
||||
|
||||
export class WindowsTestRunner extends VSCodeTestRunner {
|
||||
/** @override */
|
||||
protected async binaryPath() {
|
||||
const { nameShort } = await this.readProductJson();
|
||||
return path.join(this.repoLocation.uri.fsPath, `.build/electron/${nameShort}.exe`);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
protected getDefaultArgs() {
|
||||
return [TEST_ELECTRON_SCRIPT_PATH];
|
||||
}
|
||||
}
|
||||
|
||||
export class PosixTestRunner extends VSCodeTestRunner {
|
||||
/** @override */
|
||||
protected async binaryPath() {
|
||||
const { applicationName } = await this.readProductJson();
|
||||
return path.join(this.repoLocation.uri.fsPath, `.build/electron/${applicationName}`);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
protected getDefaultArgs() {
|
||||
return [TEST_ELECTRON_SCRIPT_PATH];
|
||||
}
|
||||
}
|
||||
|
||||
export class DarwinTestRunner extends PosixTestRunner {
|
||||
/** @override */
|
||||
protected override getDefaultArgs() {
|
||||
return [
|
||||
TEST_ELECTRON_SCRIPT_PATH,
|
||||
'--no-sandbox',
|
||||
'--disable-dev-shm-usage',
|
||||
'--use-gl=swiftshader',
|
||||
];
|
||||
}
|
||||
|
||||
/** @override */
|
||||
protected override async binaryPath() {
|
||||
const { nameLong } = await this.readProductJson();
|
||||
return path.join(
|
||||
this.repoLocation.uri.fsPath,
|
||||
`.build/electron/${nameLong}.app/Contents/MacOS/Electron`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const PlatformTestRunner =
|
||||
process.platform === 'win32'
|
||||
? WindowsTestRunner
|
||||
: process.platform === 'darwin'
|
||||
? DarwinTestRunner
|
||||
: PosixTestRunner;
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "../../../extensions/tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out",
|
||||
"types": [
|
||||
"node",
|
||||
"mocha",
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"../../../src/vscode-dts/vscode.d.ts",
|
||||
"../../../src/vscode-dts/vscode.proposed.testObserver.d.ts",
|
||||
"../../../src/vscode-dts/vscode.proposed.testRelatedCode.d.ts",
|
||||
"../../../src/vscode-dts/vscode.proposed.attributableCoverage.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@jridgewell/resolve-uri@^3.1.0":
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
|
||||
integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
|
||||
|
||||
"@jridgewell/sourcemap-codec@^1.4.14":
|
||||
version "1.4.15"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
|
||||
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
|
||||
|
||||
"@jridgewell/trace-mapping@^0.3.25":
|
||||
version "0.3.25"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0"
|
||||
integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==
|
||||
dependencies:
|
||||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@types/istanbul-lib-coverage@^2.0.6":
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7"
|
||||
integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==
|
||||
|
||||
"@types/mocha@^10.0.6":
|
||||
version "10.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.6.tgz#818551d39113081048bdddbef96701b4e8bb9d1b"
|
||||
integrity sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==
|
||||
|
||||
"@types/node@20.x":
|
||||
version "20.12.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.11.tgz#c4ef00d3507000d17690643278a60dc55a9dc9be"
|
||||
integrity sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
ansi-styles@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
|
||||
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
|
||||
|
||||
cockatiel@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/cockatiel/-/cockatiel-3.1.3.tgz#bb1774a498a17e739dd994d56610dc6538b02858"
|
||||
integrity sha512-xC759TpZ69d7HhfDp8m2WkRwEUiCkxY8Ee2OQH/3H6zmy2D/5Sm+zSTbPRa+V2QyjDtpMvjOIAOVjA2gp6N1kQ==
|
||||
|
||||
istanbul-to-vscode@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/istanbul-to-vscode/-/istanbul-to-vscode-2.0.1.tgz#84994d06e604b68cac7301840f338b1e74eb888b"
|
||||
integrity sha512-V9Hhr7kX3UvkvkaT1lK3AmCRPkaIAIogQBrduTpNiLTkp1eVsybnJhWiDSVeCQap/3aGeZ2019oIivhX9MNsCQ==
|
||||
dependencies:
|
||||
"@types/istanbul-lib-coverage" "^2.0.6"
|
||||
|
||||
undici-types@~5.26.4:
|
||||
version "5.26.5"
|
||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
|
||||
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
|
||||
Vendored
+613
@@ -0,0 +1,613 @@
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Gulp Build",
|
||||
"program": "${workspaceFolder}/node_modules/gulp/bin/gulp.js",
|
||||
"stopOnEntry": true,
|
||||
"args": [
|
||||
"hygiene"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"restart": true,
|
||||
"name": "Attach to Extension Host",
|
||||
"timeout": 0,
|
||||
"port": 5870,
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js",
|
||||
"${workspaceFolder}/extensions/*/out/**/*.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"restart": true,
|
||||
"name": "Attach to Shared Process",
|
||||
"timeout": 0,
|
||||
"port": 5879,
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "Attach to Search Process",
|
||||
"port": 5876,
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "Attach to Pty Host Process",
|
||||
"port": 5877,
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "Attach to CLI Process",
|
||||
"port": 5874,
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "Attach to Main Process",
|
||||
"timeout": 30000,
|
||||
"port": 5875,
|
||||
"continueOnAttach": true,
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
],
|
||||
"presentation": {
|
||||
"hidden": true,
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"name": "VS Code Emmet Tests",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"${workspaceFolder}/extensions/emmet/test-workspace",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/extensions/emmet",
|
||||
"--extensionTestsPath=${workspaceFolder}/extensions/emmet/out/test"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "5_tests",
|
||||
"order": 6
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"name": "VS Code Configuration Editing Tests",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/extensions/configuration-editing",
|
||||
"--extensionTestsPath=${workspaceFolder}/extensions/configuration-editing/out/test"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "5_tests",
|
||||
"order": 6
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"name": "VS Code Git Tests",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"/tmp/my4g9l",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/extensions/git",
|
||||
"--extensionTestsPath=${workspaceFolder}/extensions/git/out/test"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/extensions/git/out/**/*.js"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "5_tests",
|
||||
"order": 6
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"name": "VS Code Github Tests",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"${workspaceFolder}/extensions/github/testWorkspace",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/extensions/github",
|
||||
"--extensionTestsPath=${workspaceFolder}/extensions/github/out/test"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/extensions/github/out/**/*.js"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "5_tests",
|
||||
"order": 6
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"name": "VS Code API Tests (single folder)",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
// "${workspaceFolder}", // Uncomment for running out of sources.
|
||||
"${workspaceFolder}/extensions/vscode-api-tests/testWorkspace",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/extensions/vscode-api-tests",
|
||||
"--extensionTestsPath=${workspaceFolder}/extensions/vscode-api-tests/out/singlefolder-tests",
|
||||
"--disable-extensions"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/extensions/vscode-api-tests/out/**/*.js"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "5_tests",
|
||||
"order": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"name": "VS Code API Tests (workspace)",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"${workspaceFolder}/extensions/vscode-api-tests/testworkspace.code-workspace",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/extensions/vscode-api-tests",
|
||||
"--extensionTestsPath=${workspaceFolder}/extensions/vscode-api-tests/out/workspace-tests"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/extensions/vscode-api-tests/out/**/*.js"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "5_tests",
|
||||
"order": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"name": "VS Code Tokenizer Tests",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"${workspaceFolder}/extensions/vscode-colorize-tests/test",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/extensions/vscode-colorize-tests",
|
||||
"--extensionTestsPath=${workspaceFolder}/extensions/vscode-colorize-tests/out"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "5_tests",
|
||||
"order": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "attach",
|
||||
"name": "Attach to VS Code",
|
||||
"browserAttachLocation": "workspace",
|
||||
"port": 9222,
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
],
|
||||
"resolveSourceMapLocations": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
],
|
||||
"perScriptSourcemaps": "yes"
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch VS Code Internal",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceFolder}/scripts/code.bat"
|
||||
},
|
||||
"osx": {
|
||||
"runtimeExecutable": "${workspaceFolder}/scripts/code.sh"
|
||||
},
|
||||
"linux": {
|
||||
"runtimeExecutable": "${workspaceFolder}/scripts/code.sh"
|
||||
},
|
||||
"port": 9222,
|
||||
"timeout": 0,
|
||||
"env": {
|
||||
"VSCODE_EXTHOST_WILL_SEND_SOCKET": null,
|
||||
"VSCODE_SKIP_PRELAUNCH": "1"
|
||||
},
|
||||
"cleanUp": "wholeBrowser",
|
||||
"runtimeArgs": [
|
||||
"--inspect-brk=5875",
|
||||
"--no-cached-data",
|
||||
"--crash-reporter-directory=${workspaceFolder}/.profile-oss/crashes",
|
||||
// for general runtime freezes: https://github.com/microsoft/vscode/issues/127861#issuecomment-904144910
|
||||
"--disable-features=CalculateNativeWinOcclusion",
|
||||
"--disable-extension=vscode.vscode-api-tests"
|
||||
],
|
||||
"userDataDir": "${userHome}/.vscode-oss-dev",
|
||||
"webRoot": "${workspaceFolder}",
|
||||
"cascadeTerminateToConfigurations": [
|
||||
"Attach to Extension Host"
|
||||
],
|
||||
"pauseForSourceMap": false,
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
],
|
||||
"browserLaunchLocation": "workspace",
|
||||
"presentation": {
|
||||
"hidden": true,
|
||||
},
|
||||
// This is read by the vscode-diagnostic-tools extension
|
||||
"vscode-diagnostic-tools.debuggerScripts": [
|
||||
"${workspaceFolder}/scripts/hot-reload-injected-script.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "VS Code Server (Web)",
|
||||
"runtimeExecutable": "${workspaceFolder}/scripts/code-server.sh",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceFolder}/scripts/code-server.bat",
|
||||
},
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "0_vscode",
|
||||
"order": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Main Process",
|
||||
"attachSimplePort": 5875,
|
||||
"enableContentValidation": false,
|
||||
"runtimeExecutable": "${workspaceFolder}/scripts/code.sh",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceFolder}/scripts/code.bat",
|
||||
},
|
||||
"runtimeArgs": [
|
||||
"--inspect-brk=5875",
|
||||
"--no-cached-data",
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "1_vscode",
|
||||
"order": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"outFiles": [],
|
||||
"perScriptSourcemaps": "yes",
|
||||
"name": "VS Code Server (Web, Chrome)",
|
||||
"url": "http://localhost:8080?tkn=dev-token",
|
||||
"preLaunchTask": "Run code server",
|
||||
"presentation": {
|
||||
"group": "0_vscode",
|
||||
"order": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "msedge",
|
||||
"request": "launch",
|
||||
"outFiles": [],
|
||||
"perScriptSourcemaps": "yes",
|
||||
"name": "VS Code Server (Web, Edge)",
|
||||
"url": "http://localhost:8080?tkn=dev-token",
|
||||
"pauseForSourceMap": false,
|
||||
"preLaunchTask": "Run code server",
|
||||
"presentation": {
|
||||
"group": "0_vscode",
|
||||
"order": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"outFiles": [],
|
||||
"perScriptSourcemaps": "yes",
|
||||
"name": "VS Code Web (Chrome)",
|
||||
"url": "http://localhost:8080",
|
||||
"preLaunchTask": "Run code web",
|
||||
"presentation": {
|
||||
"group": "0_vscode",
|
||||
"order": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "msedge",
|
||||
"request": "launch",
|
||||
"outFiles": [],
|
||||
"perScriptSourcemaps": "yes",
|
||||
"name": "VS Code Web (Edge)",
|
||||
"url": "http://localhost:8080",
|
||||
"pauseForSourceMap": false,
|
||||
"preLaunchTask": "Run code web",
|
||||
"presentation": {
|
||||
"group": "0_vscode",
|
||||
"order": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Git Unit Tests",
|
||||
"program": "${workspaceFolder}/extensions/git/node_modules/mocha/bin/_mocha",
|
||||
"stopOnEntry": false,
|
||||
"cwd": "${workspaceFolder}/extensions/git",
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/extensions/git/out/**/*.js"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "5_tests",
|
||||
"order": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "HTML Server Unit Tests",
|
||||
"program": "${workspaceFolder}/extensions/html-language-features/server/test/index.js",
|
||||
"stopOnEntry": false,
|
||||
"cwd": "${workspaceFolder}/extensions/html-language-features/server",
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/extensions/html-language-features/server/out/**/*.js"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "5_tests",
|
||||
"order": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "CSS Server Unit Tests",
|
||||
"program": "${workspaceFolder}/extensions/css-language-features/server/test/index.js",
|
||||
"stopOnEntry": false,
|
||||
"cwd": "${workspaceFolder}/extensions/css-language-features/server",
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/extensions/css-language-features/server/out/**/*.js"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "5_tests",
|
||||
"order": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"name": "Markdown Extension Tests",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"${workspaceFolder}/extensions/markdown-language-features/test-workspace",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/extensions/markdown-language-features",
|
||||
"--extensionTestsPath=${workspaceFolder}/extensions/markdown-language-features/out/test"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/extensions/markdown-language-features/out/**/*.js"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "5_tests",
|
||||
"order": 7
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"name": "TypeScript Extension Tests",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"${workspaceFolder}/extensions/typescript-language-features/test-workspace",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/extensions/typescript-language-features",
|
||||
"--extensionTestsPath=${workspaceFolder}/extensions/typescript-language-features/out/test"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/extensions/typescript-language-features/out/**/*.js"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "5_tests",
|
||||
"order": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Run Unit Tests",
|
||||
"program": "${workspaceFolder}/test/unit/electron/index.js",
|
||||
"runtimeExecutable": "${workspaceFolder}/.build/electron/Code - OSS.app/Contents/MacOS/Electron",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceFolder}/.build/electron/Code - OSS.exe"
|
||||
},
|
||||
"linux": {
|
||||
"runtimeExecutable": "${workspaceFolder}/.build/electron/code-oss"
|
||||
},
|
||||
"outputCapture": "std",
|
||||
"args": [
|
||||
"--remote-debugging-port=9222"
|
||||
],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
],
|
||||
"cascadeTerminateToConfigurations": [
|
||||
"Attach to VS Code"
|
||||
],
|
||||
"env": {
|
||||
"MOCHA_COLORS": "true"
|
||||
},
|
||||
"presentation": {
|
||||
"hidden": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Run Unit Tests For Current File",
|
||||
"program": "${workspaceFolder}/test/unit/electron/index.js",
|
||||
"runtimeExecutable": "${workspaceFolder}/.build/electron/Code - OSS.app/Contents/MacOS/Electron",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceFolder}/.build/electron/Code - OSS.exe"
|
||||
},
|
||||
"linux": {
|
||||
"runtimeExecutable": "${workspaceFolder}/.build/electron/code-oss"
|
||||
},
|
||||
"cascadeTerminateToConfigurations": [
|
||||
"Attach to VS Code"
|
||||
],
|
||||
"outputCapture": "std",
|
||||
"args": [
|
||||
"--remote-debugging-port=9222",
|
||||
"--run",
|
||||
"${relativeFile}"
|
||||
],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
],
|
||||
"env": {
|
||||
"MOCHA_COLORS": "true"
|
||||
},
|
||||
"presentation": {
|
||||
"hidden": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Smoke Test",
|
||||
"program": "${workspaceFolder}/test/smoke/test/index.js",
|
||||
"cwd": "${workspaceFolder}/test/smoke",
|
||||
"timeout": 240000,
|
||||
"args": [
|
||||
"-l",
|
||||
"${workspaceFolder}/.build/electron/Code - OSS.app/Contents/MacOS/Electron"
|
||||
],
|
||||
"outFiles": [
|
||||
"${cwd}/out/**/*.js"
|
||||
],
|
||||
"env": {
|
||||
"NODE_ENV": "development",
|
||||
"VSCODE_DEV": "1",
|
||||
"VSCODE_CLI": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Launch Built-in Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceRoot}/extensions/debug-auto-launch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Monaco Editor Playground",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:5001",
|
||||
"preLaunchTask": "Launch Http Server",
|
||||
"presentation": {
|
||||
"group": "monaco",
|
||||
"order": 4
|
||||
}
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "VS Code",
|
||||
"stopAll": true,
|
||||
"configurations": [
|
||||
"Launch VS Code Internal",
|
||||
"Attach to Main Process",
|
||||
"Attach to Extension Host",
|
||||
"Attach to Shared Process",
|
||||
],
|
||||
"preLaunchTask": "Ensure Prelaunch Dependencies",
|
||||
"presentation": {
|
||||
"group": "0_vscode",
|
||||
"order": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Search, Renderer, and Main processes",
|
||||
"configurations": [
|
||||
"Launch VS Code Internal",
|
||||
"Attach to Main Process",
|
||||
"Attach to Search Process"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "1_vscode",
|
||||
"order": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Renderer, Extension Host, and Main processes",
|
||||
"configurations": [
|
||||
"Launch VS Code Internal",
|
||||
"Attach to Main Process",
|
||||
"Attach to Extension Host"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "1_vscode",
|
||||
"order": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Debug Unit Tests",
|
||||
"configurations": [
|
||||
"Attach to VS Code",
|
||||
"Run Unit Tests"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "1_vscode",
|
||||
"order": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Debug Unit Tests (Current File)",
|
||||
"configurations": [
|
||||
"Attach to VS Code",
|
||||
"Run Unit Tests For Current File"
|
||||
],
|
||||
"presentation": {
|
||||
"group": "1_vscode",
|
||||
"order": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Renderer and Main processes",
|
||||
"stopAll": true,
|
||||
"configurations": [
|
||||
"Launch VS Code Internal",
|
||||
"Attach to Main Process"
|
||||
],
|
||||
"preLaunchTask": "Ensure Prelaunch Dependencies"
|
||||
},
|
||||
]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user