diff options
author | Johannes <johannes.rieken@gmail.com> | 2022-05-05 12:35:33 +0300 |
---|---|---|
committer | Johannes <johannes.rieken@gmail.com> | 2022-05-05 12:35:33 +0300 |
commit | c13d581bcec2c5dbebe7e51a4988a46c50899f0e (patch) | |
tree | 7be7550c8e5ced92971cd821de702b10ca9caee6 | |
parent | 63076cfe1013c9d29972a9ddfdb749e29640a07e (diff) |
move gulp-tsb into the `build/lib` remove as dependency
-rw-r--r-- | build/gulpfile.extensions.js | 2 | ||||
-rw-r--r-- | build/lib/compilation.js | 2 | ||||
-rw-r--r-- | build/lib/compilation.ts | 2 | ||||
-rw-r--r-- | build/lib/tsb/builder.js | 491 | ||||
-rw-r--r-- | build/lib/tsb/builder.ts | 616 | ||||
-rw-r--r-- | build/lib/tsb/index.js | 89 | ||||
-rw-r--r-- | build/lib/tsb/index.ts | 118 | ||||
-rw-r--r-- | build/lib/tsb/utils.js | 124 | ||||
-rw-r--r-- | build/lib/tsb/utils.ts | 140 | ||||
-rw-r--r-- | build/lib/typings/gulp-tsb.d.ts | 18 | ||||
-rw-r--r-- | extensions/vscode-colorize-tests/test/colorize-fixtures/test.js | 20 | ||||
-rw-r--r-- | extensions/vscode-colorize-tests/test/colorize-results/test_js.json | 38 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | yarn.lock | 10 |
14 files changed, 1622 insertions, 49 deletions
diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index 8e05d03d295..ba2606861fd 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -102,7 +102,7 @@ const tasks = compilations.map(function (tsconfigFile) { function createPipeline(build, emitError) { const nlsDev = require('vscode-nls-dev'); - const tsb = require('gulp-tsb'); + const tsb = require('./lib/tsb'); const sourcemaps = require('gulp-sourcemaps'); const reporter = createReporter('extensions'); diff --git a/build/lib/compilation.js b/build/lib/compilation.js index 1841795cefd..db489e01f9d 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -35,7 +35,7 @@ function getTypeScriptCompilerOptions(src) { return options; } function createCompile(src, build, emitError) { - const tsb = require('gulp-tsb'); + const tsb = require('./tsb'); const sourcemaps = require('gulp-sourcemaps'); const projectPath = path.join(__dirname, '../../', src, 'tsconfig.json'); const overrideOptions = { ...getTypeScriptCompilerOptions(src), inlineSources: Boolean(build) }; diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index 1325f89fd04..d40b6139cc0 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -40,7 +40,7 @@ function getTypeScriptCompilerOptions(src: string): ts.CompilerOptions { } function createCompile(src: string, build: boolean, emitError?: boolean) { - const tsb = require('gulp-tsb') as typeof import('gulp-tsb'); + const tsb = require('./tsb') as typeof import('./tsb'); const sourcemaps = require('gulp-sourcemaps') as typeof import('gulp-sourcemaps'); diff --git a/build/lib/tsb/builder.js b/build/lib/tsb/builder.js new file mode 100644 index 00000000000..f0e1958df7e --- /dev/null +++ b/build/lib/tsb/builder.js @@ -0,0 +1,491 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.createTypeScriptBuilder = exports.CancellationToken = void 0; +const fs_1 = require("fs"); +const path = require("path"); +const crypto = require("crypto"); +const utils = require("./utils"); +const log = require("fancy-log"); +const colors = require("ansi-colors"); +const ts = require("typescript"); +const Vinyl = require("vinyl"); +var CancellationToken; +(function (CancellationToken) { + CancellationToken.None = { + isCancellationRequested() { return false; } + }; +})(CancellationToken = exports.CancellationToken || (exports.CancellationToken = {})); +function normalize(path) { + return path.replace(/\\/g, '/'); +} +function createTypeScriptBuilder(config, projectFile, cmd) { + function _log(topic, message) { + if (config.verbose) { + log(colors.cyan(topic), message); + } + } + let host = new LanguageServiceHost(cmd, projectFile, _log), service = ts.createLanguageService(host, ts.createDocumentRegistry()), lastBuildVersion = Object.create(null), lastDtsHash = Object.create(null), userWantsDeclarations = cmd.options.declaration, oldErrors = Object.create(null), headUsed = process.memoryUsage().heapUsed, emitSourceMapsInStream = true; + // always emit declaraction files + host.getCompilationSettings().declaration = true; + function file(file) { + // support gulp-sourcemaps + if (file.sourceMap) { + emitSourceMapsInStream = false; + } + if (!file.contents) { + host.removeScriptSnapshot(file.path); + } + else { + host.addScriptSnapshot(file.path, new VinylScriptSnapshot(file)); + } + } + function baseFor(snapshot) { + if (snapshot instanceof VinylScriptSnapshot) { + return cmd.options.outDir || snapshot.getBase(); + } + else { + return ''; + } + } + function isExternalModule(sourceFile) { + return sourceFile.externalModuleIndicator + || /declare\s+module\s+('|")(.+)\1/.test(sourceFile.getText()); + } + function build(out, onError, token = CancellationToken.None) { + function checkSyntaxSoon(fileName) { + return new Promise(resolve => { + process.nextTick(function () { + if (!host.getScriptSnapshot(fileName, false)) { + resolve([]); // no script, no problems + } + else { + resolve(service.getSyntacticDiagnostics(fileName)); + } + }); + }); + } + function checkSemanticsSoon(fileName) { + return new Promise(resolve => { + process.nextTick(function () { + if (!host.getScriptSnapshot(fileName, false)) { + resolve([]); // no script, no problems + } + else { + resolve(service.getSemanticDiagnostics(fileName)); + } + }); + }); + } + function emitSoon(fileName) { + return new Promise(resolve => { + process.nextTick(function () { + if (/\.d\.ts$/.test(fileName)) { + // if it's already a d.ts file just emit it signature + let snapshot = host.getScriptSnapshot(fileName); + let signature = crypto.createHash('md5') + .update(snapshot.getText(0, snapshot.getLength())) + .digest('base64'); + return resolve({ + fileName, + signature, + files: [] + }); + } + let output = service.getEmitOutput(fileName); + let files = []; + let signature; + for (let file of output.outputFiles) { + if (!emitSourceMapsInStream && /\.js\.map$/.test(file.name)) { + continue; + } + if (/\.d\.ts$/.test(file.name)) { + signature = crypto.createHash('md5') + .update(file.text) + .digest('base64'); + if (!userWantsDeclarations) { + // don't leak .d.ts files if users don't want them + continue; + } + } + let vinyl = new Vinyl({ + path: file.name, + contents: Buffer.from(file.text), + base: !config._emitWithoutBasePath && baseFor(host.getScriptSnapshot(fileName)) || undefined + }); + if (!emitSourceMapsInStream && /\.js$/.test(file.name)) { + let sourcemapFile = output.outputFiles.filter(f => /\.js\.map$/.test(f.name))[0]; + if (sourcemapFile) { + let extname = path.extname(vinyl.relative); + let basename = path.basename(vinyl.relative, extname); + let dirname = path.dirname(vinyl.relative); + let tsname = (dirname === '.' ? '' : dirname + '/') + basename + '.ts'; + let sourceMap = JSON.parse(sourcemapFile.text); + sourceMap.sources[0] = tsname.replace(/\\/g, '/'); + vinyl.sourceMap = sourceMap; + } + } + files.push(vinyl); + } + resolve({ + fileName, + signature, + files + }); + }); + }); + } + let newErrors = Object.create(null); + let t1 = Date.now(); + let toBeEmitted = []; + let toBeCheckedSyntactically = []; + let toBeCheckedSemantically = []; + let filesWithChangedSignature = []; + let dependentFiles = []; + let newLastBuildVersion = new Map(); + for (let fileName of host.getScriptFileNames()) { + if (lastBuildVersion[fileName] !== host.getScriptVersion(fileName)) { + toBeEmitted.push(fileName); + toBeCheckedSyntactically.push(fileName); + toBeCheckedSemantically.push(fileName); + } + } + return new Promise(resolve => { + let semanticCheckInfo = new Map(); + let seenAsDependentFile = new Set(); + function workOnNext() { + let promise; + // let fileName: string; + // someone told us to stop this + if (token.isCancellationRequested()) { + _log('[CANCEL]', '>>This compile run was cancelled<<'); + newLastBuildVersion.clear(); + resolve(); + return; + } + // (1st) emit code + else if (toBeEmitted.length) { + let fileName = toBeEmitted.pop(); + promise = emitSoon(fileName).then(value => { + for (let file of value.files) { + _log('[emit code]', file.path); + out(file); + } + // remember when this was build + newLastBuildVersion.set(fileName, host.getScriptVersion(fileName)); + // remeber the signature + if (value.signature && lastDtsHash[fileName] !== value.signature) { + lastDtsHash[fileName] = value.signature; + filesWithChangedSignature.push(fileName); + } + }).catch(e => { + // can't just skip this or make a result up.. + host.error(`ERROR emitting ${fileName}`); + host.error(e); + }); + } + // (2nd) check syntax + else if (toBeCheckedSyntactically.length) { + let fileName = toBeCheckedSyntactically.pop(); + _log('[check syntax]', fileName); + promise = checkSyntaxSoon(fileName).then(diagnostics => { + delete oldErrors[fileName]; + if (diagnostics.length > 0) { + diagnostics.forEach(d => onError(d)); + newErrors[fileName] = diagnostics; + // stop the world when there are syntax errors + toBeCheckedSyntactically.length = 0; + toBeCheckedSemantically.length = 0; + filesWithChangedSignature.length = 0; + } + }); + } + // (3rd) check semantics + else if (toBeCheckedSemantically.length) { + let fileName = toBeCheckedSemantically.pop(); + while (fileName && semanticCheckInfo.has(fileName)) { + fileName = toBeCheckedSemantically.pop(); + } + if (fileName) { + _log('[check semantics]', fileName); + promise = checkSemanticsSoon(fileName).then(diagnostics => { + delete oldErrors[fileName]; + semanticCheckInfo.set(fileName, diagnostics.length); + if (diagnostics.length > 0) { + diagnostics.forEach(d => onError(d)); + newErrors[fileName] = diagnostics; + } + }); + } + } + // (4th) check dependents + else if (filesWithChangedSignature.length) { + while (filesWithChangedSignature.length) { + let fileName = filesWithChangedSignature.pop(); + if (!isExternalModule(service.getProgram().getSourceFile(fileName))) { + _log('[check semantics*]', fileName + ' is an internal module and it has changed shape -> check whatever hasn\'t been checked yet'); + toBeCheckedSemantically.push(...host.getScriptFileNames()); + filesWithChangedSignature.length = 0; + dependentFiles.length = 0; + break; + } + host.collectDependents(fileName, dependentFiles); + } + } + // (5th) dependents contd + else if (dependentFiles.length) { + let fileName = dependentFiles.pop(); + while (fileName && seenAsDependentFile.has(fileName)) { + fileName = dependentFiles.pop(); + } + if (fileName) { + seenAsDependentFile.add(fileName); + let value = semanticCheckInfo.get(fileName); + if (value === 0) { + // already validated successfully -> look at dependents next + host.collectDependents(fileName, dependentFiles); + } + else if (typeof value === 'undefined') { + // first validate -> look at dependents next + dependentFiles.push(fileName); + toBeCheckedSemantically.push(fileName); + } + } + } + // (last) done + else { + resolve(); + return; + } + if (!promise) { + promise = Promise.resolve(); + } + promise.then(function () { + // change to change + process.nextTick(workOnNext); + }).catch(err => { + console.error(err); + }); + } + workOnNext(); + }).then(() => { + // store the build versions to not rebuilt the next time + newLastBuildVersion.forEach((value, key) => { + lastBuildVersion[key] = value; + }); + // print old errors and keep them + utils.collections.forEach(oldErrors, entry => { + entry.value.forEach(diag => onError(diag)); + newErrors[entry.key] = entry.value; + }); + oldErrors = newErrors; + // print stats + if (config.verbose) { + const headNow = process.memoryUsage().heapUsed; + const MB = 1024 * 1024; + log('[tsb]', 'time:', colors.yellow((Date.now() - t1) + 'ms'), 'mem:', colors.cyan(Math.ceil(headNow / MB) + 'MB'), colors.bgcyan('delta: ' + Math.ceil((headNow - headUsed) / MB))); + headUsed = headNow; + } + }); + } + return { + file, + build, + languageService: service + }; +} +exports.createTypeScriptBuilder = createTypeScriptBuilder; +class ScriptSnapshot { + constructor(text, mtime) { + this._text = text; + this._mtime = mtime; + } + getVersion() { + return this._mtime.toUTCString(); + } + getText(start, end) { + return this._text.substring(start, end); + } + getLength() { + return this._text.length; + } + getChangeRange(_oldSnapshot) { + return undefined; + } +} +class VinylScriptSnapshot extends ScriptSnapshot { + constructor(file) { + super(file.contents.toString(), file.stat.mtime); + this._base = file.base; + } + getBase() { + return this._base; + } +} +class LanguageServiceHost { + constructor(_cmdLine, _projectPath, _log) { + this._cmdLine = _cmdLine; + this._projectPath = _projectPath; + this._log = _log; + this.directoryExists = ts.sys.directoryExists; + this.getDirectories = ts.sys.getDirectories; + this.fileExists = ts.sys.fileExists; + this.readFile = ts.sys.readFile; + this.readDirectory = ts.sys.readDirectory; + this._snapshots = Object.create(null); + this._filesInProject = new Set(_cmdLine.fileNames); + this._filesAdded = new Set(); + this._dependencies = new utils.graph.Graph(s => s); + this._dependenciesRecomputeList = []; + this._fileNameToDeclaredModule = Object.create(null); + this._projectVersion = 1; + } + log(_s) { + // console.log(s); + } + trace(_s) { + // console.log(s); + } + error(s) { + console.error(s); + } + getCompilationSettings() { + return this._cmdLine.options; + } + getProjectVersion() { + return String(this._projectVersion); + } + getScriptFileNames() { + const res = Object.keys(this._snapshots).filter(path => this._filesInProject.has(path) || this._filesAdded.has(path)); + return res; + } + getScriptVersion(filename) { + filename = normalize(filename); + const result = this._snapshots[filename]; + if (result) { + return result.getVersion(); + } + return 'UNKNWON_FILE_' + Math.random().toString(16).slice(2); + } + getScriptSnapshot(filename, resolve = true) { + filename = normalize(filename); + let result = this._snapshots[filename]; + if (!result && resolve) { + try { + result = new VinylScriptSnapshot(new Vinyl({ + path: filename, + contents: (0, fs_1.readFileSync)(filename), + base: this.getCompilationSettings().outDir, + stat: (0, fs_1.statSync)(filename) + })); + this.addScriptSnapshot(filename, result); + } + catch (e) { + // ignore + } + } + return result; + } + addScriptSnapshot(filename, snapshot) { + this._projectVersion++; + filename = normalize(filename); + const old = this._snapshots[filename]; + if (!old && !this._filesInProject.has(filename) && !filename.endsWith('.d.ts')) { + // ^^^^^^^^^^^^^^^^^^^^^^^^^^ + // not very proper! + this._filesAdded.add(filename); + } + if (!old || old.getVersion() !== snapshot.getVersion()) { + this._dependenciesRecomputeList.push(filename); + let node = this._dependencies.lookup(filename); + if (node) { + node.outgoing = Object.create(null); + } + // (cheap) check for declare module + LanguageServiceHost._declareModule.lastIndex = 0; + let match; + while ((match = LanguageServiceHost._declareModule.exec(snapshot.getText(0, snapshot.getLength())))) { + let declaredModules = this._fileNameToDeclaredModule[filename]; + if (!declaredModules) { + this._fileNameToDeclaredModule[filename] = declaredModules = []; + } + declaredModules.push(match[2]); + } + } + this._snapshots[filename] = snapshot; + return old; + } + removeScriptSnapshot(filename) { + this._filesInProject.delete(filename); + this._filesAdded.delete(filename); + this._projectVersion++; + filename = normalize(filename); + delete this._fileNameToDeclaredModule[filename]; + return delete this._snapshots[filename]; + } + getCurrentDirectory() { + return path.dirname(this._projectPath); + } + getDefaultLibFileName(options) { + return ts.getDefaultLibFilePath(options); + } + // ---- dependency management + collectDependents(filename, target) { + while (this._dependenciesRecomputeList.length) { + this._processFile(this._dependenciesRecomputeList.pop()); + } + filename = normalize(filename); + const node = this._dependencies.lookup(filename); + if (node) { + utils.collections.forEach(node.incoming, entry => target.push(entry.key)); + } + } + _processFile(filename) { + if (filename.match(/.*\.d\.ts$/)) { + return; + } + filename = normalize(filename); + const snapshot = this.getScriptSnapshot(filename); + if (!snapshot) { + this._log('processFile', `Missing snapshot for: ${filename}`); + return; + } + const info = ts.preProcessFile(snapshot.getText(0, snapshot.getLength()), true); + // (1) ///-references + info.referencedFiles.forEach(ref => { + const resolvedPath = path.resolve(path.dirname(filename), ref.fileName); + const normalizedPath = normalize(resolvedPath); + this._dependencies.inertEdge(filename, normalizedPath); + }); + // (2) import-require statements + info.importedFiles.forEach(ref => { + const stopDirname = normalize(this.getCurrentDirectory()); + let dirname = filename; + let found = false; + while (!found && dirname.indexOf(stopDirname) === 0) { + dirname = path.dirname(dirname); + const resolvedPath = path.resolve(dirname, ref.fileName); + const normalizedPath = normalize(resolvedPath); + if (this.getScriptSnapshot(normalizedPath + '.ts')) { + this._dependencies.inertEdge(filename, normalizedPath + '.ts'); + found = true; + } + else if (this.getScriptSnapshot(normalizedPath + '.d.ts')) { + this._dependencies.inertEdge(filename, normalizedPath + '.d.ts'); + found = true; + } + } + if (!found) { + for (let key in this._fileNameToDeclaredModule) { + if (this._fileNameToDeclaredModule[key] && ~this._fileNameToDeclaredModule[key].indexOf(ref.fileName)) { + this._dependencies.inertEdge(filename, key); + } + } + } + }); + } +} +LanguageServiceHost._declareModule = /declare\s+module\s+('|")(.+)\1/g; diff --git a/build/lib/tsb/builder.ts b/build/lib/tsb/builder.ts new file mode 100644 index 00000000000..2f8753dd119 --- /dev/null +++ b/build/lib/tsb/builder.ts @@ -0,0 +1,616 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { statSync, readFileSync } from 'fs'; +import * as path from 'path'; +import * as crypto from 'crypto'; +import * as utils from './utils'; +import * as log from 'fancy-log'; +import * as colors from 'ansi-colors'; +import * as ts from 'typescript'; +import * as Vinyl from 'vinyl'; + +export interface IConfiguration { + verbose: boolean; + _emitWithoutBasePath?: boolean; +} + +export interface CancellationToken { + isCancellationRequested(): boolean; +} + +export namespace CancellationToken { + export const None: CancellationToken = { + isCancellationRequested() { return false; } + }; +} + +export interface ITypeScriptBuilder { + build(out: (file: Vinyl) => void, onError: (err: ts.Diagnostic) => void, token?: CancellationToken): Promise<any>; + file(file: Vinyl): void; + languageService: ts.LanguageService; +} + +function normalize(path: string): string { + return path.replace(/\\/g, '/'); +} + +export function createTypeScriptBuilder(config: IConfiguration, projectFile: string, cmd: ts.ParsedCommandLine): ITypeScriptBuilder { + + function _log(topic: string, message: string): void { + if (config.verbose) { + log(colors.cyan(topic), message); + } + } + + let host = new LanguageServiceHost(cmd, projectFile, _log), + service = ts.createLanguageService(host, ts.createDocumentRegistry()), + lastBuildVersion: { [path: string]: string } = Object.create(null), + lastDtsHash: { [path: string]: string } = Object.create(null), + userWantsDeclarations = cmd.options.declaration, + oldErrors: { [path: string]: ts.Diagnostic[] } = Object.create(null), + headUsed = process.memoryUsage().heapUsed, + emitSourceMapsInStream = true; + + // always emit declaraction files + host.getCompilationSettings().declaration = true; + + + function file(file: Vinyl): void { + // support gulp-sourcemaps + if ((<any>file).sourceMap) { + emitSourceMapsInStream = false; + } + + if (!file.contents) { + host.removeScriptSnapshot(file.path); + } else { + host.addScriptSnapshot(file.path, new VinylScriptSnapshot(file)); + } + } + + function baseFor(snapshot: ScriptSnapshot): string { + if (snapshot instanceof VinylScriptSnapshot) { + return cmd.options.outDir || snapshot.getBase(); + } else { + return ''; + } + } + + function isExternalModule(sourceFile: ts.SourceFile): boolean { + return (<any>sourceFile).externalModuleIndicator + || /declare\s+module\s+('|")(.+)\1/.test(sourceFile.getText()); + } + + function build(out: (file: Vinyl) => void, onError: (err: any) => void, token = CancellationToken.None): Promise<any> { + + function checkSyntaxSoon(fileName: string): Promise<ts.Diagnostic[]> { + return new Promise<ts.Diagnostic[]>(resolve => { + process.nextTick(function () { + if (!host.getScriptSnapshot(fileName, false)) { + resolve([]); // no script, no problems + } else { + resolve(service.getSyntacticDiagnostics(fileName)); + } + }); + }); + } + + function checkSemanticsSoon(fileName: string): Promise<ts.Diagnostic[]> { + return new Promise<ts.Diagnostic[]>(resolve => { + process.nextTick(function () { + if (!host.getScriptSnapshot(fileName, false)) { + resolve([]); // no script, no problems + } else { + resolve(service.getSemanticDiagnostics(fileName)); + } + }); + }); + } + + function emitSoon(fileName: string): Promise<{ fileName: string; signature?: string; files: Vinyl[] }> { + + return new Promise(resolve => { + process.nextTick(function () { + + if (/\.d\.ts$/.test(fileName)) { + // if it's already a d.ts file just emit it signature + let snapshot = host.getScriptSnapshot(fileName); + let signature = crypto.createHash('md5') + .update(snapshot.getText(0, snapshot.getLength())) + .digest('base64'); + + return resolve({ + fileName, + signature, + files: [] + }); + } + + let output = service.getEmitOutput(fileName); + let files: Vinyl[] = []; + let signature: string | undefined; + + for (let file of output.outputFiles) { + if (!emitSourceMapsInStream && /\.js\.map$/.test(file.name)) { + continue; + } + + if (/\.d\.ts$/.test(file.name)) { + signature = crypto.createHash('md5') + .update(file.text) + .digest('base64'); + + if (!userWantsDeclarations) { + // don't leak .d.ts files if users don't want them + continue; + } + } + + let vinyl = new Vinyl({ + path: file.name, + contents: Buffer.from(file.text), + base: !config._emitWithoutBasePath && baseFor(host.getScriptSnapshot(fileName)) || undefined + }); + + if (!emitSourceMapsInStream && /\.js$/.test(file.name)) { + let sourcemapFile = output.outputFiles.filter(f => /\.js\.map$/.test(f.name))[0]; + + if (sourcemapFile) { + let extname = path.extname(vinyl.relative); + let basename = path.basename(vinyl.relative, extname); + let dirname = path.dirname(vinyl.relative); + let tsname = (dirname === '.' ? '' : dirname + '/') + basename + '.ts'; + + let sourceMap = JSON.parse(sourcemapFile.text); + sourceMap.sources[0] = tsname.replace(/\\/g, '/'); + (<any>vinyl).sourceMap = sourceMap; + } + } + + files.push(vinyl); + } + + resolve({ + fileName, + signature, + files + }); + }); + }); + } + + let newErrors: { [path: string]: ts.Diagnostic[] } = Object.create(null); + let t1 = Date.now(); + + let toBeEmitted: string[] = []; + let toBeCheckedSyntactically: string[] = []; + let toBeCheckedSemantically: string[] = []; + let filesWithChangedSignature: string[] = []; + let dependentFiles: string[] = []; + let newLastBuildVersion = new Map<string, string>(); + + for (let fileName of host.getScriptFileNames()) { + if (lastBuildVersion[fileName] !== host.getScriptVersion(fileName)) { + + toBeEmitted.push(fileName); + toBeCheckedSyntactically.push(fileName); + toBeCheckedSemantically.push(fileName); + } + } + + return new Promise<void>(resolve => { + + let semanticCheckInfo = new Map<string, number>(); + let seenAsDependentFile = new Set<string>(); + + function workOnNext() { + + let promise: Promise<any> | undefined; + // let fileName: string; + + // someone told us to stop this + if (token.isCancellationRequested()) { + _log('[CANCEL]', '>>This compile run was cancelled<<'); + newLastBuildVersion.clear(); + resolve(); + return; + } + + // (1st) emit code + else if (toBeEmitted.length) { + let fileName = toBeEmitted.pop()!; + promise = emitSoon(fileName).then(value => { + + for (let file of value.files) { + _log('[emit code]', file.path); + out(file); + } + + // remember when this was build + newLastBuildVersion.set(fileName, host.getScriptVersion(fileName)); + + // remeber the signature + if (value.signature && lastDtsHash[fileName] !== value.signature) { + lastDtsHash[fileName] = value.signature; + filesWithChangedSignature.push(fileName); + } + }).catch(e => { + // can't just skip this or make a result up.. + host.error(`ERROR emitting ${fileName}`); + host.error(e); + }); + } + + // (2nd) check syntax + else if (toBeCheckedSyntactically.length) { + let fileName = toBeCheckedSyntactically.pop()!; + _log('[check syntax]', fileName); + promise = checkSyntaxSoon(fileName).then(diagnostics => { + delete oldErrors[fileName]; + if (diagnostics.length > 0) { + diagnostics.forEach(d => onError(d)); + newErrors[fileName] = diagnostics; + + // stop the world when there are syntax errors + toBeCheckedSyntactically.length = 0; + toBeCheckedSemantically.length = 0; + filesWithChangedSignature.length = 0; + } + }); + } + + // (3rd) check semantics + else if (toBeCheckedSemantically.length) { + + let fileName = toBeCheckedSemantically.pop(); + while (fileName && semanticCheckInfo.has(fileName)) { + fileName = toBeCheckedSemantically.pop()!; + } + + if (fileName) { + _log('[check semantics]', fileName); + promise = checkSemanticsSoon(fileName).then(diagnostics => { + delete oldErrors[fileName!]; + semanticCheckInfo.set(fileName!, diagnostics.length); + if (diagnostics.length > 0) { + diagnostics.forEach(d => onError(d)); + newErrors[fileName!] = diagnostics; + } + }); + } + } + + // (4th) check dependents + else if (filesWithChangedSignature.length) { + while (filesWithChangedSignature.length) { + let fileName = filesWithChangedSignature.pop()!; + + if (!isExternalModule(service.getProgram()!.getSourceFile(fileName)!)) { + _log('[check semantics*]', fileName + ' is an internal module and it has changed shape -> check whatever hasn\'t been checked yet'); + toBeCheckedSemantically.push(...host.getScriptFileNames()); + filesWithChangedSignature.length = 0; + dependentFiles.length = 0; + break; + } + + host.collectDependents(fileName, dependentFiles); + } + } + + // (5th) dependents contd + else if (dependentFiles.length) { + let fileName = dependentFiles.pop(); + while (fileName && seenAsDependentFile.has(fileName)) { + fileName = dependentFiles.pop(); + } + if (fileName) { + seenAsDependentFile.add(fileName); + let value = semanticCheckInfo.get(fileName); + if (value === 0) { + // already validated successfully -> look at dependents next + host.collectDependents(fileName, dependentFiles); + + } else if (typeof value === 'undefined') { + // first validate -> look at dependents next + dependentFiles.push(fileName); + toBeCheckedSemantically.push(fileName); + } + } + } + + // (last) done + else { + resolve(); + return; + } + + if (!promise) { + promise = Promise.resolve(); + } + + promise.then(function () { + // change to change + process.nextTick(workOnNext); + }).catch(err => { + console.error(err); + }); + } + + workOnNext(); + + }).then(() => { + // store the build versions to not rebuilt the next time + newLastBuildVersion.forEach((value, key) => { + lastBuildVersion[key] = value; + }); + + // print old errors and keep them + utils.collections.forEach(oldErrors, entry => { + entry.value.forEach(diag => onError(diag)); + newErrors[entry.key] = entry.value; + }); + oldErrors = newErrors; + + // print stats + if (config.verbose) { + const headNow = process.memoryUsage().heapUsed; + const MB = 1024 * 1024; + log('[tsb]', + 'time:', colors.yellow((Date.now() - t1) + 'ms'), + 'mem:', colors.cyan(Math.ceil(headNow / MB) + 'MB'), colors.bgcyan('delta: ' + Math.ceil((headNow - headUsed) / MB)) + ); + headUsed = headNow; + } + }); + } + + return { + file, + build, + languageService: service + }; +} + +class ScriptSnapshot implements ts.IScriptSnapshot { + + private readonly _text: string; + private readonly _mtime: Date; + + constructor(text: string, mtime: Date) { + this._text = text; + this._mtime = mtime; + } + + getVersion(): string { + return this._mtime.toUTCString(); + } + + getText(start: number, end: number): string { + return this._text.substring(start, end); + } + + getLength(): number { + return this._text.length; + } + + getChangeRange(_oldSnapshot: ts.IScriptSnapshot): ts.TextChangeRange | undefined { + return undefined; + } +} + +class VinylScriptSnapshot extends ScriptSnapshot { + + private readonly _base: string; + + constructor(file: Vinyl) { + super(file.contents!.toString(), file.stat!.mtime); + this._base = file.base; + } + + getBase(): string { + return this._base; + } +} + +class LanguageServiceHost implements ts.LanguageServiceHost { + + private readonly _snapshots: { [path: string]: ScriptSnapshot }; + private readonly _filesInProject: Set<string>; + private readonly _filesAdded: Set<string>; + private readonly _dependencies: utils.graph.Graph<string>; + private readonly _dependenciesRecomputeList: string[]; + private readonly _fileNameToDeclaredModule: { [path: string]: string[] }; + + private _projectVersion: number; + + constructor( + private readonly _cmdLine: ts.ParsedCommandLine, + private readonly _projectPath: string, + private readonly _log: (topic: string, message: string) => void + ) { + this._snapshots = Object.create(null); + this._filesInProject = new Set(_cmdLine.fileNames); + this._filesAdded = new Set(); + this._dependencies = new utils.graph.Graph<string>(s => s); + this._dependenciesRecomputeList = []; + this._fileNameToDeclaredModule = Object.create(null); + + this._projectVersion = 1; + } + + log(_s: string): void { + // console.log(s); + } + + trace(_s: string): void { + // console.log(s); + } + + error(s: string): void { + console.error(s); + } + + getCompilationSettings(): ts.CompilerOptions { + return this._cmdLine.options; + } + + getProjectVersion(): string { + return String(this._projectVersion); + } + + getScriptFileNames(): string[] { + const res = Object.keys(this._snapshots).filter(path => this._filesInProject.has(path) || this._filesAdded.has(path)); + return res; + } + + getScriptVersion(filename: string): string { + filename = normalize(filename); + const result = this._snapshots[filename]; + if (result) { + return result.getVersion(); + } + return 'UNKNWON_FILE_' + Math.random().toString(16).slice(2); + } + + getScriptSnapshot(filename: string, resolve: boolean = true): ScriptSnapshot { + filename = normalize(filename); + let result = this._snapshots[filename]; + if (!result && resolve) { + try { + result = new VinylScriptSnapshot(new Vinyl(<any>{ + path: filename, + contents: readFileSync(filename), + base: this.getCompilationSettings().outDir, + stat: statSync(filename) + })); + this.addScriptSnapshot(filename, result); + } catch (e) { + // ignore + } + } + return result; + } + + private static _declareModule = /declare\s+module\s+('|")(.+)\1/g; + + addScriptSnapshot(filename: string, snapshot: ScriptSnapshot): ScriptSnapshot { + this._projectVersion++; + filename = normalize(filename); + const old = this._snapshots[filename]; + if (!old && !this._filesInProject.has(filename) && !filename.endsWith('.d.ts')) { + // ^^^^^^^^^^^^^^^^^^^^^^^^^^ + // not very proper! + this._filesAdded.add(filename); + } + if (!old || old.getVersion() !== snapshot.getVersion()) { + this._dependenciesRecomputeList.push(filename); + let node = this._dependencies.lookup(filename); + if (node) { + node.outgoing = Object.create(null); + } + + // (cheap) check for declare module + LanguageServiceHost._declareModule.lastIndex = 0; + let match: RegExpExecArray | null | undefined; + while ((match = LanguageServiceHost._declareModule.exec(snapshot.getText(0, snapshot.getLength())))) { + let declaredModules = this._fileNameToDeclaredModule[filename]; + if (!declaredModules) { + this._fileNameToDeclaredModule[filename] = declaredModules = []; + } + declaredModules.push(match[2]); + } + } + this._snapshots[filename] = snapshot; + return old; + } + + removeScriptSnapshot(filename: string): boolean { + this._filesInProject.delete(filename); + this._filesAdded.delete(filename); + this._projectVersion++; + filename = normalize(filename); + delete this._fileNameToDeclaredModule[filename]; + return delete this._snapshots[filename]; + } + + getCurrentDirectory(): string { + return path.dirname(this._projectPath); + } + + getDefaultLibFileName(options: ts.CompilerOptions): string { + return ts.getDefaultLibFilePath(options); + } + + readonly directoryExists = ts.sys.directoryExists; + readonly getDirectories = ts.sys.getDirectories; + readonly fileExists = ts.sys.fileExists; + readonly readFile = ts.sys.readFile; + readonly readDirectory = ts.sys.readDirectory; + + // ---- dependency management + + collectDependents(filename: string, target: string[]): void { + while (this._dependenciesRecomputeList.length) { + this._processFile(this._dependenciesRecomputeList.pop()!); + } + filename = normalize(filename); + const node = this._dependencies.lookup(filename); + if (node) { + utils.collections.forEach(node.incoming, entry => target.push(entry.key)); + } + } + + _processFile(filename: string): void { + if (filename.match(/.*\.d\.ts$/)) { + return; + } + filename = normalize(filename); + const snapshot = this.getScriptSnapshot(filename); + if (!snapshot) { + this._log('processFile', `Missing snapshot for: ${filename}`); + return; + } + const info = ts.preProcessFile(snapshot.getText(0, snapshot.getLength()), true); + + // (1) ///-references + info.referencedFiles.forEach(ref => { + const resolvedPath = path.resolve(path.dirname(filename), ref.fileName); + const normalizedPath = normalize(resolvedPath); + + this._dependencies.inertEdge(filename, normalizedPath); + }); + + // (2) import-require statements + info.importedFiles.forEach(ref => { + const stopDirname = normalize(this.getCurrentDirectory()); + let dirname = filename; + let found = false; + + while (!found && dirname.indexOf(stopDirname) === 0) { + dirname = path.dirname(dirname); + const resolvedPath = path.resolve(dirname, ref.fileName); + const normalizedPath = normalize(resolvedPath); + + if (this.getScriptSnapshot(normalizedPath + '.ts')) { + this._dependencies.inertEdge(filename, normalizedPath + '.ts'); + found = true; + + } else if (this.getScriptSnapshot(normalizedPath + '.d.ts')) { + this._dependencies.inertEdge(filename, normalizedPath + '.d.ts'); + found = true; + } + } + + if (!found) { + for (let key in this._fileNameToDeclaredModule) { + if (this._fileNameToDeclaredModule[key] && ~this._fileNameToDeclaredModule[key].indexOf(ref.fileName)) { + this._dependencies.inertEdge(filename, key); + } + } + } + }); + } +} diff --git a/build/lib/tsb/index.js b/build/lib/tsb/index.js new file mode 100644 index 00000000000..bd30013092c --- /dev/null +++ b/build/lib/tsb/index.js @@ -0,0 +1,89 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.create = void 0; +const Vinyl = require("vinyl"); +const through = require("through"); +const builder = require("./builder"); +const ts = require("typescript"); +const stream_1 = require("stream"); +const path_1 = require("path"); +const utils_1 = require("./utils"); +const fs_1 = require("fs"); +class EmptyDuplex extends stream_1.Duplex { + _write(_chunk, _encoding, callback) { callback(); } + _read() { this.push(null); } +} +function createNullCompiler() { + const result = function () { return new EmptyDuplex(); }; + result.src = () => new EmptyDuplex(); + return result; +} +const _defaultOnError = (err) => console.log(JSON.stringify(err, null, 4)); +function create(projectPath, existingOptions, verbose = false, onError = _defaultOnError) { + function printDiagnostic(diag) { + if (!diag.file || !diag.start) { + onError(ts.flattenDiagnosticMessageText(diag.messageText, '\n')); + } + else { + const lineAndCh = diag.file.getLineAndCharacterOfPosition(diag.start); + onError(utils_1.strings.format('{0}({1},{2}): {3}', diag.file.fileName, lineAndCh.line + 1, lineAndCh.character + 1, ts.flattenDiagnosticMessageText(diag.messageText, '\n'))); + } + } + const parsed = ts.readConfigFile(projectPath, ts.sys.readFile); + if (parsed.error) { + printDiagnostic(parsed.error); + return createNullCompiler(); + } + const cmdLine = ts.parseJsonConfigFileContent(parsed.config, ts.sys, (0, path_1.dirname)(projectPath), existingOptions); + if (cmdLine.errors.length > 0) { + cmdLine.errors.forEach(printDiagnostic); + return createNullCompiler(); + } + const _builder = builder.createTypeScriptBuilder({ verbose }, projectPath, cmdLine); + function createStream(token) { + return through(function (file) { + // give the file to the compiler + if (file.isStream()) { + this.emit('error', 'no support for streams'); + return; + } + _builder.file(file); + }, function () { + // start the compilation process + _builder.build(file => this.queue(file), printDiagnostic, token).catch(e => console.error(e)).then(() => this.queue(null)); + }); + } + const result = (token) => createStream(token); + result.src = (opts) => { + let _pos = 0; + let _fileNames = cmdLine.fileNames.slice(0); + return new class extends stream_1.Readable { + constructor() { + super({ objectMode: true }); + } + _read() { + let more = true; + let path; + for (; more && _pos < _fileNames.length; _pos++) { + path = _fileNames[_pos]; + more = this.push(new Vinyl({ + path, + contents: (0, fs_1.readFileSync)(path), + stat: (0, fs_1.statSync)(path), + cwd: opts && opts.cwd, + base: opts && opts.base || (0, path_1.dirname)(projectPath) + })); + } + if (_pos >= _fileNames.length) { + this.push(null); + } + } + }; + }; + return result; +} +exports.create = create; diff --git a/build/lib/tsb/index.ts b/build/lib/tsb/index.ts new file mode 100644 index 00000000000..6e8e3d6fb77 --- /dev/null +++ b/build/lib/tsb/index.ts @@ -0,0 +1,118 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as Vinyl from 'vinyl'; +import * as through from 'through'; +import * as builder from './builder'; +import * as ts from 'typescript'; +import { Readable, Writable, Duplex } from 'stream'; +import { dirname } from 'path'; +import { strings } from './utils'; +import { readFileSync, statSync } from 'fs'; + +export interface IncrementalCompiler { + (token?: any): Readable & Writable; + src(opts?: { cwd?: string; base?: string }): Readable; +} + +class EmptyDuplex extends Duplex { + _write(_chunk: any, _encoding: string, callback: (err?: Error) => void): void { callback(); } + _read() { this.push(null); } +} + +function createNullCompiler(): IncrementalCompiler { + const result: IncrementalCompiler = function () { return new EmptyDuplex(); }; + result.src = () => new EmptyDuplex(); + return result; +} + +const _defaultOnError = (err: string) => console.log(JSON.stringify(err, null, 4)); + +export function create( + projectPath: string, + existingOptions: Partial<ts.CompilerOptions>, + verbose: boolean = false, + onError: (message: string) => void = _defaultOnError +): IncrementalCompiler { + + function printDiagnostic(diag: ts.Diagnostic): void { + + if (!diag.file || !diag.start) { + onError(ts.flattenDiagnosticMessageText(diag.messageText, '\n')); + } else { + const lineAndCh = diag.file.getLineAndCharacterOfPosition(diag.start); + onError(strings.format('{0}({1},{2}): {3}', + diag.file.fileName, + lineAndCh.line + 1, + lineAndCh.character + 1, + ts.flattenDiagnosticMessageText(diag.messageText, '\n')) + ); + } + } + + const parsed = ts.readConfigFile(projectPath, ts.sys.readFile); + if (parsed.error) { + printDiagnostic(parsed.error); + return createNullCompiler(); + } + + const cmdLine = ts.parseJsonConfigFileContent(parsed.config, ts.sys, dirname(projectPath), existingOptions); + if (cmdLine.errors.length > 0) { + cmdLine.errors.forEach(printDiagnostic); + return createNullCompiler(); + } + + const _builder = builder.createTypeScriptBuilder({ verbose }, projectPath, cmdLine); + + function createStream(token?: builder.CancellationToken): Readable & Writable { + + return through(function (this: through.ThroughStream, file: Vinyl) { + // give the file to the compiler + if (file.isStream()) { + this.emit('error', 'no support for streams'); + return; + } + _builder.file(file); + + }, function (this: { queue(a: any): void }) { + // start the compilation process + _builder.build( + file => this.queue(file), + printDiagnostic, + token + ).catch(e => console.error(e)).then(() => this.queue(null)); + }); + } + + const result = (token: builder.CancellationToken) => createStream(token); + result.src = (opts?: { cwd?: string; base?: string }) => { + let _pos = 0; + let _fileNames = cmdLine.fileNames.slice(0); + return new class extends Readable { + constructor() { + super({ objectMode: true }); + } + _read() { + let more: boolean = true; + let path: string; + for (; more && _pos < _fileNames.length; _pos++) { + path = _fileNames[_pos]; + more = this.push(new Vinyl({ + path, + contents: readFileSync(path), + stat: statSync(path), + cwd: opts && opts.cwd, + base: opts && opts.base || dirname(projectPath) + })); + } + if (_pos >= _fileNames.length) { + this.push(null); + } + } + }; + }; + + return <IncrementalCompiler>result; +} diff --git a/build/lib/tsb/utils.js b/build/lib/tsb/utils.js new file mode 100644 index 00000000000..810297dc246 --- /dev/null +++ b/build/lib/tsb/utils.js @@ -0,0 +1,124 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.graph = exports.strings = exports.collections = void 0; +var collections; +(function (collections) { + const hasOwnProperty = Object.prototype.hasOwnProperty; + function lookup(collection, key) { + if (hasOwnProperty.call(collection, key)) { + return collection[key]; + } + return null; + } + collections.lookup = lookup; + function insert(collection, key, value) { + collection[key] = value; + } + collections.insert = insert; + function lookupOrInsert(collection, key, value) { + if (hasOwnProperty.call(collection, key)) { + return collection[key]; + } + else { + collection[key] = value; + return value; + } + } + collections.lookupOrInsert = lookupOrInsert; + function forEach(collection, callback) { + for (let key in collection) { + if (hasOwnProperty.call(collection, key)) { + callback({ + key: key, + value: collection[key] + }); + } + } + } + collections.forEach = forEach; + function contains(collection, key) { + return hasOwnProperty.call(collection, key); + } + collections.contains = contains; +})(collections = exports.collections || (exports.collections = {})); +var strings; +(function (strings) { + /** + * The empty string. The one and only. + */ + strings.empty = ''; + strings.eolUnix = '\r\n'; + function format(value, ...rest) { + return value.replace(/({\d+})/g, function (match) { + const index = Number(match.substring(1, match.length - 1)); + return String(rest[index]) || match; + }); + } + strings.format = format; +})(strings = exports.strings || (exports.strings = {})); +var graph; +(function (graph) { + function newNode(data) { + return { + data: data, + incoming: {}, + outgoing: {} + }; + } + graph.newNode = newNode; + class Graph { + constructor(_hashFn) { + this._hashFn = _hashFn; + this._nodes = {}; + // empty + } + traverse(start, inwards, callback) { + const startNode = this.lookup(start); + if (!startNode) { + return; + } + this._traverse(startNode, inwards, {}, callback); + } + _traverse(node, inwards, seen, callback) { + const key = this._hashFn(node.data); + if (collections.contains(seen, key)) { + return; + } + seen[key] = true; + callback(node.data); + const nodes = inwards ? node.outgoing : node.incoming; + collections.forEach(nodes, (entry) => this._traverse(entry.value, inwards, seen, callback)); + } + inertEdge(from, to) { + const fromNode = this.lookupOrInsertNode(from); + const toNode = this.lookupOrInsertNode(to); + fromNode.outgoing[this._hashFn(to)] = toNode; + toNode.incoming[this._hashFn(from)] = fromNode; + } + removeNode(data) { + const key = this._hashFn(data); + delete this._nodes[key]; + collections.forEach(this._nodes, (entry) => { + delete entry.value.outgoing[key]; + delete entry.value.incoming[key]; + }); + } + lookupOrInsertNode(data) { + const key = this._hashFn(data); + let node = collections.lookup(this._nodes, key); + if (!node) { + node = newNode(data); + this._nodes[key] = node; + } + return node; + } + lookup(data) { + return collections.lookup(this._nodes, this._hashFn(data)); + } + } + graph.Graph = Graph; +})(graph = exports.graph || (exports.graph = {})); diff --git a/build/lib/tsb/utils.ts b/build/lib/tsb/utils.ts new file mode 100644 index 00000000000..295d873b63a --- /dev/null +++ b/build/lib/tsb/utils.ts @@ -0,0 +1,140 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export module collections { + + const hasOwnProperty = Object.prototype.hasOwnProperty; + + export function lookup<T>(collection: { [keys: string]: T }, key: string): T | null { + if (hasOwnProperty.call(collection, key)) { + return collection[key]; + } + return null; + } + + export function insert<T>(collection: { [keys: string]: T }, key: string, value: T): void { + collection[key] = value; + } + + export function lookupOrInsert<T>(collection: { [keys: string]: T }, key: string, value: T): T { + if (hasOwnProperty.call(collection, key)) { + return collection[key]; + } else { + collection[key] = value; + return value; + } + } + + export function forEach<T>(collection: { [keys: string]: T }, callback: (entry: { key: string; value: T }) => void): void { + for (let key in collection) { + if (hasOwnProperty.call(collection, key)) { + callback({ + key: key, + value: collection[key] + }); + } + } + } + + export function contains(collection: { [keys: string]: any }, key: string): boolean { + return hasOwnProperty.call(collection, key); + } +} + +export module strings { + + /** + * The empty string. The one and only. + */ + export const empty = ''; + + export const eolUnix = '\r\n'; + + export function format(value: string, ...rest: any[]): string { + return value.replace(/({\d+})/g, function (match) { + const index = Number(match.substring(1, match.length - 1)); + return String(rest[index]) || match; + }); + } +} + +export module graph { + + export interface Node<T> { + data: T; + incoming: { [key: string]: Node<T> }; + outgoing: { [key: string]: Node<T> }; + } + + export function newNode<T>(data: T): Node<T> { + return { + data: data, + incoming: {}, + outgoing: {} + }; + } + + export class Graph<T> { + + private _nodes: { [key: string]: Node<T> } = {}; + + constructor(private _hashFn: (element: T) => string) { + // empty + } + + traverse(start: T, inwards: boolean, callback: (data: T) => void): void { + const startNode = this.lookup(start); + if (!startNode) { + return; + } + this._traverse(startNode, inwards, {}, callback); + } + + private _traverse(node: Node<T>, inwards: boolean, seen: { [key: string]: boolean }, callback: (data: T) => void): void { + const key = this._hashFn(node.data); + if (collections.contains(seen, key)) { + return; + } + seen[key] = true; + callback(node.data); + const nodes = inwards ? node.outgoing : node.incoming; + collections.forEach(nodes, (entry) => this._traverse(entry.value, inwards, seen, callback)); + } + + inertEdge(from: T, to: T): void { + const fromNode = this.lookupOrInsertNode(from); + const toNode = this.lookupOrInsertNode(to); + + fromNode.outgoing[this._hashFn(to)] = toNode; + toNode.incoming[this._hashFn(from)] = fromNode; + } + + removeNode(data: T): void { + const key = this._hashFn(data); + delete this._nodes[key]; + collections.forEach(this._nodes, (entry) => { + delete entry.value.outgoing[key]; + delete entry.value.incoming[key]; + }); + } + + lookupOrInsertNode(data: T): Node<T> { + const key = this._hashFn(data); + let node = collections.lookup(this._nodes, key); + + if (!node) { + node = newNode(data); + this._nodes[key] = node; + } + + return node; + } + + lookup(data: T): Node<T> | null { + return collections.lookup(this._nodes, this._hashFn(data)); + } + } + +} diff --git a/build/lib/typings/gulp-tsb.d.ts b/build/lib/typings/gulp-tsb.d.ts deleted file mode 100644 index df9bb34d3fb..00000000000 --- a/build/lib/typings/gulp-tsb.d.ts +++ /dev/null @@ -1,18 +0,0 @@ - - -declare module "gulp-tsb" { - - export interface ICancellationToken { - isCancellationRequested(): boolean; - } - - export interface IncrementalCompiler { - (token?: ICancellationToken): NodeJS.ReadWriteStream; - src(opts?: { - cwd?: string; - base?: string; - }): NodeJS.ReadStream; - } - export function create(projectPath: string, existingOptions: any, verbose?: boolean, onError?: (message: any) => void): IncrementalCompiler; - -} diff --git a/extensions/vscode-colorize-tests/test/colorize-fixtures/test.js b/extensions/vscode-colorize-tests/test/colorize-fixtures/test.js index aa4f58de32d..64e2188f62e 100644 --- a/extensions/vscode-colorize-tests/test/colorize-fixtures/test.js +++ b/extensions/vscode-colorize-tests/test/colorize-fixtures/test.js @@ -4,20 +4,20 @@ *--------------------------------------------------------------------------------------------*/ var gulp = require('gulp'); -var tsb = require('gulp-tsb'); +var tsb = require('../../../../build/lib/tsb'); var util = require('./lib/util'); var watcher = require('./lib/watch'); var assign = require('object-assign'); var compilation = tsb.create(assign({ verbose: true }, require('./tsconfig.json').compilerOptions)); -gulp.task('compile', function() { +gulp.task('compile', function () { return gulp.src('**/*.ts', { base: '.' }) .pipe(compilation()) .pipe(gulp.dest('')); }); -gulp.task('watch', function() { +gulp.task('watch', function () { var src = gulp.src('**/*.ts', { base: '.' }); return watcher('**/*.ts', { base: '.' }) @@ -28,10 +28,10 @@ gulp.task('watch', function() { gulp.task('default', ['compile']); function cloneArray(arr) { - _.foo(); - var r = []; - for (var i = 0, len = arr.length; i < len; i++) { - r[i] = doClone(arr[i]); - } - return r; -}
\ No newline at end of file + _.foo(); + var r = []; + for (var i = 0, len = arr.length; i < len; i++) { + r[i] = doClone(arr[i]); + } + return r; +} diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_js.json b/extensions/vscode-colorize-tests/test/colorize-results/test_js.json index 7cbcf150705..8ef32f838b9 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_js.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_js.json @@ -336,7 +336,7 @@ } }, { - "c": "gulp-tsb", + "c": "../../../../build/lib/tsb", "t": "source.js meta.var.expr.js string.quoted.single.js", "r": { "dark_plus": "string: #CE9178", @@ -1356,6 +1356,18 @@ } }, { + "c": " ", + "t": "source.js meta.function.expression.js", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "hc_light": "default: #292929" + } + }, + { "c": "(", "t": "source.js meta.function.expression.js meta.parameters.js punctuation.definition.parameters.begin.js", "r": { @@ -2052,6 +2064,18 @@ } }, { + "c": " ", + "t": "source.js meta.function.expression.js", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "hc_light": "default: #292929" + } + }, + { "c": "(", "t": "source.js meta.function.expression.js meta.parameters.js punctuation.definition.parameters.begin.js", "r": { @@ -3312,7 +3336,7 @@ } }, { - "c": " ", + "c": "\t", "t": "source.js meta.function.js meta.block.js", "r": { "dark_plus": "default: #D4D4D4", @@ -3384,7 +3408,7 @@ } }, { - "c": " ", + "c": "\t", "t": "source.js meta.function.js meta.block.js", "r": { "dark_plus": "default: #D4D4D4", @@ -3492,7 +3516,7 @@ } }, { - "c": " ", + "c": "\t", "t": "source.js meta.function.js meta.block.js", "r": { "dark_plus": "default: #D4D4D4", @@ -3900,7 +3924,7 @@ } }, { - "c": " ", + "c": "\t\t", "t": "source.js meta.function.js meta.block.js meta.block.js", "r": { "dark_plus": "default: #D4D4D4", @@ -4092,7 +4116,7 @@ } }, { - "c": " ", + "c": "\t", "t": "source.js meta.function.js meta.block.js meta.block.js", "r": { "dark_plus": "default: #D4D4D4", @@ -4116,7 +4140,7 @@ } }, { - "c": " ", + "c": "\t", "t": "source.js meta.function.js meta.block.js", "r": { "dark_plus": "default: #D4D4D4", diff --git a/package.json b/package.json index 142fa369feb..b0de0019cf3 100644 --- a/package.json +++ b/package.json @@ -163,7 +163,6 @@ "gulp-replace": "^0.5.4", "gulp-sourcemaps": "^3.0.0", "gulp-svgmin": "^4.1.0", - "gulp-tsb": "4.0.6", "gulp-untar": "^0.0.7", "gulp-vinyl-zip": "^2.1.2", "husky": "^0.13.1", diff --git a/yarn.lock b/yarn.lock index 3448bd585d3..ec53256d136 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5983,16 +5983,6 @@ gulp-symdest@^1.2.0: queue "^3.1.0" vinyl-fs "^3.0.3" -gulp-tsb@4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/gulp-tsb/-/gulp-tsb-4.0.6.tgz#526f07a2c74e1d2f8932b1500316f2a64b33363c" - integrity sha512-55HSu1zvOOq7D+OqPScvGQaJPKveLbM1T5jEnSJUhbcbwX1Kx0SWv4UVe7MfFcBwFe3wMiWSkKoajNONEv5E9g== - dependencies: - ansi-colors "^1.0.1" - fancy-log "^1.3.2" - through "^2.3.6" - vinyl "^2.1.0" - gulp-untar@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/gulp-untar/-/gulp-untar-0.0.7.tgz#92067d79e0fa1e92d60562a100233a44a5aa08b4" |