Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'
This commit is contained in:
18
lib/vscode/extensions/npm/.vscode/launch.json
vendored
Normal file
18
lib/vscode/extensions/npm/.vscode/launch.json
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}"
|
||||
],
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
"outFiles": ["${workspaceFolder}/client/out/**/*.js"],
|
||||
"preLaunchTask": "npm"
|
||||
}
|
||||
]
|
||||
}
|
11
lib/vscode/extensions/npm/.vscode/tasks.json
vendored
Normal file
11
lib/vscode/extensions/npm/.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"command": "npm",
|
||||
"type": "shell",
|
||||
"presentation": {
|
||||
"reveal": "silent",
|
||||
},
|
||||
"args": ["run", "compile"],
|
||||
"isBackground": true,
|
||||
"problemMatcher": "$tsc-watch"
|
||||
}
|
7
lib/vscode/extensions/npm/.vscodeignore
Normal file
7
lib/vscode/extensions/npm/.vscodeignore
Normal file
@ -0,0 +1,7 @@
|
||||
src/**
|
||||
out/**
|
||||
tsconfig.json
|
||||
.vscode/**
|
||||
extension.webpack.config.js
|
||||
extension-browser.webpack.config.js
|
||||
yarn.lock
|
44
lib/vscode/extensions/npm/README.md
Normal file
44
lib/vscode/extensions/npm/README.md
Normal file
@ -0,0 +1,44 @@
|
||||
# Node npm
|
||||
|
||||
**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled.
|
||||
|
||||
## Features
|
||||
|
||||
### Task Running
|
||||
|
||||
This extension supports running npm scripts defined in the `package.json` as [tasks](https://code.visualstudio.com/docs/editor/tasks). Scripts with the name 'build', 'compile', or 'watch'
|
||||
are treated as build tasks.
|
||||
|
||||
To run scripts as tasks, use the **Tasks** menu.
|
||||
|
||||
For more information about auto detection of Tasks, see the [documentation](https://code.visualstudio.com/Docs/editor/tasks#_task-autodetection).
|
||||
|
||||
### Script Explorer
|
||||
|
||||
The Npm Script Explorer shows the npm scripts found in your workspace. The explorer view is enabled by the setting `npm.enableScriptExplorer`. A script can be opened, run, or debug from the explorer.
|
||||
|
||||
### Run Scripts from the Editor
|
||||
|
||||
The extension supports to run the selected script as a task when editing the `package.json`file. You can either run a script from
|
||||
the hover shown on a script or using the command `Run Selected Npm Script`.
|
||||
|
||||
### Run Scripts from a Folder in the Explorer
|
||||
|
||||
The extension supports running a script as a task from a folder in the Explorer. The command `Run NPM Script in Folder...` shown in the Explorer context menu finds all scripts in `package.json` files that are contained in this folder. You can then select the script to be executed as a task from the resulting list. You enable this support with the `npm.runScriptFromFolder` which is `false` by default.
|
||||
|
||||
### Others
|
||||
|
||||
The extension fetches data from https://registry.npmjs.org and https://registry.bower.io to provide auto-completion and information on hover features on npm dependencies.
|
||||
|
||||
## Settings
|
||||
|
||||
- `npm.autoDetect` - Enable detecting scripts as tasks, the default is `on`.
|
||||
- `npm.runSilent` - Run npm script with the `--silent` option, the default is `false`.
|
||||
- `npm.packageManager` - The package manager used to run the scripts: `npm`, `yarn` or `pnpm`, the default is `npm`.
|
||||
- `npm.exclude` - Glob patterns for folders that should be excluded from automatic script detection. The pattern is matched against the **absolute path** of the package.json. For example, to exclude all test folders use '**/test/**'.
|
||||
- `npm.enableScriptExplorer` - Enable an explorer view for npm scripts.
|
||||
- `npm.scriptExplorerAction` - The default click action: `open` or `run`, the default is `open`.
|
||||
- `npm.enableRunFromFolder` - Enable running npm scripts from the context menu of folders in Explorer, the default is `false`.
|
||||
- `npm.scriptCodeLens.enable` - Enable/disable the code lenses to run a script, the default is `false`.
|
||||
|
||||
|
@ -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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
|
||||
'use strict';
|
||||
|
||||
const withBrowserDefaults = require('../shared.webpack.config').browser;
|
||||
|
||||
module.exports = withBrowserDefaults({
|
||||
context: __dirname,
|
||||
entry: {
|
||||
extension: './src/npmBrowserMain.ts'
|
||||
},
|
||||
output: {
|
||||
filename: 'npmBrowserMain.js'
|
||||
},
|
||||
node: {
|
||||
'child_process': 'empty'
|
||||
}
|
||||
});
|
26
lib/vscode/extensions/npm/extension.webpack.config.js
Normal file
26
lib/vscode/extensions/npm/extension.webpack.config.js
Normal file
@ -0,0 +1,26 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const withDefaults = require('../shared.webpack.config');
|
||||
|
||||
module.exports = withDefaults({
|
||||
context: __dirname,
|
||||
entry: {
|
||||
extension: './src/npmMain.ts',
|
||||
},
|
||||
output: {
|
||||
filename: 'npmMain.js',
|
||||
},
|
||||
resolve: {
|
||||
mainFields: ['module', 'main'],
|
||||
extensions: ['.ts', '.js'] // support ts-files and js-files
|
||||
}
|
||||
});
|
3
lib/vscode/extensions/npm/images/code.svg
Normal file
3
lib/vscode/extensions/npm/images/code.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.32798 5.00905L1.2384 8.09746L4.32798 11.1859L3.5016 12.0123L0 8.51065V7.68427L3.5016 4.18267L4.32798 5.00905ZM12.4984 4.18267L11.672 5.00905L14.7616 8.09746L11.672 11.1859L12.4984 12.0123L16 8.51065V7.68427L12.4984 4.18267ZM4.56142 13.672L5.6049 14.1949L11.4409 2.52291L10.3974 2L4.56142 13.672V13.672Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 434 B |
BIN
lib/vscode/extensions/npm/images/npm_icon.png
Normal file
BIN
lib/vscode/extensions/npm/images/npm_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 523 B |
302
lib/vscode/extensions/npm/package.json
Normal file
302
lib/vscode/extensions/npm/package.json
Normal file
@ -0,0 +1,302 @@
|
||||
{
|
||||
"name": "npm",
|
||||
"publisher": "vscode",
|
||||
"displayName": "%displayName%",
|
||||
"description": "%description%",
|
||||
"version": "1.0.1",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "0.10.x"
|
||||
},
|
||||
"enableProposedApi": true,
|
||||
"icon": "images/npm_icon.png",
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"scripts": {
|
||||
"compile": "gulp compile-extension:npm",
|
||||
"watch": "gulp watch-extension:npm"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonc-parser": "^2.2.1",
|
||||
"minimatch": "^3.0.4",
|
||||
"request-light": "^0.4.0",
|
||||
"vscode-nls": "^4.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/minimatch": "^3.0.3",
|
||||
"@types/node": "^12.11.7"
|
||||
},
|
||||
"main": "./out/npmMain",
|
||||
"browser": "./dist/browser/npmBrowserMain",
|
||||
"activationEvents": [
|
||||
"onCommand:workbench.action.tasks.runTask",
|
||||
"onCommand:npm.runScriptFromFolder",
|
||||
"onLanguage:json",
|
||||
"workspaceContains:package.json",
|
||||
"onView:npm"
|
||||
],
|
||||
"contributes": {
|
||||
"languages": [
|
||||
{
|
||||
"id": "ignore",
|
||||
"extensions": [
|
||||
".npmignore"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "properties",
|
||||
"extensions": [
|
||||
".npmrc"
|
||||
]
|
||||
}
|
||||
],
|
||||
"views": {
|
||||
"explorer": [
|
||||
{
|
||||
"id": "npm",
|
||||
"name": "%view.name%",
|
||||
"icon": "images/code.svg",
|
||||
"visibility": "hidden"
|
||||
}
|
||||
]
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"command": "npm.runScript",
|
||||
"title": "%command.run%",
|
||||
"icon": "$(run)"
|
||||
},
|
||||
{
|
||||
"command": "npm.debugScript",
|
||||
"title": "%command.debug%",
|
||||
"icon": "$(debug)"
|
||||
},
|
||||
{
|
||||
"command": "npm.openScript",
|
||||
"title": "%command.openScript%"
|
||||
},
|
||||
{
|
||||
"command": "npm.runInstall",
|
||||
"title": "%command.runInstall%"
|
||||
},
|
||||
{
|
||||
"command": "npm.refresh",
|
||||
"title": "%command.refresh%",
|
||||
"icon": "$(refresh)"
|
||||
},
|
||||
{
|
||||
"command": "npm.runSelectedScript",
|
||||
"title": "%command.runSelectedScript%"
|
||||
},
|
||||
{
|
||||
"command": "npm.runScriptFromFolder",
|
||||
"title": "%command.runScriptFromFolder%"
|
||||
},
|
||||
{
|
||||
"command": "npm.packageManager",
|
||||
"title": "%command.packageManager"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "npm.refresh",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "npm.runScript",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "npm.debugScript",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "npm.openScript",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "npm.runInstall",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "npm.runSelectedScript",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "npm.runScriptFromFolder",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "npm.packageManager",
|
||||
"when": "false"
|
||||
}
|
||||
],
|
||||
"editor/context": [
|
||||
{
|
||||
"command": "npm.runSelectedScript",
|
||||
"when": "resourceFilename == 'package.json' && resourceScheme == file",
|
||||
"group": "navigation@+1"
|
||||
}
|
||||
],
|
||||
"view/title": [
|
||||
{
|
||||
"command": "npm.refresh",
|
||||
"when": "view == npm",
|
||||
"group": "navigation"
|
||||
}
|
||||
],
|
||||
"view/item/context": [
|
||||
{
|
||||
"command": "npm.openScript",
|
||||
"when": "view == npm && viewItem == packageJSON",
|
||||
"group": "navigation@1"
|
||||
},
|
||||
{
|
||||
"command": "npm.runInstall",
|
||||
"when": "view == npm && viewItem == packageJSON",
|
||||
"group": "navigation@2"
|
||||
},
|
||||
{
|
||||
"command": "npm.openScript",
|
||||
"when": "view == npm && viewItem == script",
|
||||
"group": "navigation@1"
|
||||
},
|
||||
{
|
||||
"command": "npm.runScript",
|
||||
"when": "view == npm && viewItem == script",
|
||||
"group": "navigation@2"
|
||||
},
|
||||
{
|
||||
"command": "npm.runScript",
|
||||
"when": "view == npm && viewItem == script",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "npm.debugScript",
|
||||
"when": "view == npm && viewItem == script",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "npm.debugScript",
|
||||
"when": "view == npm && viewItem == script",
|
||||
"group": "navigation@3"
|
||||
}
|
||||
],
|
||||
"explorer/context": [
|
||||
{
|
||||
"when": "config.npm.enableRunFromFolder && explorerViewletVisible && explorerResourceIsFolder && resourceScheme == file",
|
||||
"command": "npm.runScriptFromFolder",
|
||||
"group": "2_workspace"
|
||||
}
|
||||
]
|
||||
},
|
||||
"configuration": {
|
||||
"id": "npm",
|
||||
"type": "object",
|
||||
"title": "Npm",
|
||||
"properties": {
|
||||
"npm.autoDetect": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"off",
|
||||
"on"
|
||||
],
|
||||
"default": "on",
|
||||
"scope": "resource",
|
||||
"description": "%config.npm.autoDetect%"
|
||||
},
|
||||
"npm.runSilent": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"scope": "resource",
|
||||
"markdownDescription": "%config.npm.runSilent%"
|
||||
},
|
||||
"npm.packageManager": {
|
||||
"scope": "resource",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"npm",
|
||||
"yarn",
|
||||
"pnpm"
|
||||
],
|
||||
"default": "npm",
|
||||
"description": "%config.npm.packageManager%"
|
||||
},
|
||||
"npm.exclude": {
|
||||
"type": [
|
||||
"string",
|
||||
"array"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "%config.npm.exclude%",
|
||||
"scope": "resource"
|
||||
},
|
||||
"npm.enableScriptExplorer": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"scope": "resource",
|
||||
"deprecationMessage": "The NPM Script Explorer is now available in 'Views' menu in the Explorer in all folders.",
|
||||
"description": "%config.npm.enableScriptExplorer%"
|
||||
},
|
||||
"npm.enableRunFromFolder": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"scope": "resource",
|
||||
"description": "%config.npm.enableRunFromFolder%"
|
||||
},
|
||||
"npm.scriptExplorerAction": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"open",
|
||||
"run"
|
||||
],
|
||||
"markdownDescription": "%config.npm.scriptExplorerAction%",
|
||||
"scope": "window",
|
||||
"default": "open"
|
||||
},
|
||||
"npm.fetchOnlinePackageInfo": {
|
||||
"type": "boolean",
|
||||
"description": "%config.npm.fetchOnlinePackageInfo%",
|
||||
"default": true,
|
||||
"scope": "window",
|
||||
"tags": [
|
||||
"usesOnlineServices"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"jsonValidation": [
|
||||
{
|
||||
"fileMatch": "package.json",
|
||||
"url": "https://json.schemastore.org/package"
|
||||
},
|
||||
{
|
||||
"fileMatch": "bower.json",
|
||||
"url": "https://json.schemastore.org/bower"
|
||||
}
|
||||
],
|
||||
"taskDefinitions": [
|
||||
{
|
||||
"type": "npm",
|
||||
"required": [
|
||||
"script"
|
||||
],
|
||||
"properties": {
|
||||
"script": {
|
||||
"type": "string",
|
||||
"description": "%taskdef.script%"
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "%taskdef.path%"
|
||||
}
|
||||
},
|
||||
"when": "shellExecutionSupported"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
24
lib/vscode/extensions/npm/package.nls.json
Normal file
24
lib/vscode/extensions/npm/package.nls.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"description": "Extension to add task support for npm scripts.",
|
||||
"displayName": "NPM support for VS Code",
|
||||
"config.npm.autoDetect": "Controls whether npm scripts should be automatically detected.",
|
||||
"config.npm.runSilent": "Run npm commands with the `--silent` option.",
|
||||
"config.npm.packageManager": "The package manager used to run scripts.",
|
||||
"config.npm.exclude": "Configure glob patterns for folders that should be excluded from automatic script detection.",
|
||||
"config.npm.enableScriptExplorer": "Enable an explorer view for npm scripts when there is no top-level 'package.json' file.",
|
||||
"config.npm.scriptExplorerAction": "The default click action used in the npm scripts explorer: `open` or `run`, the default is `open`.",
|
||||
"config.npm.enableRunFromFolder": "Enable running npm scripts contained in a folder from the Explorer context menu.",
|
||||
"config.npm.fetchOnlinePackageInfo": "Fetch data from https://registry.npmjs.org and https://registry.bower.io to provide auto-completion and information on hover features on npm dependencies.",
|
||||
"npm.parseError": "Npm task detection: failed to parse the file {0}",
|
||||
"taskdef.script": "The npm script to customize.",
|
||||
"taskdef.path": "The path to the folder of the package.json file that provides the script. Can be omitted.",
|
||||
"view.name": "NPM Scripts",
|
||||
"command.refresh": "Refresh",
|
||||
"command.run": "Run",
|
||||
"command.debug": "Debug",
|
||||
"command.openScript": "Open",
|
||||
"command.runInstall": "Run Install",
|
||||
"command.runSelectedScript": "Run Script",
|
||||
"command.runScriptFromFolder": "Run NPM Script in Folder...",
|
||||
"command.packageManager": "Get Configured Package Manager"
|
||||
}
|
68
lib/vscode/extensions/npm/src/commands.ts
Normal file
68
lib/vscode/extensions/npm/src/commands.ts
Normal file
@ -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 nls from 'vscode-nls';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import {
|
||||
detectNpmScriptsForFolder,
|
||||
findScriptAtPosition,
|
||||
runScript,
|
||||
FolderTaskItem
|
||||
} from './tasks';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export function runSelectedScript() {
|
||||
let editor = vscode.window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
let document = editor.document;
|
||||
let contents = document.getText();
|
||||
let selection = editor.selection;
|
||||
let offset = document.offsetAt(selection.anchor);
|
||||
|
||||
let script = findScriptAtPosition(contents, offset);
|
||||
if (script) {
|
||||
runScript(script, document);
|
||||
} else {
|
||||
let message = localize('noScriptFound', 'Could not find a valid npm script at the selection.');
|
||||
vscode.window.showErrorMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function selectAndRunScriptFromFolder(selectedFolder: vscode.Uri) {
|
||||
let taskList: FolderTaskItem[] = await detectNpmScriptsForFolder(selectedFolder);
|
||||
|
||||
if (taskList && taskList.length > 0) {
|
||||
const quickPick = vscode.window.createQuickPick<FolderTaskItem>();
|
||||
quickPick.title = 'Run NPM script in Folder';
|
||||
quickPick.placeholder = 'Select an npm script';
|
||||
quickPick.items = taskList;
|
||||
|
||||
const toDispose: vscode.Disposable[] = [];
|
||||
|
||||
let pickPromise = new Promise<FolderTaskItem | undefined>((c) => {
|
||||
toDispose.push(quickPick.onDidAccept(() => {
|
||||
toDispose.forEach(d => d.dispose());
|
||||
c(quickPick.selectedItems[0]);
|
||||
}));
|
||||
toDispose.push(quickPick.onDidHide(() => {
|
||||
toDispose.forEach(d => d.dispose());
|
||||
c(undefined);
|
||||
}));
|
||||
});
|
||||
quickPick.show();
|
||||
let result = await pickPromise;
|
||||
quickPick.dispose();
|
||||
if (result) {
|
||||
vscode.tasks.executeTask(result.task);
|
||||
}
|
||||
}
|
||||
else {
|
||||
vscode.window.showInformationMessage(`No npm scripts found in ${selectedFolder.fsPath}`, { modal: true });
|
||||
}
|
||||
}
|
204
lib/vscode/extensions/npm/src/features/bowerJSONContribution.ts
Normal file
204
lib/vscode/extensions/npm/src/features/bowerJSONContribution.ts
Normal file
@ -0,0 +1,204 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MarkdownString, CompletionItemKind, CompletionItem, DocumentSelector, SnippetString, workspace } from 'vscode';
|
||||
import { IJSONContribution, ISuggestionsCollector } from './jsonContributions';
|
||||
import { XHRRequest } from 'request-light';
|
||||
import { Location } from 'jsonc-parser';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
const USER_AGENT = 'Visual Studio Code';
|
||||
|
||||
export class BowerJSONContribution implements IJSONContribution {
|
||||
|
||||
private topRanked = ['twitter', 'bootstrap', 'angular-1.1.6', 'angular-latest', 'angulerjs', 'd3', 'myjquery', 'jq', 'abcdef1234567890', 'jQuery', 'jquery-1.11.1', 'jquery',
|
||||
'sushi-vanilla-x-data', 'font-awsome', 'Font-Awesome', 'font-awesome', 'fontawesome', 'html5-boilerplate', 'impress.js', 'homebrew',
|
||||
'backbone', 'moment1', 'momentjs', 'moment', 'linux', 'animate.css', 'animate-css', 'reveal.js', 'jquery-file-upload', 'blueimp-file-upload', 'threejs', 'express', 'chosen',
|
||||
'normalize-css', 'normalize.css', 'semantic', 'semantic-ui', 'Semantic-UI', 'modernizr', 'underscore', 'underscore1',
|
||||
'material-design-icons', 'ionic', 'chartjs', 'Chart.js', 'nnnick-chartjs', 'select2-ng', 'select2-dist', 'phantom', 'skrollr', 'scrollr', 'less.js', 'leancss', 'parser-lib',
|
||||
'hui', 'bootstrap-languages', 'async', 'gulp', 'jquery-pjax', 'coffeescript', 'hammer.js', 'ace', 'leaflet', 'jquery-mobile', 'sweetalert', 'typeahead.js', 'soup', 'typehead.js',
|
||||
'sails', 'codeigniter2'];
|
||||
|
||||
private xhr: XHRRequest;
|
||||
|
||||
public constructor(xhr: XHRRequest) {
|
||||
this.xhr = xhr;
|
||||
}
|
||||
|
||||
public getDocumentSelector(): DocumentSelector {
|
||||
return [{ language: 'json', scheme: '*', pattern: '**/bower.json' }, { language: 'json', scheme: '*', pattern: '**/.bower.json' }];
|
||||
}
|
||||
|
||||
private isEnabled() {
|
||||
return !!workspace.getConfiguration('npm').get('fetchOnlinePackageInfo');
|
||||
}
|
||||
|
||||
public collectDefaultSuggestions(_resource: string, collector: ISuggestionsCollector): Thenable<any> {
|
||||
const defaultValue = {
|
||||
'name': '${1:name}',
|
||||
'description': '${2:description}',
|
||||
'authors': ['${3:author}'],
|
||||
'version': '${4:1.0.0}',
|
||||
'main': '${5:pathToMain}',
|
||||
'dependencies': {}
|
||||
};
|
||||
const proposal = new CompletionItem(localize('json.bower.default', 'Default bower.json'));
|
||||
proposal.kind = CompletionItemKind.Class;
|
||||
proposal.insertText = new SnippetString(JSON.stringify(defaultValue, null, '\t'));
|
||||
collector.add(proposal);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
public collectPropertySuggestions(_resource: string, location: Location, currentWord: string, addValue: boolean, isLast: boolean, collector: ISuggestionsCollector): Thenable<any> | null {
|
||||
if (!this.isEnabled()) {
|
||||
return null;
|
||||
}
|
||||
if ((location.matches(['dependencies']) || location.matches(['devDependencies']))) {
|
||||
if (currentWord.length > 0) {
|
||||
const queryUrl = 'https://registry.bower.io/packages/search/' + encodeURIComponent(currentWord);
|
||||
|
||||
return this.xhr({
|
||||
url: queryUrl,
|
||||
agent: USER_AGENT
|
||||
}).then((success) => {
|
||||
if (success.status === 200) {
|
||||
try {
|
||||
const obj = JSON.parse(success.responseText);
|
||||
if (Array.isArray(obj)) {
|
||||
const results = <{ name: string; description: string; }[]>obj;
|
||||
for (const result of results) {
|
||||
const name = result.name;
|
||||
const description = result.description || '';
|
||||
const insertText = new SnippetString().appendText(JSON.stringify(name));
|
||||
if (addValue) {
|
||||
insertText.appendText(': ').appendPlaceholder('latest');
|
||||
if (!isLast) {
|
||||
insertText.appendText(',');
|
||||
}
|
||||
}
|
||||
const proposal = new CompletionItem(name);
|
||||
proposal.kind = CompletionItemKind.Property;
|
||||
proposal.insertText = insertText;
|
||||
proposal.filterText = JSON.stringify(name);
|
||||
proposal.documentation = description;
|
||||
collector.add(proposal);
|
||||
}
|
||||
collector.setAsIncomplete();
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
} else {
|
||||
collector.error(localize('json.bower.error.repoaccess', 'Request to the bower repository failed: {0}', success.responseText));
|
||||
return 0;
|
||||
}
|
||||
return undefined;
|
||||
}, (error) => {
|
||||
collector.error(localize('json.bower.error.repoaccess', 'Request to the bower repository failed: {0}', error.responseText));
|
||||
return 0;
|
||||
});
|
||||
} else {
|
||||
this.topRanked.forEach((name) => {
|
||||
const insertText = new SnippetString().appendText(JSON.stringify(name));
|
||||
if (addValue) {
|
||||
insertText.appendText(': ').appendPlaceholder('latest');
|
||||
if (!isLast) {
|
||||
insertText.appendText(',');
|
||||
}
|
||||
}
|
||||
|
||||
const proposal = new CompletionItem(name);
|
||||
proposal.kind = CompletionItemKind.Property;
|
||||
proposal.insertText = insertText;
|
||||
proposal.filterText = JSON.stringify(name);
|
||||
proposal.documentation = '';
|
||||
collector.add(proposal);
|
||||
});
|
||||
collector.setAsIncomplete();
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public collectValueSuggestions(_resource: string, location: Location, collector: ISuggestionsCollector): Promise<any> | null {
|
||||
if (!this.isEnabled()) {
|
||||
return null;
|
||||
}
|
||||
if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']))) {
|
||||
// not implemented. Could be do done calling the bower command. Waiting for web API: https://github.com/bower/registry/issues/26
|
||||
const proposal = new CompletionItem(localize('json.bower.latest.version', 'latest'));
|
||||
proposal.insertText = new SnippetString('"${1:latest}"');
|
||||
proposal.filterText = '""';
|
||||
proposal.kind = CompletionItemKind.Value;
|
||||
proposal.documentation = 'The latest version of the package';
|
||||
collector.add(proposal);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public resolveSuggestion(item: CompletionItem): Thenable<CompletionItem | null> | null {
|
||||
if (item.kind === CompletionItemKind.Property && item.documentation === '') {
|
||||
return this.getInfo(item.label).then(documentation => {
|
||||
if (documentation) {
|
||||
item.documentation = documentation;
|
||||
return item;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private getInfo(pack: string): Thenable<string | undefined> {
|
||||
const queryUrl = 'https://registry.bower.io/packages/' + encodeURIComponent(pack);
|
||||
|
||||
return this.xhr({
|
||||
url: queryUrl,
|
||||
agent: USER_AGENT
|
||||
}).then((success) => {
|
||||
try {
|
||||
const obj = JSON.parse(success.responseText);
|
||||
if (obj && obj.url) {
|
||||
let url: string = obj.url;
|
||||
if (url.indexOf('git://') === 0) {
|
||||
url = url.substring(6);
|
||||
}
|
||||
if (url.length >= 4 && url.substr(url.length - 4) === '.git') {
|
||||
url = url.substring(0, url.length - 4);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
return undefined;
|
||||
}, () => {
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
public getInfoContribution(_resource: string, location: Location): Thenable<MarkdownString[] | null> | null {
|
||||
if (!this.isEnabled()) {
|
||||
return null;
|
||||
}
|
||||
if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']))) {
|
||||
const pack = location.path[location.path.length - 1];
|
||||
if (typeof pack === 'string') {
|
||||
return this.getInfo(pack).then(documentation => {
|
||||
if (documentation) {
|
||||
const str = new MarkdownString();
|
||||
str.appendText(documentation);
|
||||
return [str];
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
172
lib/vscode/extensions/npm/src/features/jsonContributions.ts
Normal file
172
lib/vscode/extensions/npm/src/features/jsonContributions.ts
Normal file
@ -0,0 +1,172 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Location, getLocation, createScanner, SyntaxKind, ScanError, JSONScanner } from 'jsonc-parser';
|
||||
import { basename } from 'path';
|
||||
import { BowerJSONContribution } from './bowerJSONContribution';
|
||||
import { PackageJSONContribution } from './packageJSONContribution';
|
||||
import { XHRRequest } from 'request-light';
|
||||
|
||||
import {
|
||||
CompletionItem, CompletionItemProvider, CompletionList, TextDocument, Position, Hover, HoverProvider,
|
||||
CancellationToken, Range, MarkedString, DocumentSelector, languages, Disposable
|
||||
} from 'vscode';
|
||||
|
||||
export interface ISuggestionsCollector {
|
||||
add(suggestion: CompletionItem): void;
|
||||
error(message: string): void;
|
||||
log(message: string): void;
|
||||
setAsIncomplete(): void;
|
||||
}
|
||||
|
||||
export interface IJSONContribution {
|
||||
getDocumentSelector(): DocumentSelector;
|
||||
getInfoContribution(fileName: string, location: Location): Thenable<MarkedString[] | null> | null;
|
||||
collectPropertySuggestions(fileName: string, location: Location, currentWord: string, addValue: boolean, isLast: boolean, result: ISuggestionsCollector): Thenable<any> | null;
|
||||
collectValueSuggestions(fileName: string, location: Location, result: ISuggestionsCollector): Thenable<any> | null;
|
||||
collectDefaultSuggestions(fileName: string, result: ISuggestionsCollector): Thenable<any>;
|
||||
resolveSuggestion?(item: CompletionItem): Thenable<CompletionItem | null> | null;
|
||||
}
|
||||
|
||||
export function addJSONProviders(xhr: XHRRequest, canRunNPM: boolean): Disposable {
|
||||
const contributions = [new PackageJSONContribution(xhr, canRunNPM), new BowerJSONContribution(xhr)];
|
||||
const subscriptions: Disposable[] = [];
|
||||
contributions.forEach(contribution => {
|
||||
const selector = contribution.getDocumentSelector();
|
||||
subscriptions.push(languages.registerCompletionItemProvider(selector, new JSONCompletionItemProvider(contribution), '"', ':'));
|
||||
subscriptions.push(languages.registerHoverProvider(selector, new JSONHoverProvider(contribution)));
|
||||
});
|
||||
return Disposable.from(...subscriptions);
|
||||
}
|
||||
|
||||
export class JSONHoverProvider implements HoverProvider {
|
||||
|
||||
constructor(private jsonContribution: IJSONContribution) {
|
||||
}
|
||||
|
||||
public provideHover(document: TextDocument, position: Position, _token: CancellationToken): Thenable<Hover> | null {
|
||||
const fileName = basename(document.fileName);
|
||||
const offset = document.offsetAt(position);
|
||||
const location = getLocation(document.getText(), offset);
|
||||
if (!location.previousNode) {
|
||||
return null;
|
||||
}
|
||||
const node = location.previousNode;
|
||||
if (node && node.offset <= offset && offset <= node.offset + node.length) {
|
||||
const promise = this.jsonContribution.getInfoContribution(fileName, location);
|
||||
if (promise) {
|
||||
return promise.then(htmlContent => {
|
||||
const range = new Range(document.positionAt(node.offset), document.positionAt(node.offset + node.length));
|
||||
const result: Hover = {
|
||||
contents: htmlContent || [],
|
||||
range: range
|
||||
};
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class JSONCompletionItemProvider implements CompletionItemProvider {
|
||||
|
||||
constructor(private jsonContribution: IJSONContribution) {
|
||||
}
|
||||
|
||||
public resolveCompletionItem(item: CompletionItem, _token: CancellationToken): Thenable<CompletionItem | null> {
|
||||
if (this.jsonContribution.resolveSuggestion) {
|
||||
const resolver = this.jsonContribution.resolveSuggestion(item);
|
||||
if (resolver) {
|
||||
return resolver;
|
||||
}
|
||||
}
|
||||
return Promise.resolve(item);
|
||||
}
|
||||
|
||||
public provideCompletionItems(document: TextDocument, position: Position, _token: CancellationToken): Thenable<CompletionList | null> | null {
|
||||
|
||||
const fileName = basename(document.fileName);
|
||||
|
||||
const currentWord = this.getCurrentWord(document, position);
|
||||
let overwriteRange: Range;
|
||||
|
||||
const items: CompletionItem[] = [];
|
||||
let isIncomplete = false;
|
||||
|
||||
const offset = document.offsetAt(position);
|
||||
const location = getLocation(document.getText(), offset);
|
||||
|
||||
const node = location.previousNode;
|
||||
if (node && node.offset <= offset && offset <= node.offset + node.length && (node.type === 'property' || node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
|
||||
overwriteRange = new Range(document.positionAt(node.offset), document.positionAt(node.offset + node.length));
|
||||
} else {
|
||||
overwriteRange = new Range(document.positionAt(offset - currentWord.length), position);
|
||||
}
|
||||
|
||||
const proposed: { [key: string]: boolean } = {};
|
||||
const collector: ISuggestionsCollector = {
|
||||
add: (suggestion: CompletionItem) => {
|
||||
if (!proposed[suggestion.label]) {
|
||||
proposed[suggestion.label] = true;
|
||||
suggestion.range = { replacing: overwriteRange, inserting: new Range(overwriteRange.start, overwriteRange.start) };
|
||||
items.push(suggestion);
|
||||
}
|
||||
},
|
||||
setAsIncomplete: () => isIncomplete = true,
|
||||
error: (message: string) => console.error(message),
|
||||
log: (message: string) => console.log(message)
|
||||
};
|
||||
|
||||
let collectPromise: Thenable<any> | null = null;
|
||||
|
||||
if (location.isAtPropertyKey) {
|
||||
const scanner = createScanner(document.getText(), true);
|
||||
const addValue = !location.previousNode || !this.hasColonAfter(scanner, location.previousNode.offset + location.previousNode.length);
|
||||
const isLast = this.isLast(scanner, document.offsetAt(position));
|
||||
collectPromise = this.jsonContribution.collectPropertySuggestions(fileName, location, currentWord, addValue, isLast, collector);
|
||||
} else {
|
||||
if (location.path.length === 0) {
|
||||
collectPromise = this.jsonContribution.collectDefaultSuggestions(fileName, collector);
|
||||
} else {
|
||||
collectPromise = this.jsonContribution.collectValueSuggestions(fileName, location, collector);
|
||||
}
|
||||
}
|
||||
if (collectPromise) {
|
||||
return collectPromise.then(() => {
|
||||
if (items.length > 0) {
|
||||
return new CompletionList(items, isIncomplete);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private getCurrentWord(document: TextDocument, position: Position) {
|
||||
let i = position.character - 1;
|
||||
const text = document.lineAt(position.line).text;
|
||||
while (i >= 0 && ' \t\n\r\v":{[,'.indexOf(text.charAt(i)) === -1) {
|
||||
i--;
|
||||
}
|
||||
return text.substring(i + 1, position.character);
|
||||
}
|
||||
|
||||
private isLast(scanner: JSONScanner, offset: number): boolean {
|
||||
scanner.setPosition(offset);
|
||||
let nextToken = scanner.scan();
|
||||
if (nextToken === SyntaxKind.StringLiteral && scanner.getTokenError() === ScanError.UnexpectedEndOfString) {
|
||||
nextToken = scanner.scan();
|
||||
}
|
||||
return nextToken === SyntaxKind.CloseBraceToken || nextToken === SyntaxKind.EOF;
|
||||
}
|
||||
private hasColonAfter(scanner: JSONScanner, offset: number): boolean {
|
||||
scanner.setPosition(offset);
|
||||
return scanner.scan() === SyntaxKind.ColonToken;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const xhrDisabled = () => Promise.reject({ responseText: 'Use of online resources is disabled.' });
|
@ -0,0 +1,383 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MarkedString, CompletionItemKind, CompletionItem, DocumentSelector, SnippetString, workspace, MarkdownString } from 'vscode';
|
||||
import { IJSONContribution, ISuggestionsCollector } from './jsonContributions';
|
||||
import { XHRRequest } from 'request-light';
|
||||
import { Location } from 'jsonc-parser';
|
||||
|
||||
import * as cp from 'child_process';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
const LIMIT = 40;
|
||||
const SCOPED_LIMIT = 250;
|
||||
|
||||
const USER_AGENT = 'Visual Studio Code';
|
||||
|
||||
export class PackageJSONContribution implements IJSONContribution {
|
||||
|
||||
private mostDependedOn = ['lodash', 'async', 'underscore', 'request', 'commander', 'express', 'debug', 'chalk', 'colors', 'q', 'coffee-script',
|
||||
'mkdirp', 'optimist', 'through2', 'yeoman-generator', 'moment', 'bluebird', 'glob', 'gulp-util', 'minimist', 'cheerio', 'pug', 'redis', 'node-uuid',
|
||||
'socket', 'io', 'uglify-js', 'winston', 'through', 'fs-extra', 'handlebars', 'body-parser', 'rimraf', 'mime', 'semver', 'mongodb', 'jquery',
|
||||
'grunt', 'connect', 'yosay', 'underscore', 'string', 'xml2js', 'ejs', 'mongoose', 'marked', 'extend', 'mocha', 'superagent', 'js-yaml', 'xtend',
|
||||
'shelljs', 'gulp', 'yargs', 'browserify', 'minimatch', 'react', 'less', 'prompt', 'inquirer', 'ws', 'event-stream', 'inherits', 'mysql', 'esprima',
|
||||
'jsdom', 'stylus', 'when', 'readable-stream', 'aws-sdk', 'concat-stream', 'chai', 'Thenable', 'wrench'];
|
||||
|
||||
private knownScopes = ['@types', '@angular', '@babel', '@nuxtjs', '@vue', '@bazel'];
|
||||
|
||||
public getDocumentSelector(): DocumentSelector {
|
||||
return [{ language: 'json', scheme: '*', pattern: '**/package.json' }];
|
||||
}
|
||||
|
||||
public constructor(private xhr: XHRRequest, private canRunNPM: boolean) {
|
||||
}
|
||||
|
||||
public collectDefaultSuggestions(_fileName: string, result: ISuggestionsCollector): Thenable<any> {
|
||||
const defaultValue = {
|
||||
'name': '${1:name}',
|
||||
'description': '${2:description}',
|
||||
'authors': '${3:author}',
|
||||
'version': '${4:1.0.0}',
|
||||
'main': '${5:pathToMain}',
|
||||
'dependencies': {}
|
||||
};
|
||||
const proposal = new CompletionItem(localize('json.package.default', 'Default package.json'));
|
||||
proposal.kind = CompletionItemKind.Module;
|
||||
proposal.insertText = new SnippetString(JSON.stringify(defaultValue, null, '\t'));
|
||||
result.add(proposal);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
private isEnabled() {
|
||||
return this.canRunNPM || this.onlineEnabled();
|
||||
}
|
||||
|
||||
private onlineEnabled() {
|
||||
return !!workspace.getConfiguration('npm').get('fetchOnlinePackageInfo');
|
||||
}
|
||||
|
||||
public collectPropertySuggestions(
|
||||
_resource: string,
|
||||
location: Location,
|
||||
currentWord: string,
|
||||
addValue: boolean,
|
||||
isLast: boolean,
|
||||
collector: ISuggestionsCollector
|
||||
): Thenable<any> | null {
|
||||
if (!this.isEnabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((location.matches(['dependencies']) || location.matches(['devDependencies']) || location.matches(['optionalDependencies']) || location.matches(['peerDependencies']))) {
|
||||
let queryUrl: string;
|
||||
if (currentWord.length > 0) {
|
||||
if (currentWord[0] === '@') {
|
||||
if (currentWord.indexOf('/') !== -1) {
|
||||
return this.collectScopedPackages(currentWord, addValue, isLast, collector);
|
||||
}
|
||||
for (let scope of this.knownScopes) {
|
||||
const proposal = new CompletionItem(scope);
|
||||
proposal.kind = CompletionItemKind.Property;
|
||||
proposal.insertText = new SnippetString().appendText(`"${scope}/`).appendTabstop().appendText('"');
|
||||
proposal.filterText = JSON.stringify(scope);
|
||||
proposal.documentation = '';
|
||||
proposal.command = {
|
||||
title: '',
|
||||
command: 'editor.action.triggerSuggest'
|
||||
};
|
||||
collector.add(proposal);
|
||||
}
|
||||
collector.setAsIncomplete();
|
||||
}
|
||||
|
||||
queryUrl = `https://api.npms.io/v2/search/suggestions?size=${LIMIT}&q=${encodeURIComponent(currentWord)}`;
|
||||
return this.xhr({
|
||||
url: queryUrl,
|
||||
agent: USER_AGENT
|
||||
}).then((success) => {
|
||||
if (success.status === 200) {
|
||||
try {
|
||||
const obj = JSON.parse(success.responseText);
|
||||
if (obj && Array.isArray(obj)) {
|
||||
const results = <{ package: SearchPackageInfo; }[]>obj;
|
||||
for (const result of results) {
|
||||
this.processPackage(result.package, addValue, isLast, collector);
|
||||
}
|
||||
if (results.length === LIMIT) {
|
||||
collector.setAsIncomplete();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
} else {
|
||||
collector.error(localize('json.npm.error.repoaccess', 'Request to the NPM repository failed: {0}', success.responseText));
|
||||
return 0;
|
||||
}
|
||||
return undefined;
|
||||
}, (error) => {
|
||||
collector.error(localize('json.npm.error.repoaccess', 'Request to the NPM repository failed: {0}', error.responseText));
|
||||
return 0;
|
||||
});
|
||||
} else {
|
||||
this.mostDependedOn.forEach((name) => {
|
||||
const insertText = new SnippetString().appendText(JSON.stringify(name));
|
||||
if (addValue) {
|
||||
insertText.appendText(': "').appendTabstop().appendText('"');
|
||||
if (!isLast) {
|
||||
insertText.appendText(',');
|
||||
}
|
||||
}
|
||||
const proposal = new CompletionItem(name);
|
||||
proposal.kind = CompletionItemKind.Property;
|
||||
proposal.insertText = insertText;
|
||||
proposal.filterText = JSON.stringify(name);
|
||||
proposal.documentation = '';
|
||||
collector.add(proposal);
|
||||
});
|
||||
this.collectScopedPackages(currentWord, addValue, isLast, collector);
|
||||
collector.setAsIncomplete();
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private collectScopedPackages(currentWord: string, addValue: boolean, isLast: boolean, collector: ISuggestionsCollector): Thenable<any> {
|
||||
let segments = currentWord.split('/');
|
||||
if (segments.length === 2 && segments[0].length > 1) {
|
||||
let scope = segments[0].substr(1);
|
||||
let name = segments[1];
|
||||
if (name.length < 4) {
|
||||
name = '';
|
||||
}
|
||||
let queryUrl = `https://api.npms.io/v2/search?q=scope:${scope}%20${name}&size=250`;
|
||||
return this.xhr({
|
||||
url: queryUrl,
|
||||
agent: USER_AGENT
|
||||
}).then((success) => {
|
||||
if (success.status === 200) {
|
||||
try {
|
||||
const obj = JSON.parse(success.responseText);
|
||||
if (obj && Array.isArray(obj.results)) {
|
||||
const objects = <{ package: SearchPackageInfo }[]>obj.results;
|
||||
for (let object of objects) {
|
||||
this.processPackage(object.package, addValue, isLast, collector);
|
||||
}
|
||||
if (objects.length === SCOPED_LIMIT) {
|
||||
collector.setAsIncomplete();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
} else {
|
||||
collector.error(localize('json.npm.error.repoaccess', 'Request to the NPM repository failed: {0}', success.responseText));
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
public async collectValueSuggestions(_fileName: string, location: Location, result: ISuggestionsCollector): Promise<any> {
|
||||
if (!this.isEnabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']) || location.matches(['optionalDependencies', '*']) || location.matches(['peerDependencies', '*']))) {
|
||||
const currentKey = location.path[location.path.length - 1];
|
||||
if (typeof currentKey === 'string') {
|
||||
const info = await this.fetchPackageInfo(currentKey);
|
||||
if (info && info.version) {
|
||||
|
||||
let name = JSON.stringify(info.version);
|
||||
let proposal = new CompletionItem(name);
|
||||
proposal.kind = CompletionItemKind.Property;
|
||||
proposal.insertText = name;
|
||||
proposal.documentation = localize('json.npm.latestversion', 'The currently latest version of the package');
|
||||
result.add(proposal);
|
||||
|
||||
name = JSON.stringify('^' + info.version);
|
||||
proposal = new CompletionItem(name);
|
||||
proposal.kind = CompletionItemKind.Property;
|
||||
proposal.insertText = name;
|
||||
proposal.documentation = localize('json.npm.majorversion', 'Matches the most recent major version (1.x.x)');
|
||||
result.add(proposal);
|
||||
|
||||
name = JSON.stringify('~' + info.version);
|
||||
proposal = new CompletionItem(name);
|
||||
proposal.kind = CompletionItemKind.Property;
|
||||
proposal.insertText = name;
|
||||
proposal.documentation = localize('json.npm.minorversion', 'Matches the most recent minor version (1.2.x)');
|
||||
result.add(proposal);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private getDocumentation(description: string | undefined, version: string | undefined, homepage: string | undefined): MarkdownString {
|
||||
const str = new MarkdownString();
|
||||
if (description) {
|
||||
str.appendText(description);
|
||||
}
|
||||
if (version) {
|
||||
str.appendText('\n\n');
|
||||
str.appendText(localize('json.npm.version.hover', 'Latest version: {0}', version));
|
||||
}
|
||||
if (homepage) {
|
||||
str.appendText('\n\n');
|
||||
str.appendText(homepage);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
public resolveSuggestion(item: CompletionItem): Thenable<CompletionItem | null> | null {
|
||||
if (item.kind === CompletionItemKind.Property && !item.documentation) {
|
||||
return this.fetchPackageInfo(item.label).then(info => {
|
||||
if (info) {
|
||||
item.documentation = this.getDocumentation(info.description, info.version, info.homepage);
|
||||
return item;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private isValidNPMName(name: string): boolean {
|
||||
// following rules from https://github.com/npm/validate-npm-package-name
|
||||
if (!name || name.length > 214 || name.match(/^[_.]/)) {
|
||||
return false;
|
||||
}
|
||||
const match = name.match(/^(?:@([^/]+?)[/])?([^/]+?)$/);
|
||||
if (match) {
|
||||
const scope = match[1];
|
||||
if (scope && encodeURIComponent(scope) !== scope) {
|
||||
return false;
|
||||
}
|
||||
const name = match[2];
|
||||
return encodeURIComponent(name) === name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private async fetchPackageInfo(pack: string): Promise<ViewPackageInfo | undefined> {
|
||||
if (!this.isValidNPMName(pack)) {
|
||||
return undefined; // avoid unnecessary lookups
|
||||
}
|
||||
let info: ViewPackageInfo | undefined;
|
||||
if (this.canRunNPM) {
|
||||
info = await this.npmView(pack);
|
||||
}
|
||||
if (!info && this.onlineEnabled()) {
|
||||
info = await this.npmjsView(pack);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
private npmView(pack: string): Promise<ViewPackageInfo | undefined> {
|
||||
return new Promise((resolve, _reject) => {
|
||||
const args = ['view', '--json', pack, 'description', 'dist-tags.latest', 'homepage', 'version'];
|
||||
cp.execFile(process.platform === 'win32' ? 'npm.cmd' : 'npm', args, (error, stdout) => {
|
||||
if (!error) {
|
||||
try {
|
||||
const content = JSON.parse(stdout);
|
||||
resolve({
|
||||
description: content['description'],
|
||||
version: content['dist-tags.latest'] || content['version'],
|
||||
homepage: content['homepage']
|
||||
});
|
||||
return;
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
resolve(undefined);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async npmjsView(pack: string): Promise<ViewPackageInfo | undefined> {
|
||||
const queryUrl = 'https://api.npms.io/v2/package/' + encodeURIComponent(pack);
|
||||
try {
|
||||
const success = await this.xhr({
|
||||
url: queryUrl,
|
||||
agent: USER_AGENT
|
||||
});
|
||||
const obj = JSON.parse(success.responseText);
|
||||
const metadata = obj?.collected?.metadata;
|
||||
if (metadata) {
|
||||
return {
|
||||
description: metadata.description || '',
|
||||
version: metadata.version,
|
||||
homepage: metadata.links?.homepage || ''
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
//ignore
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public getInfoContribution(_fileName: string, location: Location): Thenable<MarkedString[] | null> | null {
|
||||
if (!this.isEnabled()) {
|
||||
return null;
|
||||
}
|
||||
if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']) || location.matches(['optionalDependencies', '*']) || location.matches(['peerDependencies', '*']))) {
|
||||
const pack = location.path[location.path.length - 1];
|
||||
if (typeof pack === 'string') {
|
||||
return this.fetchPackageInfo(pack).then(info => {
|
||||
if (info) {
|
||||
return [this.getDocumentation(info.description, info.version, info.homepage)];
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private processPackage(pack: SearchPackageInfo, addValue: boolean, isLast: boolean, collector: ISuggestionsCollector) {
|
||||
if (pack && pack.name) {
|
||||
const name = pack.name;
|
||||
const insertText = new SnippetString().appendText(JSON.stringify(name));
|
||||
if (addValue) {
|
||||
insertText.appendText(': "');
|
||||
if (pack.version) {
|
||||
insertText.appendVariable('version', pack.version);
|
||||
} else {
|
||||
insertText.appendTabstop();
|
||||
}
|
||||
insertText.appendText('"');
|
||||
if (!isLast) {
|
||||
insertText.appendText(',');
|
||||
}
|
||||
}
|
||||
const proposal = new CompletionItem(name);
|
||||
proposal.kind = CompletionItemKind.Property;
|
||||
proposal.insertText = insertText;
|
||||
proposal.filterText = JSON.stringify(name);
|
||||
proposal.documentation = this.getDocumentation(pack.description, pack.version, pack?.links?.homepage);
|
||||
collector.add(proposal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface SearchPackageInfo {
|
||||
name: string;
|
||||
description?: string;
|
||||
version?: string;
|
||||
links?: { homepage?: string; };
|
||||
}
|
||||
|
||||
interface ViewPackageInfo {
|
||||
description: string;
|
||||
version?: string;
|
||||
homepage?: string;
|
||||
}
|
15
lib/vscode/extensions/npm/src/npmBrowserMain.ts
Normal file
15
lib/vscode/extensions/npm/src/npmBrowserMain.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as httpRequest from 'request-light';
|
||||
import * as vscode from 'vscode';
|
||||
import { addJSONProviders } from './features/jsonContributions';
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext): Promise<void> {
|
||||
context.subscriptions.push(addJSONProviders(httpRequest.xhr, false));
|
||||
}
|
||||
|
||||
export function deactivate(): void {
|
||||
}
|
123
lib/vscode/extensions/npm/src/npmMain.ts
Normal file
123
lib/vscode/extensions/npm/src/npmMain.ts
Normal file
@ -0,0 +1,123 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as httpRequest from 'request-light';
|
||||
import * as vscode from 'vscode';
|
||||
import { addJSONProviders } from './features/jsonContributions';
|
||||
import { runSelectedScript, selectAndRunScriptFromFolder } from './commands';
|
||||
import { NpmScriptsTreeDataProvider } from './npmView';
|
||||
import { getPackageManager, invalidateTasksCache, NpmTaskProvider } from './tasks';
|
||||
import { invalidateHoverScriptsCache, NpmScriptHoverProvider } from './scriptHover';
|
||||
|
||||
let treeDataProvider: NpmScriptsTreeDataProvider | undefined;
|
||||
|
||||
function invalidateScriptCaches() {
|
||||
invalidateHoverScriptsCache();
|
||||
invalidateTasksCache();
|
||||
if (treeDataProvider) {
|
||||
treeDataProvider.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext): Promise<void> {
|
||||
configureHttpRequest();
|
||||
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('http.proxy') || e.affectsConfiguration('http.proxyStrictSSL')) {
|
||||
configureHttpRequest();
|
||||
}
|
||||
}));
|
||||
|
||||
const canRunNPM = canRunNpmInCurrentWorkspace();
|
||||
context.subscriptions.push(addJSONProviders(httpRequest.xhr, canRunNPM));
|
||||
|
||||
treeDataProvider = registerExplorer(context);
|
||||
|
||||
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration((e) => {
|
||||
if (e.affectsConfiguration('npm.exclude') || e.affectsConfiguration('npm.autoDetect')) {
|
||||
invalidateTasksCache();
|
||||
if (treeDataProvider) {
|
||||
treeDataProvider.refresh();
|
||||
}
|
||||
}
|
||||
if (e.affectsConfiguration('npm.scriptExplorerAction')) {
|
||||
if (treeDataProvider) {
|
||||
treeDataProvider.refresh();
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
registerTaskProvider(context);
|
||||
registerHoverProvider(context);
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('npm.runSelectedScript', runSelectedScript));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('npm.runScriptFromFolder', selectAndRunScriptFromFolder));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('npm.refresh', () => {
|
||||
invalidateScriptCaches();
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('npm.packageManager', (args) => {
|
||||
if (args instanceof vscode.Uri) {
|
||||
return getPackageManager(args);
|
||||
}
|
||||
return '';
|
||||
}));
|
||||
}
|
||||
|
||||
function canRunNpmInCurrentWorkspace() {
|
||||
if (vscode.workspace.workspaceFolders) {
|
||||
return vscode.workspace.workspaceFolders.some(f => f.uri.scheme === 'file');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function registerTaskProvider(context: vscode.ExtensionContext): vscode.Disposable | undefined {
|
||||
if (vscode.workspace.workspaceFolders) {
|
||||
let watcher = vscode.workspace.createFileSystemWatcher('**/package.json');
|
||||
watcher.onDidChange((_e) => invalidateScriptCaches());
|
||||
watcher.onDidDelete((_e) => invalidateScriptCaches());
|
||||
watcher.onDidCreate((_e) => invalidateScriptCaches());
|
||||
context.subscriptions.push(watcher);
|
||||
|
||||
let workspaceWatcher = vscode.workspace.onDidChangeWorkspaceFolders((_e) => invalidateScriptCaches());
|
||||
context.subscriptions.push(workspaceWatcher);
|
||||
|
||||
let provider: vscode.TaskProvider = new NpmTaskProvider();
|
||||
let disposable = vscode.tasks.registerTaskProvider('npm', provider);
|
||||
context.subscriptions.push(disposable);
|
||||
return disposable;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function registerExplorer(context: vscode.ExtensionContext): NpmScriptsTreeDataProvider | undefined {
|
||||
if (vscode.workspace.workspaceFolders) {
|
||||
let treeDataProvider = new NpmScriptsTreeDataProvider(context);
|
||||
const view = vscode.window.createTreeView('npm', { treeDataProvider: treeDataProvider, showCollapseAll: true });
|
||||
context.subscriptions.push(view);
|
||||
return treeDataProvider;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function registerHoverProvider(context: vscode.ExtensionContext): NpmScriptHoverProvider | undefined {
|
||||
if (vscode.workspace.workspaceFolders) {
|
||||
let npmSelector: vscode.DocumentSelector = {
|
||||
language: 'json',
|
||||
scheme: 'file',
|
||||
pattern: '**/package.json'
|
||||
};
|
||||
let provider = new NpmScriptHoverProvider(context);
|
||||
context.subscriptions.push(vscode.languages.registerHoverProvider(npmSelector, provider));
|
||||
return provider;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function configureHttpRequest() {
|
||||
const httpSettings = vscode.workspace.getConfiguration('http');
|
||||
httpRequest.configure(httpSettings.get<string>('proxy', ''), httpSettings.get<boolean>('proxyStrictSSL', true));
|
||||
}
|
||||
|
||||
export function deactivate(): void {
|
||||
}
|
300
lib/vscode/extensions/npm/src/npmView.ts
Normal file
300
lib/vscode/extensions/npm/src/npmView.ts
Normal file
@ -0,0 +1,300 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { JSONVisitor, visit } from 'jsonc-parser';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
commands, Event, EventEmitter, ExtensionContext,
|
||||
Selection, Task,
|
||||
TaskGroup, tasks, TextDocument, ThemeIcon, TreeDataProvider, TreeItem, TreeItemCollapsibleState, Uri,
|
||||
window, workspace, WorkspaceFolder
|
||||
} from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import {
|
||||
createTask, getTaskName, isAutoDetectionEnabled, isWorkspaceFolder, NpmTaskDefinition,
|
||||
startDebugging
|
||||
} from './tasks';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
class Folder extends TreeItem {
|
||||
packages: PackageJSON[] = [];
|
||||
workspaceFolder: WorkspaceFolder;
|
||||
|
||||
constructor(folder: WorkspaceFolder) {
|
||||
super(folder.name, TreeItemCollapsibleState.Expanded);
|
||||
this.contextValue = 'folder';
|
||||
this.resourceUri = folder.uri;
|
||||
this.workspaceFolder = folder;
|
||||
this.iconPath = ThemeIcon.Folder;
|
||||
}
|
||||
|
||||
addPackage(packageJson: PackageJSON) {
|
||||
this.packages.push(packageJson);
|
||||
}
|
||||
}
|
||||
|
||||
const packageName = 'package.json';
|
||||
|
||||
class PackageJSON extends TreeItem {
|
||||
path: string;
|
||||
folder: Folder;
|
||||
scripts: NpmScript[] = [];
|
||||
|
||||
static getLabel(_folderName: string, relativePath: string): string {
|
||||
if (relativePath.length > 0) {
|
||||
return path.join(relativePath, packageName);
|
||||
}
|
||||
return packageName;
|
||||
}
|
||||
|
||||
constructor(folder: Folder, relativePath: string) {
|
||||
super(PackageJSON.getLabel(folder.label!, relativePath), TreeItemCollapsibleState.Expanded);
|
||||
this.folder = folder;
|
||||
this.path = relativePath;
|
||||
this.contextValue = 'packageJSON';
|
||||
if (relativePath) {
|
||||
this.resourceUri = Uri.file(path.join(folder!.resourceUri!.fsPath, relativePath, packageName));
|
||||
} else {
|
||||
this.resourceUri = Uri.file(path.join(folder!.resourceUri!.fsPath, packageName));
|
||||
}
|
||||
this.iconPath = ThemeIcon.File;
|
||||
}
|
||||
|
||||
addScript(script: NpmScript) {
|
||||
this.scripts.push(script);
|
||||
}
|
||||
}
|
||||
|
||||
type ExplorerCommands = 'open' | 'run';
|
||||
|
||||
class NpmScript extends TreeItem {
|
||||
task: Task;
|
||||
package: PackageJSON;
|
||||
|
||||
constructor(_context: ExtensionContext, packageJson: PackageJSON, task: Task) {
|
||||
super(task.name, TreeItemCollapsibleState.None);
|
||||
const command: ExplorerCommands = workspace.getConfiguration('npm').get<ExplorerCommands>('scriptExplorerAction') || 'open';
|
||||
|
||||
const commandList = {
|
||||
'open': {
|
||||
title: 'Edit Script',
|
||||
command: 'npm.openScript',
|
||||
arguments: [this]
|
||||
},
|
||||
'run': {
|
||||
title: 'Run Script',
|
||||
command: 'npm.runScript',
|
||||
arguments: [this]
|
||||
}
|
||||
};
|
||||
this.contextValue = 'script';
|
||||
this.package = packageJson;
|
||||
this.task = task;
|
||||
this.command = commandList[command];
|
||||
|
||||
if (task.group && task.group === TaskGroup.Clean) {
|
||||
this.iconPath = new ThemeIcon('wrench-subaction');
|
||||
} else {
|
||||
this.iconPath = new ThemeIcon('wrench');
|
||||
}
|
||||
if (task.detail) {
|
||||
this.tooltip = task.detail;
|
||||
}
|
||||
}
|
||||
|
||||
getFolder(): WorkspaceFolder {
|
||||
return this.package.folder.workspaceFolder;
|
||||
}
|
||||
}
|
||||
|
||||
class NoScripts extends TreeItem {
|
||||
constructor(message: string) {
|
||||
super(message, TreeItemCollapsibleState.None);
|
||||
this.contextValue = 'noscripts';
|
||||
}
|
||||
}
|
||||
|
||||
export class NpmScriptsTreeDataProvider implements TreeDataProvider<TreeItem> {
|
||||
private taskTree: Folder[] | PackageJSON[] | NoScripts[] | null = null;
|
||||
private extensionContext: ExtensionContext;
|
||||
private _onDidChangeTreeData: EventEmitter<TreeItem | null> = new EventEmitter<TreeItem | null>();
|
||||
readonly onDidChangeTreeData: Event<TreeItem | null> = this._onDidChangeTreeData.event;
|
||||
|
||||
constructor(context: ExtensionContext) {
|
||||
const subscriptions = context.subscriptions;
|
||||
this.extensionContext = context;
|
||||
subscriptions.push(commands.registerCommand('npm.runScript', this.runScript, this));
|
||||
subscriptions.push(commands.registerCommand('npm.debugScript', this.debugScript, this));
|
||||
subscriptions.push(commands.registerCommand('npm.openScript', this.openScript, this));
|
||||
subscriptions.push(commands.registerCommand('npm.runInstall', this.runInstall, this));
|
||||
}
|
||||
|
||||
private async runScript(script: NpmScript) {
|
||||
tasks.executeTask(script.task);
|
||||
}
|
||||
|
||||
private async debugScript(script: NpmScript) {
|
||||
startDebugging(script.task.definition.script, path.dirname(script.package.resourceUri!.fsPath), script.getFolder());
|
||||
}
|
||||
|
||||
private findScript(document: TextDocument, script?: NpmScript): number {
|
||||
let scriptOffset = 0;
|
||||
let inScripts = false;
|
||||
|
||||
let visitor: JSONVisitor = {
|
||||
onError() {
|
||||
return scriptOffset;
|
||||
},
|
||||
onObjectEnd() {
|
||||
if (inScripts) {
|
||||
inScripts = false;
|
||||
}
|
||||
},
|
||||
onObjectProperty(property: string, offset: number, _length: number) {
|
||||
if (property === 'scripts') {
|
||||
inScripts = true;
|
||||
if (!script) { // select the script section
|
||||
scriptOffset = offset;
|
||||
}
|
||||
}
|
||||
else if (inScripts && script) {
|
||||
let label = getTaskName(property, script.task.definition.path);
|
||||
if (script.task.name === label) {
|
||||
scriptOffset = offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
visit(document.getText(), visitor);
|
||||
return scriptOffset;
|
||||
|
||||
}
|
||||
|
||||
private async runInstall(selection: PackageJSON) {
|
||||
let uri: Uri | undefined = undefined;
|
||||
if (selection instanceof PackageJSON) {
|
||||
uri = selection.resourceUri;
|
||||
}
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
let task = createTask('install', 'install', selection.folder.workspaceFolder, uri, undefined, []);
|
||||
tasks.executeTask(task);
|
||||
}
|
||||
|
||||
private async openScript(selection: PackageJSON | NpmScript) {
|
||||
let uri: Uri | undefined = undefined;
|
||||
if (selection instanceof PackageJSON) {
|
||||
uri = selection.resourceUri!;
|
||||
} else if (selection instanceof NpmScript) {
|
||||
uri = selection.package.resourceUri;
|
||||
}
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
let document: TextDocument = await workspace.openTextDocument(uri);
|
||||
let offset = this.findScript(document, selection instanceof NpmScript ? selection : undefined);
|
||||
let position = document.positionAt(offset);
|
||||
await window.showTextDocument(document, { preserveFocus: true, selection: new Selection(position, position) });
|
||||
}
|
||||
|
||||
public refresh() {
|
||||
this.taskTree = null;
|
||||
this._onDidChangeTreeData.fire(null);
|
||||
}
|
||||
|
||||
getTreeItem(element: TreeItem): TreeItem {
|
||||
return element;
|
||||
}
|
||||
|
||||
getParent(element: TreeItem): TreeItem | null {
|
||||
if (element instanceof Folder) {
|
||||
return null;
|
||||
}
|
||||
if (element instanceof PackageJSON) {
|
||||
return element.folder;
|
||||
}
|
||||
if (element instanceof NpmScript) {
|
||||
return element.package;
|
||||
}
|
||||
if (element instanceof NoScripts) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async getChildren(element?: TreeItem): Promise<TreeItem[]> {
|
||||
if (!this.taskTree) {
|
||||
let taskItems = await tasks.fetchTasks({ type: 'npm' });
|
||||
if (taskItems) {
|
||||
this.taskTree = this.buildTaskTree(taskItems);
|
||||
if (this.taskTree.length === 0) {
|
||||
let message = localize('noScripts', 'No scripts found.');
|
||||
if (!isAutoDetectionEnabled()) {
|
||||
message = localize('autoDetectIsOff', 'The setting "npm.autoDetect" is "off".');
|
||||
}
|
||||
this.taskTree = [new NoScripts(message)];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (element instanceof Folder) {
|
||||
return element.packages;
|
||||
}
|
||||
if (element instanceof PackageJSON) {
|
||||
return element.scripts;
|
||||
}
|
||||
if (element instanceof NpmScript) {
|
||||
return [];
|
||||
}
|
||||
if (element instanceof NoScripts) {
|
||||
return [];
|
||||
}
|
||||
if (!element) {
|
||||
if (this.taskTree) {
|
||||
return this.taskTree;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
private isInstallTask(task: Task): boolean {
|
||||
let fullName = getTaskName('install', task.definition.path);
|
||||
return fullName === task.name;
|
||||
}
|
||||
|
||||
private buildTaskTree(tasks: Task[]): Folder[] | PackageJSON[] | NoScripts[] {
|
||||
let folders: Map<String, Folder> = new Map();
|
||||
let packages: Map<String, PackageJSON> = new Map();
|
||||
|
||||
let folder = null;
|
||||
let packageJson = null;
|
||||
|
||||
tasks.forEach(each => {
|
||||
if (isWorkspaceFolder(each.scope) && !this.isInstallTask(each)) {
|
||||
folder = folders.get(each.scope.name);
|
||||
if (!folder) {
|
||||
folder = new Folder(each.scope);
|
||||
folders.set(each.scope.name, folder);
|
||||
}
|
||||
let definition: NpmTaskDefinition = <NpmTaskDefinition>each.definition;
|
||||
let relativePath = definition.path ? definition.path : '';
|
||||
let fullPath = path.join(each.scope.name, relativePath);
|
||||
packageJson = packages.get(fullPath);
|
||||
if (!packageJson) {
|
||||
packageJson = new PackageJSON(folder, relativePath);
|
||||
folder.addPackage(packageJson);
|
||||
packages.set(fullPath, packageJson);
|
||||
}
|
||||
let script = new NpmScript(this.extensionContext, packageJson, each);
|
||||
packageJson.addScript(script);
|
||||
}
|
||||
});
|
||||
if (folders.size === 1) {
|
||||
return [...packages.values()];
|
||||
}
|
||||
return [...folders.values()];
|
||||
}
|
||||
}
|
119
lib/vscode/extensions/npm/src/scriptHover.ts
Normal file
119
lib/vscode/extensions/npm/src/scriptHover.ts
Normal file
@ -0,0 +1,119 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import {
|
||||
ExtensionContext, TextDocument, commands, ProviderResult, CancellationToken,
|
||||
workspace, tasks, Range, HoverProvider, Hover, Position, MarkdownString, Uri
|
||||
} from 'vscode';
|
||||
import {
|
||||
createTask, startDebugging, findAllScriptRanges
|
||||
} from './tasks';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { dirname } from 'path';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
let cachedDocument: Uri | undefined = undefined;
|
||||
let cachedScriptsMap: Map<string, [number, number, string]> | undefined = undefined;
|
||||
|
||||
export function invalidateHoverScriptsCache(document?: TextDocument) {
|
||||
if (!document) {
|
||||
cachedDocument = undefined;
|
||||
return;
|
||||
}
|
||||
if (document.uri === cachedDocument) {
|
||||
cachedDocument = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export class NpmScriptHoverProvider implements HoverProvider {
|
||||
|
||||
constructor(context: ExtensionContext) {
|
||||
context.subscriptions.push(commands.registerCommand('npm.runScriptFromHover', this.runScriptFromHover, this));
|
||||
context.subscriptions.push(commands.registerCommand('npm.debugScriptFromHover', this.debugScriptFromHover, this));
|
||||
context.subscriptions.push(workspace.onDidChangeTextDocument((e) => {
|
||||
invalidateHoverScriptsCache(e.document);
|
||||
}));
|
||||
}
|
||||
|
||||
public provideHover(document: TextDocument, position: Position, _token: CancellationToken): ProviderResult<Hover> {
|
||||
let hover: Hover | undefined = undefined;
|
||||
|
||||
if (!cachedDocument || cachedDocument.fsPath !== document.uri.fsPath) {
|
||||
cachedScriptsMap = findAllScriptRanges(document.getText());
|
||||
cachedDocument = document.uri;
|
||||
}
|
||||
|
||||
cachedScriptsMap!.forEach((value, key) => {
|
||||
let start = document.positionAt(value[0]);
|
||||
let end = document.positionAt(value[0] + value[1]);
|
||||
let range = new Range(start, end);
|
||||
|
||||
if (range.contains(position)) {
|
||||
let contents: MarkdownString = new MarkdownString();
|
||||
contents.isTrusted = true;
|
||||
contents.appendMarkdown(this.createRunScriptMarkdown(key, document.uri));
|
||||
contents.appendMarkdown(this.createDebugScriptMarkdown(key, document.uri));
|
||||
hover = new Hover(contents);
|
||||
}
|
||||
});
|
||||
return hover;
|
||||
}
|
||||
|
||||
private createRunScriptMarkdown(script: string, documentUri: Uri): string {
|
||||
let args = {
|
||||
documentUri: documentUri,
|
||||
script: script,
|
||||
};
|
||||
return this.createMarkdownLink(
|
||||
localize('runScript', 'Run Script'),
|
||||
'npm.runScriptFromHover',
|
||||
args,
|
||||
localize('runScript.tooltip', 'Run the script as a task')
|
||||
);
|
||||
}
|
||||
|
||||
private createDebugScriptMarkdown(script: string, documentUri: Uri): string {
|
||||
const args = {
|
||||
documentUri: documentUri,
|
||||
script: script,
|
||||
};
|
||||
return this.createMarkdownLink(
|
||||
localize('debugScript', 'Debug Script'),
|
||||
'npm.debugScriptFromHover',
|
||||
args,
|
||||
localize('debugScript.tooltip', 'Runs the script under the debugger'),
|
||||
'|'
|
||||
);
|
||||
}
|
||||
|
||||
private createMarkdownLink(label: string, cmd: string, args: any, tooltip: string, separator?: string): string {
|
||||
let encodedArgs = encodeURIComponent(JSON.stringify(args));
|
||||
let prefix = '';
|
||||
if (separator) {
|
||||
prefix = ` ${separator} `;
|
||||
}
|
||||
return `${prefix}[${label}](command:${cmd}?${encodedArgs} "${tooltip}")`;
|
||||
}
|
||||
|
||||
public runScriptFromHover(args: any) {
|
||||
let script = args.script;
|
||||
let documentUri = args.documentUri;
|
||||
let folder = workspace.getWorkspaceFolder(documentUri);
|
||||
if (folder) {
|
||||
let task = createTask(script, `run ${script}`, folder, documentUri);
|
||||
tasks.executeTask(task);
|
||||
}
|
||||
}
|
||||
|
||||
public debugScriptFromHover(args: { script: string; documentUri: Uri }) {
|
||||
let script = args.script;
|
||||
let documentUri = args.documentUri;
|
||||
let folder = workspace.getWorkspaceFolder(documentUri);
|
||||
if (folder) {
|
||||
startDebugging(script, dirname(documentUri.fsPath), folder);
|
||||
}
|
||||
}
|
||||
}
|
515
lib/vscode/extensions/npm/src/tasks.ts
Normal file
515
lib/vscode/extensions/npm/src/tasks.ts
Normal file
@ -0,0 +1,515 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import {
|
||||
TaskDefinition, Task, TaskGroup, WorkspaceFolder, RelativePattern, ShellExecution, Uri, workspace,
|
||||
DebugConfiguration, debug, TaskProvider, TextDocument, tasks, TaskScope, QuickPickItem
|
||||
} from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as minimatch from 'minimatch';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { JSONVisitor, visit, ParseErrorCode } from 'jsonc-parser';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export interface NpmTaskDefinition extends TaskDefinition {
|
||||
script: string;
|
||||
path?: string;
|
||||
}
|
||||
|
||||
export interface FolderTaskItem extends QuickPickItem {
|
||||
label: string;
|
||||
task: Task;
|
||||
}
|
||||
|
||||
type AutoDetect = 'on' | 'off';
|
||||
|
||||
let cachedTasks: Task[] | undefined = undefined;
|
||||
|
||||
const INSTALL_SCRIPT = 'install';
|
||||
|
||||
export class NpmTaskProvider implements TaskProvider {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
public provideTasks() {
|
||||
return provideNpmScripts();
|
||||
}
|
||||
|
||||
public resolveTask(_task: Task): Task | undefined {
|
||||
const npmTask = (<any>_task.definition).script;
|
||||
if (npmTask) {
|
||||
const kind: NpmTaskDefinition = (<any>_task.definition);
|
||||
let packageJsonUri: Uri;
|
||||
if (_task.scope === undefined || _task.scope === TaskScope.Global || _task.scope === TaskScope.Workspace) {
|
||||
// scope is required to be a WorkspaceFolder for resolveTask
|
||||
return undefined;
|
||||
}
|
||||
if (kind.path) {
|
||||
packageJsonUri = _task.scope.uri.with({ path: _task.scope.uri.path + '/' + kind.path + 'package.json' });
|
||||
} else {
|
||||
packageJsonUri = _task.scope.uri.with({ path: _task.scope.uri.path + '/package.json' });
|
||||
}
|
||||
return createTask(kind, `${kind.script === INSTALL_SCRIPT ? '' : 'run '}${kind.script}`, _task.scope, packageJsonUri);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function invalidateTasksCache() {
|
||||
cachedTasks = undefined;
|
||||
}
|
||||
|
||||
const buildNames: string[] = ['build', 'compile', 'watch'];
|
||||
function isBuildTask(name: string): boolean {
|
||||
for (let buildName of buildNames) {
|
||||
if (name.indexOf(buildName) !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const testNames: string[] = ['test'];
|
||||
function isTestTask(name: string): boolean {
|
||||
for (let testName of testNames) {
|
||||
if (name === testName) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getPrePostScripts(scripts: any): Set<string> {
|
||||
const prePostScripts: Set<string> = new Set([
|
||||
'preuninstall', 'postuninstall', 'prepack', 'postpack', 'preinstall', 'postinstall',
|
||||
'prepack', 'postpack', 'prepublish', 'postpublish', 'preversion', 'postversion',
|
||||
'prestop', 'poststop', 'prerestart', 'postrestart', 'preshrinkwrap', 'postshrinkwrap',
|
||||
'pretest', 'postest', 'prepublishOnly'
|
||||
]);
|
||||
let keys = Object.keys(scripts);
|
||||
for (const script of keys) {
|
||||
const prepost = ['pre' + script, 'post' + script];
|
||||
prepost.forEach(each => {
|
||||
if (scripts[each] !== undefined) {
|
||||
prePostScripts.add(each);
|
||||
}
|
||||
});
|
||||
}
|
||||
return prePostScripts;
|
||||
}
|
||||
|
||||
export function isWorkspaceFolder(value: any): value is WorkspaceFolder {
|
||||
return value && typeof value !== 'number';
|
||||
}
|
||||
|
||||
export function getPackageManager(folder: Uri): string {
|
||||
return workspace.getConfiguration('npm', folder).get<string>('packageManager', 'npm');
|
||||
}
|
||||
|
||||
export async function hasNpmScripts(): Promise<boolean> {
|
||||
let folders = workspace.workspaceFolders;
|
||||
if (!folders) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
for (const folder of folders) {
|
||||
if (isAutoDetectionEnabled(folder)) {
|
||||
let relativePattern = new RelativePattern(folder, '**/package.json');
|
||||
let paths = await workspace.findFiles(relativePattern, '**/node_modules/**');
|
||||
if (paths.length > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function detectNpmScripts(): Promise<Task[]> {
|
||||
|
||||
let emptyTasks: Task[] = [];
|
||||
let allTasks: Task[] = [];
|
||||
let visitedPackageJsonFiles: Set<string> = new Set();
|
||||
|
||||
let folders = workspace.workspaceFolders;
|
||||
if (!folders) {
|
||||
return emptyTasks;
|
||||
}
|
||||
try {
|
||||
for (const folder of folders) {
|
||||
if (isAutoDetectionEnabled(folder)) {
|
||||
let relativePattern = new RelativePattern(folder, '**/package.json');
|
||||
let paths = await workspace.findFiles(relativePattern, '**/{node_modules,.vscode-test}/**');
|
||||
for (const path of paths) {
|
||||
if (!isExcluded(folder, path) && !visitedPackageJsonFiles.has(path.fsPath)) {
|
||||
let tasks = await provideNpmScriptsForFolder(path);
|
||||
visitedPackageJsonFiles.add(path.fsPath);
|
||||
allTasks.push(...tasks);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return allTasks;
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function detectNpmScriptsForFolder(folder: Uri): Promise<FolderTaskItem[]> {
|
||||
|
||||
let folderTasks: FolderTaskItem[] = [];
|
||||
|
||||
try {
|
||||
let relativePattern = new RelativePattern(folder.fsPath, '**/package.json');
|
||||
let paths = await workspace.findFiles(relativePattern, '**/node_modules/**');
|
||||
|
||||
let visitedPackageJsonFiles: Set<string> = new Set();
|
||||
for (const path of paths) {
|
||||
if (!visitedPackageJsonFiles.has(path.fsPath)) {
|
||||
let tasks = await provideNpmScriptsForFolder(path);
|
||||
visitedPackageJsonFiles.add(path.fsPath);
|
||||
folderTasks.push(...tasks.map(t => ({ label: t.name, task: t })));
|
||||
}
|
||||
}
|
||||
return folderTasks;
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function provideNpmScripts(): Promise<Task[]> {
|
||||
if (!cachedTasks) {
|
||||
cachedTasks = await detectNpmScripts();
|
||||
}
|
||||
return cachedTasks;
|
||||
}
|
||||
|
||||
export function isAutoDetectionEnabled(folder?: WorkspaceFolder): boolean {
|
||||
return workspace.getConfiguration('npm', folder?.uri).get<AutoDetect>('autoDetect') === 'on';
|
||||
}
|
||||
|
||||
function isExcluded(folder: WorkspaceFolder, packageJsonUri: Uri) {
|
||||
function testForExclusionPattern(path: string, pattern: string): boolean {
|
||||
return minimatch(path, pattern, { dot: true });
|
||||
}
|
||||
|
||||
let exclude = workspace.getConfiguration('npm', folder.uri).get<string | string[]>('exclude');
|
||||
let packageJsonFolder = path.dirname(packageJsonUri.fsPath);
|
||||
|
||||
if (exclude) {
|
||||
if (Array.isArray(exclude)) {
|
||||
for (let pattern of exclude) {
|
||||
if (testForExclusionPattern(packageJsonFolder, pattern)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (testForExclusionPattern(packageJsonFolder, exclude)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isDebugScript(script: string): boolean {
|
||||
let match = script.match(/--(inspect|debug)(-brk)?(=((\[[0-9a-fA-F:]*\]|[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[a-zA-Z0-9\.]*):)?(\d+))?/);
|
||||
return match !== null;
|
||||
}
|
||||
|
||||
async function provideNpmScriptsForFolder(packageJsonUri: Uri): Promise<Task[]> {
|
||||
let emptyTasks: Task[] = [];
|
||||
|
||||
let folder = workspace.getWorkspaceFolder(packageJsonUri);
|
||||
if (!folder) {
|
||||
return emptyTasks;
|
||||
}
|
||||
let scripts = await getScripts(packageJsonUri);
|
||||
if (!scripts) {
|
||||
return emptyTasks;
|
||||
}
|
||||
|
||||
const result: Task[] = [];
|
||||
|
||||
const prePostScripts = getPrePostScripts(scripts);
|
||||
Object.keys(scripts).forEach(each => {
|
||||
const task = createTask(each, `run ${each}`, folder!, packageJsonUri, scripts![each]);
|
||||
const lowerCaseTaskName = each.toLowerCase();
|
||||
if (isBuildTask(lowerCaseTaskName)) {
|
||||
task.group = TaskGroup.Build;
|
||||
} else if (isTestTask(lowerCaseTaskName)) {
|
||||
task.group = TaskGroup.Test;
|
||||
}
|
||||
if (prePostScripts.has(each)) {
|
||||
task.group = TaskGroup.Clean; // hack: use Clean group to tag pre/post scripts
|
||||
}
|
||||
|
||||
// todo@connor4312: all scripts are now debuggable, what is a 'debug script'?
|
||||
if (isDebugScript(scripts![each])) {
|
||||
task.group = TaskGroup.Rebuild; // hack: use Rebuild group to tag debug scripts
|
||||
}
|
||||
result.push(task);
|
||||
});
|
||||
// always add npm install (without a problem matcher)
|
||||
result.push(createTask(INSTALL_SCRIPT, INSTALL_SCRIPT, folder, packageJsonUri, 'install dependencies from package', []));
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getTaskName(script: string, relativePath: string | undefined) {
|
||||
if (relativePath && relativePath.length) {
|
||||
return `${script} - ${relativePath.substring(0, relativePath.length - 1)}`;
|
||||
}
|
||||
return script;
|
||||
}
|
||||
|
||||
export function createTask(script: NpmTaskDefinition | string, cmd: string, folder: WorkspaceFolder, packageJsonUri: Uri, detail?: string, matcher?: any): Task {
|
||||
let kind: NpmTaskDefinition;
|
||||
if (typeof script === 'string') {
|
||||
kind = { type: 'npm', script: script };
|
||||
} else {
|
||||
kind = script;
|
||||
}
|
||||
|
||||
function getCommandLine(folder: WorkspaceFolder, cmd: string): string {
|
||||
let packageManager = getPackageManager(folder.uri);
|
||||
if (workspace.getConfiguration('npm', folder.uri).get<boolean>('runSilent')) {
|
||||
return `${packageManager} --silent ${cmd}`;
|
||||
}
|
||||
return `${packageManager} ${cmd}`;
|
||||
}
|
||||
|
||||
function getRelativePath(folder: WorkspaceFolder, packageJsonUri: Uri): string {
|
||||
let rootUri = folder.uri;
|
||||
let absolutePath = packageJsonUri.path.substring(0, packageJsonUri.path.length - 'package.json'.length);
|
||||
return absolutePath.substring(rootUri.path.length + 1);
|
||||
}
|
||||
|
||||
let relativePackageJson = getRelativePath(folder, packageJsonUri);
|
||||
if (relativePackageJson.length) {
|
||||
kind.path = getRelativePath(folder, packageJsonUri);
|
||||
}
|
||||
let taskName = getTaskName(kind.script, relativePackageJson);
|
||||
let cwd = path.dirname(packageJsonUri.fsPath);
|
||||
const task = new Task(kind, folder, taskName, 'npm', new ShellExecution(getCommandLine(folder, cmd), { cwd: cwd }), matcher);
|
||||
task.detail = detail;
|
||||
return task;
|
||||
}
|
||||
|
||||
|
||||
export function getPackageJsonUriFromTask(task: Task): Uri | null {
|
||||
if (isWorkspaceFolder(task.scope)) {
|
||||
if (task.definition.path) {
|
||||
return Uri.file(path.join(task.scope.uri.fsPath, task.definition.path, 'package.json'));
|
||||
} else {
|
||||
return Uri.file(path.join(task.scope.uri.fsPath, 'package.json'));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function hasPackageJson(): Promise<boolean> {
|
||||
let folders = workspace.workspaceFolders;
|
||||
if (!folders) {
|
||||
return false;
|
||||
}
|
||||
for (const folder of folders) {
|
||||
if (folder.uri.scheme === 'file') {
|
||||
let packageJson = path.join(folder.uri.fsPath, 'package.json');
|
||||
if (await exists(packageJson)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function exists(file: string): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve, _reject) => {
|
||||
fs.exists(file, (value) => {
|
||||
resolve(value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function readFile(file: string): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
fs.readFile(file, (err, data) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
resolve(data.toString());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function runScript(script: string, document: TextDocument) {
|
||||
let uri = document.uri;
|
||||
let folder = workspace.getWorkspaceFolder(uri);
|
||||
if (folder) {
|
||||
let task = createTask(script, `run ${script}`, folder, uri);
|
||||
tasks.executeTask(task);
|
||||
}
|
||||
}
|
||||
|
||||
export function startDebugging(scriptName: string, cwd: string, folder: WorkspaceFolder) {
|
||||
const config: DebugConfiguration = {
|
||||
type: 'pwa-node',
|
||||
request: 'launch',
|
||||
name: `Debug ${scriptName}`,
|
||||
cwd,
|
||||
runtimeExecutable: getPackageManager(folder.uri),
|
||||
runtimeArgs: [
|
||||
'run',
|
||||
scriptName,
|
||||
],
|
||||
};
|
||||
|
||||
if (folder) {
|
||||
debug.startDebugging(folder, config);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export type StringMap = { [s: string]: string; };
|
||||
|
||||
async function findAllScripts(buffer: string): Promise<StringMap> {
|
||||
let scripts: StringMap = {};
|
||||
let script: string | undefined = undefined;
|
||||
let inScripts = false;
|
||||
|
||||
let visitor: JSONVisitor = {
|
||||
onError(_error: ParseErrorCode, _offset: number, _length: number) {
|
||||
console.log(_error);
|
||||
},
|
||||
onObjectEnd() {
|
||||
if (inScripts) {
|
||||
inScripts = false;
|
||||
}
|
||||
},
|
||||
onLiteralValue(value: any, _offset: number, _length: number) {
|
||||
if (script) {
|
||||
if (typeof value === 'string') {
|
||||
scripts[script] = value;
|
||||
}
|
||||
script = undefined;
|
||||
}
|
||||
},
|
||||
onObjectProperty(property: string, _offset: number, _length: number) {
|
||||
if (property === 'scripts') {
|
||||
inScripts = true;
|
||||
}
|
||||
else if (inScripts && !script) {
|
||||
script = property;
|
||||
} else { // nested object which is invalid, ignore the script
|
||||
script = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
visit(buffer, visitor);
|
||||
return scripts;
|
||||
}
|
||||
|
||||
export function findAllScriptRanges(buffer: string): Map<string, [number, number, string]> {
|
||||
let scripts: Map<string, [number, number, string]> = new Map();
|
||||
let script: string | undefined = undefined;
|
||||
let offset: number;
|
||||
let length: number;
|
||||
|
||||
let inScripts = false;
|
||||
|
||||
let visitor: JSONVisitor = {
|
||||
onError(_error: ParseErrorCode, _offset: number, _length: number) {
|
||||
},
|
||||
onObjectEnd() {
|
||||
if (inScripts) {
|
||||
inScripts = false;
|
||||
}
|
||||
},
|
||||
onLiteralValue(value: any, _offset: number, _length: number) {
|
||||
if (script) {
|
||||
scripts.set(script, [offset, length, value]);
|
||||
script = undefined;
|
||||
}
|
||||
},
|
||||
onObjectProperty(property: string, off: number, len: number) {
|
||||
if (property === 'scripts') {
|
||||
inScripts = true;
|
||||
}
|
||||
else if (inScripts) {
|
||||
script = property;
|
||||
offset = off;
|
||||
length = len;
|
||||
}
|
||||
}
|
||||
};
|
||||
visit(buffer, visitor);
|
||||
return scripts;
|
||||
}
|
||||
|
||||
export function findScriptAtPosition(buffer: string, offset: number): string | undefined {
|
||||
let script: string | undefined = undefined;
|
||||
let foundScript: string | undefined = undefined;
|
||||
let inScripts = false;
|
||||
let scriptStart: number | undefined;
|
||||
let visitor: JSONVisitor = {
|
||||
onError(_error: ParseErrorCode, _offset: number, _length: number) {
|
||||
},
|
||||
onObjectEnd() {
|
||||
if (inScripts) {
|
||||
inScripts = false;
|
||||
scriptStart = undefined;
|
||||
}
|
||||
},
|
||||
onLiteralValue(value: any, nodeOffset: number, nodeLength: number) {
|
||||
if (inScripts && scriptStart) {
|
||||
if (typeof value === 'string' && offset >= scriptStart && offset < nodeOffset + nodeLength) {
|
||||
// found the script
|
||||
inScripts = false;
|
||||
foundScript = script;
|
||||
} else {
|
||||
script = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
onObjectProperty(property: string, nodeOffset: number) {
|
||||
if (property === 'scripts') {
|
||||
inScripts = true;
|
||||
}
|
||||
else if (inScripts) {
|
||||
scriptStart = nodeOffset;
|
||||
script = property;
|
||||
} else { // nested object which is invalid, ignore the script
|
||||
script = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
visit(buffer, visitor);
|
||||
return foundScript;
|
||||
}
|
||||
|
||||
export async function getScripts(packageJsonUri: Uri): Promise<StringMap | undefined> {
|
||||
|
||||
if (packageJsonUri.scheme !== 'file') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let packageJson = packageJsonUri.fsPath;
|
||||
if (!await exists(packageJson)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
let contents = await readFile(packageJson);
|
||||
let json = findAllScripts(contents);//JSON.parse(contents);
|
||||
return json;
|
||||
} catch (e) {
|
||||
let localizedParseError = localize('npm.parseError', 'Npm task detection: failed to parse the file {0}', packageJsonUri.fsPath);
|
||||
throw new Error(localizedParseError);
|
||||
}
|
||||
}
|
8
lib/vscode/extensions/npm/src/typings/refs.d.ts
vendored
Normal file
8
lib/vscode/extensions/npm/src/typings/refs.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/// <reference path='../../../../src/vs/vscode.d.ts'/>
|
||||
/// <reference path='../../../../src/vs/vscode.proposed.d.ts'/>
|
||||
/// <reference types='@types/node'/>
|
9
lib/vscode/extensions/npm/tsconfig.json
Normal file
9
lib/vscode/extensions/npm/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../shared.tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
116
lib/vscode/extensions/npm/yarn.lock
Normal file
116
lib/vscode/extensions/npm/yarn.lock
Normal file
@ -0,0 +1,116 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@types/minimatch@^3.0.3":
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
|
||||
|
||||
"@types/node@^12.11.7":
|
||||
version "12.11.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a"
|
||||
integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA==
|
||||
|
||||
agent-base@4:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce"
|
||||
integrity sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg==
|
||||
dependencies:
|
||||
es6-promisify "^5.0.0"
|
||||
|
||||
agent-base@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
|
||||
integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
|
||||
dependencies:
|
||||
es6-promisify "^5.0.0"
|
||||
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
||||
|
||||
debug@3.1.0, debug@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
es6-promise@^4.0.3:
|
||||
version "4.2.4"
|
||||
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29"
|
||||
integrity sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==
|
||||
|
||||
es6-promisify@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
|
||||
integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
|
||||
dependencies:
|
||||
es6-promise "^4.0.3"
|
||||
|
||||
http-proxy-agent@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
|
||||
integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==
|
||||
dependencies:
|
||||
agent-base "4"
|
||||
debug "3.1.0"
|
||||
|
||||
https-proxy-agent@^2.2.4:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
|
||||
integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==
|
||||
dependencies:
|
||||
agent-base "^4.3.0"
|
||||
debug "^3.1.0"
|
||||
|
||||
jsonc-parser@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc"
|
||||
integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w==
|
||||
|
||||
minimatch@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||
|
||||
request-light@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.4.0.tgz#c6b91ef00b18cb0de75d2127e55b3a2c9f7f90f9"
|
||||
integrity sha512-fimzjIVw506FBZLspTAXHdpvgvQebyjpNyLRd0e6drPPRq7gcrROeGWRyF81wLqFg5ijPgnOQbmfck5wdTqpSA==
|
||||
dependencies:
|
||||
http-proxy-agent "^2.1.0"
|
||||
https-proxy-agent "^2.2.4"
|
||||
vscode-nls "^4.1.2"
|
||||
|
||||
vscode-nls@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c"
|
||||
integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A==
|
||||
|
||||
vscode-nls@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167"
|
||||
integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw==
|
Reference in New Issue
Block a user