Vscode merge (#4582)

* Merge from vscode 37cb23d3dd4f9433d56d4ba5ea3203580719a0bd

* fix issues with merges

* bump node version in azpipe

* replace license headers

* remove duplicate launch task

* fix build errors

* fix build errors

* fix tslint issues

* working through package and linux build issues

* more work

* wip

* fix packaged builds

* working through linux build errors

* wip

* wip

* wip

* fix mac and linux file limits

* iterate linux pipeline

* disable editor typing

* revert series to parallel

* remove optimize vscode from linux

* fix linting issues

* revert testing change

* add work round for new node

* readd packaging for extensions

* fix issue with angular not resolving decorator dependencies
This commit is contained in:
Anthony Dresser
2019-03-19 17:44:35 -07:00
committed by GitHub
parent 833d197412
commit 87765e8673
1879 changed files with 54505 additions and 38058 deletions

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import { dirname, basename } from 'path';
import { dirname, basename } from 'vs/base/common/path';
import * as objects from 'vs/base/common/objects';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';

View File

@@ -24,8 +24,8 @@ export class LineDecoder {
}
public write(buffer: Buffer): string[] {
let result: string[] = [];
let value = this.remaining
const result: string[] = [];
const value = this.remaining
? this.remaining + this.stringDecoder.write(buffer)
: this.stringDecoder.write(buffer);
@@ -41,7 +41,7 @@ export class LineDecoder {
result.push(value.substring(start, idx));
idx++;
if (idx < value.length) {
let lastChar = ch;
const lastChar = ch;
ch = value.charCodeAt(idx);
if ((lastChar === CharCode.CarriageReturn && ch === CharCode.LineFeed) || (lastChar === CharCode.LineFeed && ch === CharCode.CarriageReturn)) {
idx++;

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import * as paths from 'path';
import * as paths from 'vs/base/common/path';
import { nfcall } from 'vs/base/common/async';
import { normalizeNFC } from 'vs/base/common/normalization';
import * as platform from 'vs/base/common/platform';
@@ -219,7 +219,7 @@ export function del(path: string, tmpFolder: string, callback: (error: Error | n
}
function rmRecursive(path: string, callback: (error: Error | null) => void): void {
if (path === '\\' || path === '/') {
if (path === paths.win32.sep || path === paths.posix.sep) {
return callback(new Error('Will not delete root!'));
}
@@ -277,6 +277,10 @@ function rmRecursive(path: string, callback: (error: Error | null) => void): voi
}
export function delSync(path: string): void {
if (path === paths.win32.sep || path === paths.posix.sep) {
throw new Error('Will not delete root!');
}
try {
const stat = fs.lstatSync(path);
if (stat.isDirectory() && !stat.isSymbolicLink()) {

View File

@@ -10,8 +10,8 @@ import * as assert from 'assert';
* array to the callback (callback). The resulting errors and results are evaluated by calling the provided callback function.
*/
export function parallel<T, E>(list: T[], fn: (item: T, callback: (err: Error | null, result: E | null) => void) => void, callback: (err: Array<Error | null> | null, result: E[]) => void): void {
let results = new Array(list.length);
let errors = new Array<Error | null>(list.length);
const results = new Array(list.length);
const errors = new Array<Error | null>(list.length);
let didErrorOccur = false;
let doneCount = 0;
@@ -68,9 +68,9 @@ export function loop<E>(param: any, fn: (item: any, callback: (error: Error | nu
// Expect the param to be an array and loop over it
else {
let results: E[] = [];
const results: E[] = [];
let looper: (i: number) => void = function (i: number): void {
const looper: (i: number) => void = function (i: number): void {
// Still work to do
if (i < param.length) {
@@ -126,11 +126,11 @@ function Sequence(sequences: { (...param: any[]): void; }[]): void {
});
// Execute in Loop
let errorHandler = sequences.splice(0, 1)[0]; //Remove error handler
const errorHandler = sequences.splice(0, 1)[0]; //Remove error handler
let sequenceResult: any = null;
loop(sequences, (sequence, clb) => {
let sequenceFunction = function (error: any, result: any): void {
const sequenceFunction = function (error: any, result: any): void {
// A method might only send a boolean value as return value (e.g. fs.exists), support this case gracefully
if (error === true || error === false) {

View File

@@ -97,7 +97,7 @@ function getMacMachineId(): Promise<string> {
// TODO@sbatten: Remove this when getmac is patched
setTimeout(() => {
resolve(undefined);
}, 1000);
}, 10000);
} catch (err) {
errors.onUnexpectedError(err);
resolve(undefined);

23
src/vs/base/node/languagePacks.d.ts vendored Normal file
View File

@@ -0,0 +1,23 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface NLSConfiguration {
locale: string;
availableLanguages: {
[key: string]: string;
};
pseudo?: boolean;
}
export interface InternalNLSConfiguration extends NLSConfiguration {
_languagePackId: string;
_translationsConfigFile: string;
_cacheRoot: string;
_resolvedLanguagePackCoreLocation: string;
_corruptedFile: string;
_languagePackSupport?: boolean;
}
export function getNLSConfiguration(commit: string, userDataPath: string, metaDataFile: string, locale: string): Promise<NLSConfiguration>;

View File

@@ -0,0 +1,332 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
//@ts-check
/**
* @param {NodeRequire} nodeRequire
* @param {typeof import('path')} path
* @param {typeof import('fs')} fs
* @param {typeof import('../common/performance')} perf
*/
function factory(nodeRequire, path, fs, perf) {
/**
* @param {string} file
* @returns {Promise<boolean>}
*/
function exists(file) {
return new Promise(c => fs.exists(file, c));
}
/**
* @param {string} file
* @returns {Promise<void>}
*/
function touch(file) {
return new Promise((c, e) => { const d = new Date(); fs.utimes(file, d, d, err => err ? e(err) : c()); });
}
/**
* @param {string} file
* @returns {Promise<object>}
*/
function lstat(file) {
return new Promise((c, e) => fs.lstat(file, (err, stats) => err ? e(err) : c(stats)));
}
/**
* @param {string} dir
* @returns {Promise<string[]>}
*/
function readdir(dir) {
return new Promise((c, e) => fs.readdir(dir, (err, files) => err ? e(err) : c(files)));
}
/**
* @param {string} dir
* @returns {Promise<string>}
*/
function mkdir(dir) {
return new Promise((c, e) => fs.mkdir(dir, err => (err && err.code !== 'EEXIST') ? e(err) : c(dir)));
}
/**
* @param {string} dir
* @returns {Promise<void>}
*/
function rmdir(dir) {
return new Promise((c, e) => fs.rmdir(dir, err => err ? e(err) : c(undefined)));
}
/**
* @param {string} file
* @returns {Promise<void>}
*/
function unlink(file) {
return new Promise((c, e) => fs.unlink(file, err => err ? e(err) : c(undefined)));
}
/**
* @param {string} location
* @returns {Promise<void>}
*/
function rimraf(location) {
return lstat(location).then(stat => {
if (stat.isDirectory() && !stat.isSymbolicLink()) {
return readdir(location)
.then(children => Promise.all(children.map(child => rimraf(path.join(location, child)))))
.then(() => rmdir(location));
} else {
return unlink(location);
}
}, err => {
if (err.code === 'ENOENT') {
return undefined;
}
throw err;
});
}
/**
* @param {string} dir
* @returns {Promise<string>}
*/
function mkdirp(dir) {
return mkdir(dir).then(null, err => {
if (err && err.code === 'ENOENT') {
const parent = path.dirname(dir);
if (parent !== dir) { // if not arrived at root
return mkdirp(parent).then(() => mkdir(dir));
}
}
throw err;
});
}
function readFile(file) {
return new Promise(function (resolve, reject) {
fs.readFile(file, 'utf8', function (err, data) {
if (err) {
reject(err);
return;
}
resolve(data);
});
});
}
/**
* @param {string} file
* @param {string} content
* @returns {Promise<void>}
*/
function writeFile(file, content) {
return new Promise(function (resolve, reject) {
fs.writeFile(file, content, 'utf8', function (err) {
if (err) {
reject(err);
return;
}
resolve();
});
});
}
/**
* @param {string} userDataPath
* @returns {object}
*/
function getLanguagePackConfigurations(userDataPath) {
const configFile = path.join(userDataPath, 'languagepacks.json');
try {
return nodeRequire(configFile);
} catch (err) {
// Do nothing. If we can't read the file we have no
// language pack config.
}
return undefined;
}
/**
* @param {object} config
* @param {string} locale
*/
function resolveLanguagePackLocale(config, locale) {
try {
while (locale) {
if (config[locale]) {
return locale;
} else {
const index = locale.lastIndexOf('-');
if (index > 0) {
locale = locale.substring(0, index);
} else {
return undefined;
}
}
}
} catch (err) {
console.error('Resolving language pack configuration failed.', err);
}
return undefined;
}
/**
* @param {string} commit
* @param {string} userDataPath
* @param {string} metaDataFile
* @param {string} locale
*/
function getNLSConfiguration(commit, userDataPath, metaDataFile, locale) {
if (locale === 'pseudo') {
return Promise.resolve({ locale: locale, availableLanguages: {}, pseudo: true });
}
if (process.env['VSCODE_DEV']) {
return Promise.resolve({ locale: locale, availableLanguages: {} });
}
// We have a built version so we have extracted nls file. Try to find
// the right file to use.
// Check if we have an English or English US locale. If so fall to default since that is our
// English translation (we don't ship *.nls.en.json files)
if (locale && (locale === 'en' || locale === 'en-us')) {
return Promise.resolve({ locale: locale, availableLanguages: {} });
}
const initialLocale = locale;
perf.mark('nlsGeneration:start');
const defaultResult = function (locale) {
perf.mark('nlsGeneration:end');
return Promise.resolve({ locale: locale, availableLanguages: {} });
};
try {
if (!commit) {
return defaultResult(initialLocale);
}
const configs = getLanguagePackConfigurations(userDataPath);
if (!configs) {
return defaultResult(initialLocale);
}
locale = resolveLanguagePackLocale(configs, locale);
if (!locale) {
return defaultResult(initialLocale);
}
const packConfig = configs[locale];
let mainPack;
if (!packConfig || typeof packConfig.hash !== 'string' || !packConfig.translations || typeof (mainPack = packConfig.translations['vscode']) !== 'string') {
return defaultResult(initialLocale);
}
return exists(mainPack).then(fileExists => {
if (!fileExists) {
return defaultResult(initialLocale);
}
const packId = packConfig.hash + '.' + locale;
const cacheRoot = path.join(userDataPath, 'clp', packId);
const coreLocation = path.join(cacheRoot, commit);
const translationsConfigFile = path.join(cacheRoot, 'tcf.json');
const corruptedFile = path.join(cacheRoot, 'corrupted.info');
const result = {
locale: initialLocale,
availableLanguages: { '*': locale },
_languagePackId: packId,
_translationsConfigFile: translationsConfigFile,
_cacheRoot: cacheRoot,
_resolvedLanguagePackCoreLocation: coreLocation,
_corruptedFile: corruptedFile
};
return exists(corruptedFile).then(corrupted => {
// The nls cache directory is corrupted.
let toDelete;
if (corrupted) {
toDelete = rimraf(cacheRoot);
} else {
toDelete = Promise.resolve(undefined);
}
return toDelete.then(() => {
return exists(coreLocation).then(fileExists => {
if (fileExists) {
// We don't wait for this. No big harm if we can't touch
touch(coreLocation).catch(() => { });
perf.mark('nlsGeneration:end');
return result;
}
return mkdirp(coreLocation).then(() => {
return Promise.all([readFile(metaDataFile), readFile(mainPack)]);
}).then(values => {
const metadata = JSON.parse(values[0]);
const packData = JSON.parse(values[1]).contents;
const bundles = Object.keys(metadata.bundles);
const writes = [];
for (let bundle of bundles) {
const modules = metadata.bundles[bundle];
const target = Object.create(null);
for (let module of modules) {
const keys = metadata.keys[module];
const defaultMessages = metadata.messages[module];
const translations = packData[module];
let targetStrings;
if (translations) {
targetStrings = [];
for (let i = 0; i < keys.length; i++) {
const elem = keys[i];
const key = typeof elem === 'string' ? elem : elem.key;
let translatedMessage = translations[key];
if (translatedMessage === undefined) {
translatedMessage = defaultMessages[i];
}
targetStrings.push(translatedMessage);
}
} else {
targetStrings = defaultMessages;
}
target[module] = targetStrings;
}
writes.push(writeFile(path.join(coreLocation, bundle.replace(/\//g, '!') + '.nls.json'), JSON.stringify(target)));
}
writes.push(writeFile(translationsConfigFile, JSON.stringify(packConfig.translations)));
return Promise.all(writes);
}).then(() => {
perf.mark('nlsGeneration:end');
return result;
}).catch(err => {
console.error('Generating translation files failed.', err);
return defaultResult(locale);
});
});
});
});
});
} catch (err) {
console.error('Generating translation files failed.', err);
return defaultResult(locale);
}
}
return {
getNLSConfiguration
};
}
if (typeof define === 'function') {
// amd
define(['path', 'fs', 'vs/base/common/performance'], function (path, fs, perf) { return factory(require.__$__nodeRequire, path, fs, perf); });
} else if (typeof module === 'object' && typeof module.exports === 'object') {
const path = require('path');
const fs = require('fs');
const perf = require('../common/performance');
module.exports = factory(require, path, fs, perf);
} else {
throw new Error('Unknown context');
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as extfs from 'vs/base/node/extfs';
import { join } from 'path';
import { join } from 'vs/base/common/path';
import { nfcall, Queue } from 'vs/base/common/async';
import * as fs from 'fs';
import * as os from 'os';

View File

@@ -9,8 +9,8 @@ import * as net from 'net';
* @returns Returns a random port between 1025 and 65535.
*/
export function randomPort(): number {
let min = 1025;
let max = 65535;
const min = 1025;
const max = 65535;
return min + Math.floor((max - min) * Math.random());
}

View File

@@ -3,14 +3,14 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import * as path from 'vs/base/common/path';
import * as fs from 'fs';
import * as cp from 'child_process';
import * as nls from 'vs/nls';
import * as Types from 'vs/base/common/types';
import { IStringDictionary } from 'vs/base/common/collections';
import * as Objects from 'vs/base/common/objects';
import * as TPath from 'vs/base/common/paths';
import * as extpath from 'vs/base/common/extpath';
import * as Platform from 'vs/base/common/platform';
import { LineDecoder } from 'vs/base/node/decoder';
import { CommandOptions, ForkOptions, SuccessData, Source, TerminateResponse, TerminateResponseCode, Executable } from 'vs/base/common/processes';
@@ -42,7 +42,7 @@ function getWindowsCode(status: number): TerminateResponseCode {
export function terminateProcess(process: cp.ChildProcess, cwd?: string): TerminateResponse {
if (Platform.isWindows) {
try {
let options: any = {
const options: any = {
stdio: ['pipe', 'pipe', 'ignore']
};
if (cwd) {
@@ -54,8 +54,8 @@ export function terminateProcess(process: cp.ChildProcess, cwd?: string): Termin
}
} else if (Platform.isLinux || Platform.isMacintosh) {
try {
let cmd = getPathFromAmdModule(require, 'vs/base/node/terminateProcess.sh');
let result = cp.spawnSync(cmd, [process.pid.toString()]);
const cmd = getPathFromAmdModule(require, 'vs/base/node/terminateProcess.sh');
const result = cp.spawnSync(cmd, [process.pid.toString()]);
if (result.error) {
return { success: false, error: result.error };
}
@@ -72,26 +72,6 @@ export function getWindowsShell(): string {
return process.env['comspec'] || 'cmd.exe';
}
/**
* Sanitizes a VS Code process environment by removing all Electron/VS Code-related values.
*/
export function sanitizeProcessEnvironment(env: Platform.IProcessEnvironment): void {
const keysToRemove = [
/^ELECTRON_.+$/,
/^GOOGLE_API_KEY$/,
/^VSCODE_.+$/
];
const envKeys = Object.keys(env);
envKeys.forEach(envKey => {
for (let i = 0; i < keysToRemove.length; i++) {
if (envKey.search(keysToRemove[i]) !== -1) {
delete env[envKey];
break;
}
}
});
}
export abstract class AbstractProcess<TProgressData> {
private cmd: string;
private args: string[];
@@ -133,7 +113,7 @@ export abstract class AbstractProcess<TProgressData> {
this.shell = arg3;
this.options = arg4;
} else {
let executable = <Executable>arg1;
const executable = <Executable>arg1;
this.cmd = executable.command;
this.shell = executable.isShellCommand;
this.args = executable.args.slice(0);
@@ -144,7 +124,7 @@ export abstract class AbstractProcess<TProgressData> {
this.terminateRequested = false;
if (this.options.env) {
let newEnv: IStringDictionary<string> = Object.create(null);
const newEnv: IStringDictionary<string> = Object.create(null);
Object.keys(process.env).forEach((key) => {
newEnv[key] = process.env[key]!;
});
@@ -157,7 +137,7 @@ export abstract class AbstractProcess<TProgressData> {
public getSanitizedCommand(): string {
let result = this.cmd.toLowerCase();
let index = result.lastIndexOf(path.sep);
const index = result.lastIndexOf(path.sep);
if (index !== -1) {
result = result.substring(index + 1);
}
@@ -168,13 +148,13 @@ export abstract class AbstractProcess<TProgressData> {
}
public start(pp: ProgressCallback<TProgressData>): Promise<SuccessData> {
if (Platform.isWindows && ((this.options && this.options.cwd && TPath.isUNC(this.options.cwd)) || !this.options && TPath.isUNC(process.cwd()))) {
if (Platform.isWindows && ((this.options && this.options.cwd && extpath.isUNC(this.options.cwd)) || !this.options && extpath.isUNC(process.cwd()))) {
return Promise.reject(new Error(nls.localize('TaskRunner.UNC', 'Can\'t execute a shell command on a UNC drive.')));
}
return this.useExec().then((useExec) => {
let cc: ValueCallback<SuccessData>;
let ee: ErrorCallback;
let result = new Promise<any>((c, e) => {
const result = new Promise<any>((c, e) => {
cc = c;
ee = e;
});
@@ -186,7 +166,7 @@ export abstract class AbstractProcess<TProgressData> {
}
this.childProcess = cp.exec(cmd, this.options, (error, stdout, stderr) => {
this.childProcess = null;
let err: any = error;
const err: any = error;
// This is tricky since executing a command shell reports error back in case the executed command return an
// error or the command didn't exist at all. So we can't blindly treat an error as a failed command. So we
// always parse the output and report success unless the job got killed.
@@ -198,11 +178,11 @@ export abstract class AbstractProcess<TProgressData> {
});
} else {
let childProcess: cp.ChildProcess | null = null;
let closeHandler = (data: any) => {
const closeHandler = (data: any) => {
this.childProcess = null;
this.childProcessPromise = null;
this.handleClose(data, cc, pp, ee);
let result: SuccessData = {
const result: SuccessData = {
terminated: this.terminateRequested
};
if (Types.isNumber(data)) {
@@ -211,12 +191,12 @@ export abstract class AbstractProcess<TProgressData> {
cc(result);
};
if (this.shell && Platform.isWindows) {
let options: any = Objects.deepClone(this.options);
const options: any = Objects.deepClone(this.options);
options.windowsVerbatimArguments = true;
options.detached = false;
let quotedCommand: boolean = false;
let quotedArg: boolean = false;
let commandLine: string[] = [];
const commandLine: string[] = [];
let quoted = this.ensureQuotes(this.cmd);
commandLine.push(quoted.value);
quotedCommand = quoted.quoted;
@@ -227,7 +207,7 @@ export abstract class AbstractProcess<TProgressData> {
quotedArg = quotedArg && quoted.quoted;
});
}
let args: string[] = [
const args: string[] = [
'/s',
'/c',
];
@@ -307,7 +287,7 @@ export abstract class AbstractProcess<TProgressData> {
}
return this.childProcessPromise.then((childProcess) => {
this.terminateRequested = true;
let result = terminateProcess(childProcess, this.options.cwd);
const result = terminateProcess(childProcess, this.options.cwd);
if (result.success) {
this.childProcess = null;
}
@@ -320,14 +300,14 @@ export abstract class AbstractProcess<TProgressData> {
private useExec(): Promise<boolean> {
return new Promise<boolean>((c, e) => {
if (!this.shell || !Platform.isWindows) {
c(false);
return c(false);
}
let cmdShell = cp.spawn(getWindowsShell(), ['/s', '/c']);
const cmdShell = cp.spawn(getWindowsShell(), ['/s', '/c']);
cmdShell.on('error', (error: Error) => {
c(true);
return c(true);
});
cmdShell.on('exit', (data: any) => {
c(false);
return c(false);
});
});
}
@@ -346,12 +326,12 @@ export class LineProcess extends AbstractProcess<LineData> {
protected handleExec(cc: ValueCallback<SuccessData>, pp: ProgressCallback<LineData>, error: Error, stdout: Buffer, stderr: Buffer) {
[stdout, stderr].forEach((buffer: Buffer, index: number) => {
let lineDecoder = new LineDecoder();
let lines = lineDecoder.write(buffer);
const lineDecoder = new LineDecoder();
const lines = lineDecoder.write(buffer);
lines.forEach((line) => {
pp({ line: line, source: index === 0 ? Source.stdout : Source.stderr });
});
let line = lineDecoder.end();
const line = lineDecoder.end();
if (line) {
pp({ line: line, source: index === 0 ? Source.stdout : Source.stderr });
}
@@ -363,11 +343,11 @@ export class LineProcess extends AbstractProcess<LineData> {
this.stdoutLineDecoder = new LineDecoder();
this.stderrLineDecoder = new LineDecoder();
childProcess.stdout.on('data', (data: Buffer) => {
let lines = this.stdoutLineDecoder.write(data);
const lines = this.stdoutLineDecoder.write(data);
lines.forEach(line => pp({ line: line, source: Source.stdout }));
});
childProcess.stderr.on('data', (data: Buffer) => {
let lines = this.stderrLineDecoder.write(data);
const lines = this.stderrLineDecoder.write(data);
lines.forEach(line => pp({ line: line, source: Source.stderr }));
});
}
@@ -400,7 +380,7 @@ export function createQueuedSender(childProcess: cp.ChildProcess): IQueuedSender
return;
}
let result = childProcess.send(msg, (error: Error) => {
const result = childProcess.send(msg, (error: Error) => {
if (error) {
console.error(error); // unlikely to happen, best we can do is log this error
}
@@ -432,7 +412,7 @@ export namespace win32 {
if (cwd === undefined) {
cwd = process.cwd();
}
let dir = path.dirname(command);
const dir = path.dirname(command);
if (dir !== '.') {
// We have a directory and the directory is relative (see above). Make the path absolute
// to the current working directory.
@@ -469,4 +449,4 @@ export namespace win32 {
}
return path.join(cwd, command);
}
}
}

View File

@@ -151,7 +151,7 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
rootItem = processItems.get(rootPid);
if (rootItem) {
processItems.forEach(item => {
let parent = processItems.get(item.ppid);
const parent = processItems.get(item.ppid);
if (parent) {
if (!parent.children) {
parent.children = [];
@@ -186,7 +186,7 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
const lines = stdout.toString().split('\n');
for (const line of lines) {
let matches = PID_CMD.exec(line.trim());
const matches = PID_CMD.exec(line.trim());
if (matches && matches.length === 6) {
addToTree(parseInt(matches[1]), parseInt(matches[2]), matches[5], parseFloat(matches[3]), parseFloat(matches[4]));
}

View File

@@ -152,7 +152,7 @@ export function asText(context: IRequestContext): Promise<string | null> {
return c(null);
}
let buffer: string[] = [];
const buffer: string[] = [];
context.stream.on('data', (d: string) => buffer.push(d));
context.stream.on('end', () => c(buffer.join('')));
context.stream.on('error', e);

View File

@@ -1,197 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { readdir, stat, exists, readFile } from 'fs';
import { join } from 'path';
import { parse, ParseError } from 'vs/base/common/json';
export interface WorkspaceStatItem {
name: string;
count: number;
}
export interface WorkspaceStats {
fileTypes: WorkspaceStatItem[];
configFiles: WorkspaceStatItem[];
fileCount: number;
maxFilesReached: boolean;
}
function asSortedItems(map: Map<string, number>): WorkspaceStatItem[] {
let a: WorkspaceStatItem[] = [];
map.forEach((value, index) => a.push({ name: index, count: value }));
return a.sort((a, b) => b.count - a.count);
}
export function collectLaunchConfigs(folder: string): Promise<WorkspaceStatItem[]> {
let launchConfigs = new Map<string, number>();
let launchConfig = join(folder, '.vscode', 'launch.json');
return new Promise((resolve, reject) => {
exists(launchConfig, (doesExist) => {
if (doesExist) {
readFile(launchConfig, (err, contents) => {
if (err) {
return resolve([]);
}
const errors: ParseError[] = [];
const json = parse(contents.toString(), errors);
if (errors.length) {
console.log(`Unable to parse ${launchConfig}`);
return resolve([]);
}
if (json['configurations']) {
for (const each of json['configurations']) {
const type = each['type'];
if (type) {
if (launchConfigs.has(type)) {
launchConfigs.set(type, launchConfigs.get(type)! + 1);
} else {
launchConfigs.set(type, 1);
}
}
}
}
return resolve(asSortedItems(launchConfigs));
});
} else {
return resolve([]);
}
});
});
}
export function collectWorkspaceStats(folder: string, filter: string[]): Promise<WorkspaceStats> {
const configFilePatterns = [
{ 'tag': 'grunt.js', 'pattern': /^gruntfile\.js$/i },
{ 'tag': 'gulp.js', 'pattern': /^gulpfile\.js$/i },
{ 'tag': 'tsconfig.json', 'pattern': /^tsconfig\.json$/i },
{ 'tag': 'package.json', 'pattern': /^package\.json$/i },
{ 'tag': 'jsconfig.json', 'pattern': /^jsconfig\.json$/i },
{ 'tag': 'tslint.json', 'pattern': /^tslint\.json$/i },
{ 'tag': 'eslint.json', 'pattern': /^eslint\.json$/i },
{ 'tag': 'tasks.json', 'pattern': /^tasks\.json$/i },
{ 'tag': 'launch.json', 'pattern': /^launch\.json$/i },
{ 'tag': 'settings.json', 'pattern': /^settings\.json$/i },
{ 'tag': 'webpack.config.js', 'pattern': /^webpack\.config\.js$/i },
{ 'tag': 'project.json', 'pattern': /^project\.json$/i },
{ 'tag': 'makefile', 'pattern': /^makefile$/i },
{ 'tag': 'sln', 'pattern': /^.+\.sln$/i },
{ 'tag': 'csproj', 'pattern': /^.+\.csproj$/i },
{ 'tag': 'cmake', 'pattern': /^.+\.cmake$/i }
];
let fileTypes = new Map<string, number>();
let configFiles = new Map<string, number>();
const MAX_FILES = 20000;
function walk(dir: string, filter: string[], token, done: (allFiles: string[]) => void): void {
let results: string[] = [];
readdir(dir, async (err, files) => {
// Ignore folders that can't be read
if (err) {
return done(results);
}
let pending = files.length;
if (pending === 0) {
return done(results);
}
for (const file of files) {
if (token.maxReached) {
return done(results);
}
stat(join(dir, file), (err, stats) => {
// Ignore files that can't be read
if (err) {
if (--pending === 0) {
return done(results);
}
} else {
if (stats.isDirectory()) {
if (filter.indexOf(file) === -1) {
walk(join(dir, file), filter, token, (res: string[]) => {
results = results.concat(res);
if (--pending === 0) {
return done(results);
}
});
} else {
if (--pending === 0) {
done(results);
}
}
} else {
if (token.count >= MAX_FILES) {
token.maxReached = true;
}
token.count++;
results.push(file);
if (--pending === 0) {
done(results);
}
}
}
});
}
});
}
let addFileType = (fileType: string) => {
if (fileTypes.has(fileType)) {
fileTypes.set(fileType, fileTypes.get(fileType)! + 1);
}
else {
fileTypes.set(fileType, 1);
}
};
let addConfigFiles = (fileName: string) => {
for (const each of configFilePatterns) {
if (each.pattern.test(fileName)) {
if (configFiles.has(each.tag)) {
configFiles.set(each.tag, configFiles.get(each.tag)! + 1);
} else {
configFiles.set(each.tag, 1);
}
}
}
};
let acceptFile = (name: string) => {
if (name.lastIndexOf('.') >= 0) {
let suffix: string | undefined = name.split('.').pop();
if (suffix) {
addFileType(suffix);
}
}
addConfigFiles(name);
};
let token: { count: number, maxReached: boolean } = { count: 0, maxReached: false };
return new Promise((resolve, reject) => {
walk(folder, filter, token, (files) => {
files.forEach(acceptFile);
resolve({
configFiles: asSortedItems(configFiles),
fileTypes: asSortedItems(fileTypes),
fileCount: token.count,
maxFilesReached: token.maxReached
});
});
});
}

View File

@@ -9,8 +9,7 @@ import { Emitter, Event } from 'vs/base/common/event';
import { ThrottledDelayer, timeout } from 'vs/base/common/async';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { mapToString, setToString } from 'vs/base/common/map';
import { basename } from 'path';
import { mark } from 'vs/base/common/performance';
import { basename } from 'vs/base/common/path';
import { copy, renameIgnoreError, unlink } from 'vs/base/node/pfs';
import { fill } from 'vs/base/common/arrays';
@@ -62,8 +61,8 @@ export interface IStorage extends IDisposable {
getBoolean(key: string, fallbackValue: boolean): boolean;
getBoolean(key: string, fallbackValue?: boolean): boolean | undefined;
getInteger(key: string, fallbackValue: number): number;
getInteger(key: string, fallbackValue?: number): number | undefined;
getNumber(key: string, fallbackValue: number): number;
getNumber(key: string, fallbackValue?: number): number | undefined;
set(key: string, value: string | boolean | number): Promise<void>;
delete(key: string): Promise<void>;
@@ -84,7 +83,7 @@ export class Storage extends Disposable implements IStorage {
private static readonly DEFAULT_FLUSH_DELAY = 100;
private _onDidChangeStorage: Emitter<string> = this._register(new Emitter<string>());
private readonly _onDidChangeStorage: Emitter<string> = this._register(new Emitter<string>());
get onDidChangeStorage(): Event<string> { return this._onDidChangeStorage.event; }
private state = StorageState.None;
@@ -196,9 +195,9 @@ export class Storage extends Disposable implements IStorage {
return value === 'true';
}
getInteger(key: string, fallbackValue: number): number;
getInteger(key: string, fallbackValue?: number): number | undefined;
getInteger(key: string, fallbackValue?: number): number | undefined {
getNumber(key: string, fallbackValue: number): number;
getNumber(key: string, fallbackValue?: number): number | undefined;
getNumber(key: string, fallbackValue?: number): number | undefined {
const value = this.get(key);
if (isUndefinedOrNull(value)) {
@@ -326,8 +325,6 @@ export class SQLiteStorageDatabase implements IStorageDatabase {
get onDidChangeItemsExternal(): Event<IStorageItemsChangeEvent> { return Event.None; } // since we are the only client, there can be no external changes
private static measuredRequireDuration: boolean; // TODO@Ben remove me after a while
private static BUSY_OPEN_TIMEOUT = 2000; // timeout in ms to retry when opening DB fails with SQLITE_BUSY
private static MAX_HOST_PARAMETERS = 256; // maximum number of parameters within a statement
@@ -355,7 +352,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase {
rows.forEach(row => items.set(row.key, row.value));
if (this.logger.isTracing) {
this.logger.trace(`[storage ${this.name}] getItems(): ${mapToString(items)}`);
this.logger.trace(`[storage ${this.name}] getItems(): ${items.size} rows`);
}
return items;
@@ -598,21 +595,8 @@ export class SQLiteStorageDatabase implements IStorageDatabase {
}
private doConnect(path: string): Promise<IDatabaseConnection> {
// TODO@Ben clean up performance markers
return new Promise((resolve, reject) => {
let measureRequireDuration = false;
if (!SQLiteStorageDatabase.measuredRequireDuration) {
SQLiteStorageDatabase.measuredRequireDuration = true;
measureRequireDuration = true;
mark('willRequireSQLite');
}
import('vscode-sqlite3').then(sqlite3 => {
if (measureRequireDuration) {
mark('didRequireSQLite');
}
const connection: IDatabaseConnection = {
db: new (this.logger.isTracing ? sqlite3.verbose().Database : sqlite3.Database)(path, error => {
if (error) {
@@ -622,17 +606,12 @@ export class SQLiteStorageDatabase implements IStorageDatabase {
// The following exec() statement serves two purposes:
// - create the DB if it does not exist yet
// - validate that the DB is not corrupt (the open() call does not throw otherwise)
mark('willSetupSQLiteSchema');
return this.exec(connection, [
'PRAGMA user_version = 1;',
'CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB)'
].join('')).then(() => {
mark('didSetupSQLiteSchema');
return resolve(connection);
}, error => {
mark('didSetupSQLiteSchema');
return connection.db.close(() => reject(error));
});
}),

View File

@@ -92,7 +92,7 @@ export function readToMatchingString(file: string, matchingString: string, chunk
});
}
let buffer = Buffer.allocUnsafe(maximumBytesToRead);
const buffer = Buffer.allocUnsafe(maximumBytesToRead);
let offset = 0;
function readChunk(): void {

Binary file not shown.

View File

@@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as path from 'vs/base/common/path';
import * as os from 'os';
import { extract } from 'vs/base/node/zip';
import { generateUuid } from 'vs/base/common/uuid';
import { rimraf, exists } from 'vs/base/node/pfs';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { createCancelablePromise } from 'vs/base/common/async';
const fixtures = getPathFromAmdModule(require, './fixtures');
suite('Zip', () => {
test('extract should handle directories', () => {
const fixture = path.join(fixtures, 'extract.zip');
const target = path.join(os.tmpdir(), generateUuid());
return createCancelablePromise(token => extract(fixture, target, {}, token)
.then(() => exists(path.join(target, 'extension')))
.then(exists => assert(exists))
.then(() => rimraf(target)));
});
});

231
src/vs/base/node/zip.ts Normal file
View File

@@ -0,0 +1,231 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as path from 'vs/base/common/path';
import { createWriteStream, WriteStream } from 'fs';
import { Readable } from 'stream';
import { nfcall, ninvoke, Sequencer, createCancelablePromise } from 'vs/base/common/async';
import { mkdirp, rimraf } from 'vs/base/node/pfs';
import { open as _openZip, Entry, ZipFile } from 'yauzl';
import * as yazl from 'yazl';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Event } from 'vs/base/common/event';
export interface IExtractOptions {
overwrite?: boolean;
/**
* Source path within the ZIP archive. Only the files contained in this
* path will be extracted.
*/
sourcePath?: string;
}
interface IOptions {
sourcePathRegex: RegExp;
}
export type ExtractErrorType = 'CorruptZip' | 'Incomplete';
export class ExtractError extends Error {
readonly type?: ExtractErrorType;
readonly cause: Error;
constructor(type: ExtractErrorType | undefined, cause: Error) {
let message = cause.message;
switch (type) {
case 'CorruptZip': message = `Corrupt ZIP: ${message}`; break;
}
super(message);
this.type = type;
this.cause = cause;
}
}
function modeFromEntry(entry: Entry) {
const attr = entry.externalFileAttributes >> 16 || 33188;
return [448 /* S_IRWXU */, 56 /* S_IRWXG */, 7 /* S_IRWXO */]
.map(mask => attr & mask)
.reduce((a, b) => a + b, attr & 61440 /* S_IFMT */);
}
function toExtractError(err: Error): ExtractError {
if (err instanceof ExtractError) {
return err;
}
let type: ExtractErrorType | undefined = undefined;
if (/end of central directory record signature not found/.test(err.message)) {
type = 'CorruptZip';
}
return new ExtractError(type, err);
}
function extractEntry(stream: Readable, fileName: string, mode: number, targetPath: string, options: IOptions, token: CancellationToken): Promise<void> {
const dirName = path.dirname(fileName);
const targetDirName = path.join(targetPath, dirName);
if (targetDirName.indexOf(targetPath) !== 0) {
return Promise.reject(new Error(nls.localize('invalid file', "Error extracting {0}. Invalid file.", fileName)));
}
const targetFileName = path.join(targetPath, fileName);
let istream: WriteStream;
Event.once(token.onCancellationRequested)(() => {
if (istream) {
istream.destroy();
}
});
return Promise.resolve(mkdirp(targetDirName, undefined, token)).then(() => new Promise<void>((c, e) => {
if (token.isCancellationRequested) {
return;
}
try {
istream = createWriteStream(targetFileName, { mode });
istream.once('close', () => c());
istream.once('error', e);
stream.once('error', e);
stream.pipe(istream);
} catch (error) {
e(error);
}
}));
}
function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, token: CancellationToken): Promise<void> {
let last = createCancelablePromise<void>(() => Promise.resolve());
let extractedEntriesCount = 0;
Event.once(token.onCancellationRequested)(() => {
last.cancel();
zipfile.close();
});
return new Promise((c, e) => {
const throttler = new Sequencer();
const readNextEntry = (token: CancellationToken) => {
if (token.isCancellationRequested) {
return;
}
extractedEntriesCount++;
zipfile.readEntry();
};
zipfile.once('error', e);
zipfile.once('close', () => last.then(() => {
if (token.isCancellationRequested || zipfile.entryCount === extractedEntriesCount) {
c();
} else {
e(new ExtractError('Incomplete', new Error(nls.localize('incompleteExtract', "Incomplete. Found {0} of {1} entries", extractedEntriesCount, zipfile.entryCount))));
}
}, e));
zipfile.readEntry();
zipfile.on('entry', (entry: Entry) => {
if (token.isCancellationRequested) {
return;
}
if (!options.sourcePathRegex.test(entry.fileName)) {
readNextEntry(token);
return;
}
const fileName = entry.fileName.replace(options.sourcePathRegex, '');
// directory file names end with '/'
if (/\/$/.test(fileName)) {
const targetFileName = path.join(targetPath, fileName);
last = createCancelablePromise(token => mkdirp(targetFileName, undefined, token).then(() => readNextEntry(token)).then(undefined, e));
return;
}
const stream = ninvoke(zipfile, zipfile.openReadStream, entry);
const mode = modeFromEntry(entry);
last = createCancelablePromise(token => throttler.queue(() => stream.then(stream => extractEntry(stream, fileName, mode, targetPath, options, token).then(() => readNextEntry(token)))).then(null!, e));
});
});
}
function openZip(zipFile: string, lazy: boolean = false): Promise<ZipFile> {
return nfcall<ZipFile>(_openZip, zipFile, lazy ? { lazyEntries: true } : undefined)
.then(undefined, err => Promise.reject(toExtractError(err)));
}
export interface IFile {
path: string;
contents?: Buffer | string;
localPath?: string;
}
export function zip(zipPath: string, files: IFile[]): Promise<string> {
return new Promise<string>((c, e) => {
const zip = new yazl.ZipFile();
files.forEach(f => {
if (f.contents) {
zip.addBuffer(typeof f.contents === 'string' ? Buffer.from(f.contents, 'utf8') : f.contents, f.path);
} else if (f.localPath) {
zip.addFile(f.localPath, f.path);
}
});
zip.end();
const zipStream = createWriteStream(zipPath);
zip.outputStream.pipe(zipStream);
zip.outputStream.once('error', e);
zipStream.once('error', e);
zipStream.once('finish', () => c(zipPath));
});
}
export function extract(zipPath: string, targetPath: string, options: IExtractOptions = {}, token: CancellationToken): Promise<void> {
const sourcePathRegex = new RegExp(options.sourcePath ? `^${options.sourcePath}` : '');
let promise = openZip(zipPath, true);
if (options.overwrite) {
promise = promise.then(zipfile => rimraf(targetPath).then(() => zipfile));
}
return promise.then(zipfile => extractZip(zipfile, targetPath, { sourcePathRegex }, token));
}
function read(zipPath: string, filePath: string): Promise<Readable> {
return openZip(zipPath).then(zipfile => {
return new Promise<Readable>((c, e) => {
zipfile.on('entry', (entry: Entry) => {
if (entry.fileName === filePath) {
ninvoke<Readable>(zipfile, zipfile.openReadStream, entry).then(stream => c(stream), err => e(err));
}
});
zipfile.once('close', () => e(new Error(nls.localize('notFound', "{0} not found inside zip.", filePath))));
});
});
}
export function buffer(zipPath: string, filePath: string): Promise<Buffer> {
return read(zipPath, filePath).then(stream => {
return new Promise<Buffer>((c, e) => {
const buffers: Buffer[] = [];
stream.once('error', e);
stream.on('data', b => buffers.push(b as Buffer));
stream.on('end', () => c(Buffer.concat(buffers)));
});
});
}