From 37fe6093b7f6d494e2ed11f40269ce08dbfeca2a Mon Sep 17 00:00:00 2001 From: Eugene Zolenko Date: Fri, 10 Feb 2017 18:24:28 -0700 Subject: [PATCH] - options --- .vscode/settings.json | 3 +- dist/rollup-plugin-typescript2.cjs.js | 162 +++++++++++++++++++------ dist/rollup-plugin-typescript2.es.js | 167 ++++++++++++++++++++------ package.json | 6 +- rollup.config.js | 3 +- src/cache.ts | 42 +++++-- src/context.ts | 61 ++++++++++ src/index.ts | 84 +++++++------ 8 files changed, 397 insertions(+), 131 deletions(-) create mode 100644 src/context.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index f015944..90e1422 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "typescript.tsdk": "./node_modules/typescript/lib" + "editor.tabSize": 4, + "editor.useTabStops": true } \ No newline at end of file diff --git a/dist/rollup-plugin-typescript2.cjs.js b/dist/rollup-plugin-typescript2.cjs.js index 295d381..ba45c7d 100644 --- a/dist/rollup-plugin-typescript2.cjs.js +++ b/dist/rollup-plugin-typescript2.cjs.js @@ -8,6 +8,7 @@ var graph = require('graphlib'); var hash = require('object-hash'); var rollupPluginutils = require('rollup-pluginutils'); var path = require('path'); +var colors = require('colors/safe'); const __assign = Object.assign || function (target) { for (var source, i = 1; i < arguments.length; i++) { @@ -21,6 +22,42 @@ const __assign = Object.assign || function (target) { return target; }; +var VerbosityLevel; +(function (VerbosityLevel) { + VerbosityLevel[VerbosityLevel["Error"] = 0] = "Error"; + VerbosityLevel[VerbosityLevel["Warning"] = 1] = "Warning"; + VerbosityLevel[VerbosityLevel["Info"] = 2] = "Info"; + VerbosityLevel[VerbosityLevel["Debug"] = 3] = "Debug"; +})(VerbosityLevel || (VerbosityLevel = {})); +var ConsoleContext = (function () { + function ConsoleContext(verbosity, prefix) { + if (prefix === void 0) { prefix = ""; } + this.verbosity = verbosity; + this.prefix = prefix; + } + ConsoleContext.prototype.warn = function (message) { + if (this.verbosity < VerbosityLevel.Warning) + return; + console.log("" + this.prefix + message); + }; + ConsoleContext.prototype.error = function (message) { + if (this.verbosity < VerbosityLevel.Error) + return; + console.log("" + this.prefix + message); + }; + ConsoleContext.prototype.info = function (message) { + if (this.verbosity < VerbosityLevel.Info) + return; + console.log("" + this.prefix + message); + }; + ConsoleContext.prototype.debug = function (message) { + if (this.verbosity < VerbosityLevel.Debug) + return; + console.log("" + this.prefix + message); + }; + return ConsoleContext; +}()); + var LanguageServiceHost = (function () { function LanguageServiceHost(parsedConfig) { this.parsedConfig = parsedConfig; @@ -59,7 +96,15 @@ var LanguageServiceHost = (function () { return LanguageServiceHost; }()); +/** + * Saves data in new cache folder or reads it from old one. + * Avoids perpetually growing cache and situations when things need to consider changed and then reverted data to be changed. + */ var RollingCache = (function () { + /** + * @param cacheRoot: root folder for the cache + * @param checkNewCache: whether to also look in new cache when reading from cache + */ function RollingCache(cacheRoot, checkNewCache) { this.cacheRoot = cacheRoot; this.checkNewCache = checkNewCache; @@ -67,16 +112,25 @@ var RollingCache = (function () { this.newCacheRoot = this.cacheRoot + "/cache_"; fs.emptyDirSync(this.newCacheRoot); } + /** + * @returns true if name exist in old cache (or either old of new cache if checkNewCache is true) + */ RollingCache.prototype.exists = function (name) { if (this.checkNewCache && fs.existsSync(this.newCacheRoot + "/" + name)) return true; return fs.existsSync(this.oldCacheRoot + "/" + name); }; + /** + * @returns true if old cache contains all names and nothing more + */ RollingCache.prototype.match = function (names) { if (!fs.existsSync(this.oldCacheRoot)) - return false; + return names.length === 0; // empty folder matches return _.isEqual(fs.readdirSync(this.oldCacheRoot).sort(), names.sort()); }; + /** + * @returns data for name, must exist in old cache (or either old of new cache if checkNewCache is true) + */ RollingCache.prototype.read = function (name) { if (this.checkNewCache && fs.existsSync(this.newCacheRoot + "/" + name)) return fs.readJsonSync(this.newCacheRoot + "/" + name, "utf8"); @@ -85,54 +139,72 @@ var RollingCache = (function () { RollingCache.prototype.write = function (name, data) { if (data === undefined) return; - fs.writeJson(this.newCacheRoot + "/" + name, data, { encoding: "utf8" }, function () { }); + if (this.checkNewCache) + fs.writeJsonSync(this.newCacheRoot + "/" + name, data); + else + fs.writeJson(this.newCacheRoot + "/" + name, data, { encoding: "utf8" }, function () { }); }; RollingCache.prototype.touch = function (name) { - fs.ensureFile(this.newCacheRoot + "/" + name, function () { }); + if (this.checkNewCache) + fs.ensureFileSync(this.newCacheRoot + "/" + name); + else + fs.ensureFile(this.newCacheRoot + "/" + name, function () { }); }; + /** + * clears old cache and moves new in its place + */ RollingCache.prototype.roll = function () { var _this = this; fs.remove(this.oldCacheRoot, function () { - fs.move(_this.newCacheRoot, _this.oldCacheRoot, function () { }); + fs.move(_this.newCacheRoot, _this.oldCacheRoot, function () { }); }); }; return RollingCache; }()); var Cache = (function () { - function Cache(host, cache, options, rootFilenames) { + function Cache(host, cache, options, rootFilenames, context) { var _this = this; this.host = host; this.options = options; + this.context = context; this.cacheVersion = "1"; - this.typesDirty = false; + this.ambientTypesDirty = false; this.cacheDir = cache + "/" + hash.sha1({ version: this.cacheVersion, rootFilenames: rootFilenames, options: this.options }); this.codeCache = new RollingCache(this.cacheDir + "/code", true); this.typesCache = new RollingCache(this.cacheDir + "/types", false); this.diagnosticsCache = new RollingCache(this.cacheDir + "/diagnostics", false); this.dependencyTree = new graph.Graph({ directed: true }); this.dependencyTree.setDefaultNodeLabel(function (_node) { return { dirty: false }; }); - this.types = _.filter(rootFilenames, function (file) { return _.endsWith(file, ".d.ts"); }) + this.ambientTypes = _.filter(rootFilenames, function (file) { return _.endsWith(file, ".d.ts"); }) .map(function (id) { return { id: id, snapshot: _this.host.getScriptSnapshot(id) }; }); } + Cache.prototype.clean = function () { + this.context.info("cleaning cache: " + this.cacheDir); + fs.emptyDirSync(this.cacheDir); + }; Cache.prototype.walkTree = function (cb) { var acyclic = graph.alg.isAcyclic(this.dependencyTree); if (acyclic) { _.each(graph.alg.topsort(this.dependencyTree), function (id) { return cb(id); }); return; } + this.context.info("import tree has cycles"); _.each(this.dependencyTree.nodes(), function (id) { return cb(id); }); }; Cache.prototype.setDependency = function (importee, importer) { // importee -> importer + this.context.debug(importee + " -> " + importer); this.dependencyTree.setEdge(importer, importee); }; Cache.prototype.compileDone = function () { var _this = this; - var typeNames = _.filter(this.types, function (snaphot) { return snaphot.snapshot !== undefined; }) + var typeNames = _.filter(this.ambientTypes, function (snaphot) { return snaphot.snapshot !== undefined; }) .map(function (snaphot) { return _this.makeName(snaphot.id, snaphot.snapshot); }); // types dirty if any d.ts changed, added or removed - this.typesDirty = !this.typesCache.match(typeNames); + this.ambientTypesDirty = !this.typesCache.match(typeNames); + if (this.ambientTypesDirty) + this.context.info("ambient types changed, redoing all diagnostics"); _.each(typeNames, function (name) { return _this.typesCache.touch(name); }); }; Cache.prototype.diagnosticsDone = function () { @@ -143,12 +215,13 @@ var Cache = (function () { Cache.prototype.getCompiled = function (id, snapshot, transform) { var name = this.makeName(id, snapshot); if (!this.codeCache.exists(name) || this.isDirty(id, snapshot, false)) { - console.log("compile cache miss: " + id); + this.context.debug("fresh transpile for: " + id); var data_1 = transform(); this.codeCache.write(name, data_1); this.markAsDirty(id, snapshot); return data_1; } + this.context.debug("old transpile for: " + id); var data = this.codeCache.read(name); this.codeCache.write(name, data); return data; @@ -156,17 +229,19 @@ var Cache = (function () { Cache.prototype.getDiagnostics = function (id, snapshot, check) { var name = this.makeName(id, snapshot); if (!this.diagnosticsCache.exists(name) || this.isDirty(id, snapshot, true)) { - console.log("diag cache miss: " + id); + this.context.debug("fresh diagnostics for: " + id); var data_2 = this.convert(check()); this.diagnosticsCache.write(name, data_2); this.markAsDirty(id, snapshot); return data_2; } + this.context.debug("old diagnostics for: " + id); var data = this.diagnosticsCache.read(name); this.diagnosticsCache.write(name, data); return data; }; Cache.prototype.markAsDirty = function (id, _snapshot) { + this.context.debug("changed: " + id); this.dependencyTree.setNode(id, { dirty: true }); }; // returns true if node or any of its imports or any of global types changed @@ -177,7 +252,7 @@ var Cache = (function () { return false; if (!checkImports || label.dirty) return label.dirty; - if (this.typesDirty) + if (this.ambientTypesDirty) return true; var dependencies = graph.alg.dijkstra(this.dependencyTree, id); return _.some(dependencies, function (dependency, node) { @@ -186,7 +261,7 @@ var Cache = (function () { var l = _this.dependencyTree.node(node); var dirty = l === undefined ? true : l.dirty; if (dirty) - console.log(("dirty: " + id + " -> " + node).gray); + _this.context.debug("import changed: " + id + " -> " + node); return dirty; }); }; @@ -197,11 +272,11 @@ var Cache = (function () { Cache.prototype.convert = function (data) { return _.map(data, function (diagnostic) { var entry = { - flatMessage: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n").yellow, + flatMessage: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"), }; if (diagnostic.file) { var _a = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start), line = _a.line, character = _a.character; - entry.fileLine = (diagnostic.file.fileName + " (" + (line + 1) + "," + (character + 1) + ")").white; + entry.fileLine = diagnostic.file.fileName + " (" + (line + 1) + "," + (character + 1) + ")"; } return entry; }); @@ -223,17 +298,15 @@ function getOptionsOverrides() { // MIT Licenced function findFile(cwd, filename) { var fp = cwd ? (cwd + "/" + filename) : filename; - if (fs.existsSync(fp)) { + if (fs.existsSync(fp)) return fp; - } var segs = cwd.split(path.sep); var len = segs.length; while (len--) { cwd = segs.slice(0, len).join("/"); fp = cwd + "/" + filename; - if (fs.existsSync(fp)) { + if (fs.existsSync(fp)) return fp; - } } return null; } @@ -258,22 +331,33 @@ function parseTsConfig() { var configParseResult = ts.parseJsonConfigFileContent(result.config, ts.sys, path.dirname(fileName), getOptionsOverrides(), fileName); return configParseResult; } -function printDiagnostics(diagnostics) { +function printDiagnostics(context, diagnostics) { _.each(diagnostics, function (diagnostic) { if (diagnostic.fileLine) - console.log(diagnostic.fileLine + ": " + diagnostic.flatMessage); + context.warn(diagnostic.fileLine + ": " + colors.yellow(diagnostic.flatMessage)); else - console.log(diagnostic.flatMessage); + context.warn(colors.yellow(diagnostic.flatMessage)); }); } function typescript(options) { options = __assign({}, options); - var filter$$1 = rollupPluginutils.createFilter(options.include || ["*.ts+(|x)", "**/*.ts+(|x)"], options.exclude || ["*.d.ts", "**/*.d.ts"]); + _.defaults(options, { + check: true, + verbose: VerbosityLevel.Info, + clean: false, + cacheRoot: process.cwd() + "/.rts2_cache", + include: ["*.ts+(|x)", "**/*.ts+(|x)"], + exclude: ["*.d.ts", "**/*.d.ts"], + }); + var filter$$1 = rollupPluginutils.createFilter(options.include, options.exclude); var parsedConfig = parseTsConfig(); var servicesHost = new LanguageServiceHost(parsedConfig); var services = ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); - var cache = new Cache(servicesHost, process.cwd() + "/.rts2_cache", parsedConfig.options, parsedConfig.fileNames); + var context = new ConsoleContext(options.verbose, ""); + var cache = new Cache(servicesHost, options.cacheRoot, parsedConfig.options, parsedConfig.fileNames, context); + if (options.clean) + cache.clean(); return { resolveId: function (importee, importer) { if (importee === TSLIB) @@ -304,7 +388,7 @@ function typescript(options) { var result = cache.getCompiled(id, snapshot, function () { var output = services.getEmitOutput(id); if (output.emitSkipped) - _this.error({ message: "failed to transpile " + id }); + _this.error({ message: colors.red("failed to transpile " + id) }); var transpiled = _.find(output.outputFiles, function (entry) { return _.endsWith(entry.name, ".js"); }); var map$$1 = _.find(output.outputFiles, function (entry) { return _.endsWith(entry.name, ".map"); }); return { @@ -316,20 +400,22 @@ function typescript(options) { }, outro: function () { cache.compileDone(); - cache.walkTree(function (id) { - var snapshot = servicesHost.getScriptSnapshot(id); - if (!snapshot) { - console.log("failed lo load snapshot for " + id); - return; - } - var diagnostics = cache.getDiagnostics(id, snapshot, function () { - return services - .getCompilerOptionsDiagnostics() - .concat(services.getSyntacticDiagnostics(id)) - .concat(services.getSemanticDiagnostics(id)); + if (options.check) { + cache.walkTree(function (id) { + var snapshot = servicesHost.getScriptSnapshot(id); + if (!snapshot) { + context.error(colors.red("failed lo load snapshot for " + id)); + return; + } + var diagnostics = cache.getDiagnostics(id, snapshot, function () { + return services + .getCompilerOptionsDiagnostics() + .concat(services.getSyntacticDiagnostics(id)) + .concat(services.getSemanticDiagnostics(id)); + }); + printDiagnostics(context, diagnostics); }); - printDiagnostics(diagnostics); - }); + } cache.diagnosticsDone(); }, }; diff --git a/dist/rollup-plugin-typescript2.es.js b/dist/rollup-plugin-typescript2.es.js index 82c08aa..c482424 100644 --- a/dist/rollup-plugin-typescript2.es.js +++ b/dist/rollup-plugin-typescript2.es.js @@ -1,9 +1,9 @@ /* eslint-disable */ -import { emptyDirSync, ensureFile, existsSync, move, readFileSync, readJsonSync, readdirSync, remove, writeJson } from 'fs-extra'; +import { emptyDirSync, ensureFile, ensureFileSync, existsSync, move, readFileSync, readJsonSync, readdirSync, remove, writeJson, writeJsonSync } from 'fs-extra'; import * as fs from 'fs-extra'; import { ModuleKind, ScriptSnapshot, createDocumentRegistry, createLanguageService, flattenDiagnosticMessageText, getDefaultLibFilePath, nodeModuleNameResolver, parseConfigFileTextToJson, parseJsonConfigFileContent, sys } from 'typescript'; import * as ts from 'typescript'; -import { each, endsWith, filter, find, has, isEqual, map, some } from 'lodash'; +import { defaults, each, endsWith, filter, find, has, isEqual, map, some } from 'lodash'; import * as _ from 'lodash'; import { Graph, alg } from 'graphlib'; import * as graph from 'graphlib'; @@ -12,6 +12,8 @@ import * as hash from 'object-hash'; import { createFilter } from 'rollup-pluginutils'; import { dirname, sep } from 'path'; import * as path from 'path'; +import { red, yellow } from 'colors/safe'; +import * as colors from 'colors/safe'; const __assign = Object.assign || function (target) { for (var source, i = 1; i < arguments.length; i++) { @@ -25,6 +27,42 @@ const __assign = Object.assign || function (target) { return target; }; +var VerbosityLevel; +(function (VerbosityLevel) { + VerbosityLevel[VerbosityLevel["Error"] = 0] = "Error"; + VerbosityLevel[VerbosityLevel["Warning"] = 1] = "Warning"; + VerbosityLevel[VerbosityLevel["Info"] = 2] = "Info"; + VerbosityLevel[VerbosityLevel["Debug"] = 3] = "Debug"; +})(VerbosityLevel || (VerbosityLevel = {})); +var ConsoleContext = (function () { + function ConsoleContext(verbosity, prefix) { + if (prefix === void 0) { prefix = ""; } + this.verbosity = verbosity; + this.prefix = prefix; + } + ConsoleContext.prototype.warn = function (message) { + if (this.verbosity < VerbosityLevel.Warning) + return; + console.log("" + this.prefix + message); + }; + ConsoleContext.prototype.error = function (message) { + if (this.verbosity < VerbosityLevel.Error) + return; + console.log("" + this.prefix + message); + }; + ConsoleContext.prototype.info = function (message) { + if (this.verbosity < VerbosityLevel.Info) + return; + console.log("" + this.prefix + message); + }; + ConsoleContext.prototype.debug = function (message) { + if (this.verbosity < VerbosityLevel.Debug) + return; + console.log("" + this.prefix + message); + }; + return ConsoleContext; +}()); + var LanguageServiceHost = (function () { function LanguageServiceHost(parsedConfig) { this.parsedConfig = parsedConfig; @@ -63,7 +101,15 @@ var LanguageServiceHost = (function () { return LanguageServiceHost; }()); +/** + * Saves data in new cache folder or reads it from old one. + * Avoids perpetually growing cache and situations when things need to consider changed and then reverted data to be changed. + */ var RollingCache = (function () { + /** + * @param cacheRoot: root folder for the cache + * @param checkNewCache: whether to also look in new cache when reading from cache + */ function RollingCache(cacheRoot, checkNewCache) { this.cacheRoot = cacheRoot; this.checkNewCache = checkNewCache; @@ -71,16 +117,25 @@ var RollingCache = (function () { this.newCacheRoot = this.cacheRoot + "/cache_"; emptyDirSync(this.newCacheRoot); } + /** + * @returns true if name exist in old cache (or either old of new cache if checkNewCache is true) + */ RollingCache.prototype.exists = function (name) { if (this.checkNewCache && existsSync(this.newCacheRoot + "/" + name)) return true; return existsSync(this.oldCacheRoot + "/" + name); }; + /** + * @returns true if old cache contains all names and nothing more + */ RollingCache.prototype.match = function (names) { if (!existsSync(this.oldCacheRoot)) - return false; + return names.length === 0; // empty folder matches return isEqual(readdirSync(this.oldCacheRoot).sort(), names.sort()); }; + /** + * @returns data for name, must exist in old cache (or either old of new cache if checkNewCache is true) + */ RollingCache.prototype.read = function (name) { if (this.checkNewCache && existsSync(this.newCacheRoot + "/" + name)) return readJsonSync(this.newCacheRoot + "/" + name, "utf8"); @@ -89,54 +144,72 @@ var RollingCache = (function () { RollingCache.prototype.write = function (name, data) { if (data === undefined) return; - writeJson(this.newCacheRoot + "/" + name, data, { encoding: "utf8" }, function () { }); + if (this.checkNewCache) + writeJsonSync(this.newCacheRoot + "/" + name, data); + else + writeJson(this.newCacheRoot + "/" + name, data, { encoding: "utf8" }, function () { }); }; RollingCache.prototype.touch = function (name) { - ensureFile(this.newCacheRoot + "/" + name, function () { }); + if (this.checkNewCache) + ensureFileSync(this.newCacheRoot + "/" + name); + else + ensureFile(this.newCacheRoot + "/" + name, function () { }); }; + /** + * clears old cache and moves new in its place + */ RollingCache.prototype.roll = function () { var _this = this; remove(this.oldCacheRoot, function () { - move(_this.newCacheRoot, _this.oldCacheRoot, function () { }); + move(_this.newCacheRoot, _this.oldCacheRoot, function () { }); }); }; return RollingCache; }()); var Cache = (function () { - function Cache(host, cache, options, rootFilenames) { + function Cache(host, cache, options, rootFilenames, context) { var _this = this; this.host = host; this.options = options; + this.context = context; this.cacheVersion = "1"; - this.typesDirty = false; + this.ambientTypesDirty = false; this.cacheDir = cache + "/" + sha1({ version: this.cacheVersion, rootFilenames: rootFilenames, options: this.options }); this.codeCache = new RollingCache(this.cacheDir + "/code", true); this.typesCache = new RollingCache(this.cacheDir + "/types", false); this.diagnosticsCache = new RollingCache(this.cacheDir + "/diagnostics", false); this.dependencyTree = new Graph({ directed: true }); this.dependencyTree.setDefaultNodeLabel(function (_node) { return { dirty: false }; }); - this.types = filter(rootFilenames, function (file) { return endsWith(file, ".d.ts"); }) + this.ambientTypes = filter(rootFilenames, function (file) { return endsWith(file, ".d.ts"); }) .map(function (id) { return { id: id, snapshot: _this.host.getScriptSnapshot(id) }; }); } + Cache.prototype.clean = function () { + this.context.info("cleaning cache: " + this.cacheDir); + emptyDirSync(this.cacheDir); + }; Cache.prototype.walkTree = function (cb) { var acyclic = alg.isAcyclic(this.dependencyTree); if (acyclic) { each(alg.topsort(this.dependencyTree), function (id) { return cb(id); }); return; } + this.context.info("import tree has cycles"); each(this.dependencyTree.nodes(), function (id) { return cb(id); }); }; Cache.prototype.setDependency = function (importee, importer) { // importee -> importer + this.context.debug(importee + " -> " + importer); this.dependencyTree.setEdge(importer, importee); }; Cache.prototype.compileDone = function () { var _this = this; - var typeNames = filter(this.types, function (snaphot) { return snaphot.snapshot !== undefined; }) + var typeNames = filter(this.ambientTypes, function (snaphot) { return snaphot.snapshot !== undefined; }) .map(function (snaphot) { return _this.makeName(snaphot.id, snaphot.snapshot); }); // types dirty if any d.ts changed, added or removed - this.typesDirty = !this.typesCache.match(typeNames); + this.ambientTypesDirty = !this.typesCache.match(typeNames); + if (this.ambientTypesDirty) + this.context.info("ambient types changed, redoing all diagnostics"); each(typeNames, function (name) { return _this.typesCache.touch(name); }); }; Cache.prototype.diagnosticsDone = function () { @@ -147,12 +220,13 @@ var Cache = (function () { Cache.prototype.getCompiled = function (id, snapshot, transform) { var name = this.makeName(id, snapshot); if (!this.codeCache.exists(name) || this.isDirty(id, snapshot, false)) { - console.log("compile cache miss: " + id); + this.context.debug("fresh transpile for: " + id); var data_1 = transform(); this.codeCache.write(name, data_1); this.markAsDirty(id, snapshot); return data_1; } + this.context.debug("old transpile for: " + id); var data = this.codeCache.read(name); this.codeCache.write(name, data); return data; @@ -160,17 +234,19 @@ var Cache = (function () { Cache.prototype.getDiagnostics = function (id, snapshot, check) { var name = this.makeName(id, snapshot); if (!this.diagnosticsCache.exists(name) || this.isDirty(id, snapshot, true)) { - console.log("diag cache miss: " + id); + this.context.debug("fresh diagnostics for: " + id); var data_2 = this.convert(check()); this.diagnosticsCache.write(name, data_2); this.markAsDirty(id, snapshot); return data_2; } + this.context.debug("old diagnostics for: " + id); var data = this.diagnosticsCache.read(name); this.diagnosticsCache.write(name, data); return data; }; Cache.prototype.markAsDirty = function (id, _snapshot) { + this.context.debug("changed: " + id); this.dependencyTree.setNode(id, { dirty: true }); }; // returns true if node or any of its imports or any of global types changed @@ -181,7 +257,7 @@ var Cache = (function () { return false; if (!checkImports || label.dirty) return label.dirty; - if (this.typesDirty) + if (this.ambientTypesDirty) return true; var dependencies = alg.dijkstra(this.dependencyTree, id); return some(dependencies, function (dependency, node) { @@ -190,7 +266,7 @@ var Cache = (function () { var l = _this.dependencyTree.node(node); var dirty = l === undefined ? true : l.dirty; if (dirty) - console.log(("dirty: " + id + " -> " + node).gray); + _this.context.debug("import changed: " + id + " -> " + node); return dirty; }); }; @@ -201,11 +277,11 @@ var Cache = (function () { Cache.prototype.convert = function (data) { return map(data, function (diagnostic) { var entry = { - flatMessage: flattenDiagnosticMessageText(diagnostic.messageText, "\n").yellow, + flatMessage: flattenDiagnosticMessageText(diagnostic.messageText, "\n"), }; if (diagnostic.file) { var _a = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start), line = _a.line, character = _a.character; - entry.fileLine = (diagnostic.file.fileName + " (" + (line + 1) + "," + (character + 1) + ")").white; + entry.fileLine = diagnostic.file.fileName + " (" + (line + 1) + "," + (character + 1) + ")"; } return entry; }); @@ -227,17 +303,15 @@ function getOptionsOverrides() { // MIT Licenced function findFile(cwd, filename) { var fp = cwd ? (cwd + "/" + filename) : filename; - if (existsSync(fp)) { + if (existsSync(fp)) return fp; - } var segs = cwd.split(sep); var len = segs.length; while (len--) { cwd = segs.slice(0, len).join("/"); fp = cwd + "/" + filename; - if (existsSync(fp)) { + if (existsSync(fp)) return fp; - } } return null; } @@ -262,22 +336,33 @@ function parseTsConfig() { var configParseResult = parseJsonConfigFileContent(result.config, sys, dirname(fileName), getOptionsOverrides(), fileName); return configParseResult; } -function printDiagnostics(diagnostics) { +function printDiagnostics(context, diagnostics) { each(diagnostics, function (diagnostic) { if (diagnostic.fileLine) - console.log(diagnostic.fileLine + ": " + diagnostic.flatMessage); + context.warn(diagnostic.fileLine + ": " + yellow(diagnostic.flatMessage)); else - console.log(diagnostic.flatMessage); + context.warn(yellow(diagnostic.flatMessage)); }); } function typescript(options) { options = __assign({}, options); - var filter$$1 = createFilter(options.include || ["*.ts+(|x)", "**/*.ts+(|x)"], options.exclude || ["*.d.ts", "**/*.d.ts"]); + defaults(options, { + check: true, + verbose: VerbosityLevel.Info, + clean: false, + cacheRoot: process.cwd() + "/.rts2_cache", + include: ["*.ts+(|x)", "**/*.ts+(|x)"], + exclude: ["*.d.ts", "**/*.d.ts"], + }); + var filter$$1 = createFilter(options.include, options.exclude); var parsedConfig = parseTsConfig(); var servicesHost = new LanguageServiceHost(parsedConfig); var services = createLanguageService(servicesHost, createDocumentRegistry()); - var cache = new Cache(servicesHost, process.cwd() + "/.rts2_cache", parsedConfig.options, parsedConfig.fileNames); + var context = new ConsoleContext(options.verbose, ""); + var cache = new Cache(servicesHost, options.cacheRoot, parsedConfig.options, parsedConfig.fileNames, context); + if (options.clean) + cache.clean(); return { resolveId: function (importee, importer) { if (importee === TSLIB) @@ -308,7 +393,7 @@ function typescript(options) { var result = cache.getCompiled(id, snapshot, function () { var output = services.getEmitOutput(id); if (output.emitSkipped) - _this.error({ message: "failed to transpile " + id }); + _this.error({ message: red("failed to transpile " + id) }); var transpiled = find(output.outputFiles, function (entry) { return endsWith(entry.name, ".js"); }); var map$$1 = find(output.outputFiles, function (entry) { return endsWith(entry.name, ".map"); }); return { @@ -320,20 +405,22 @@ function typescript(options) { }, outro: function () { cache.compileDone(); - cache.walkTree(function (id) { - var snapshot = servicesHost.getScriptSnapshot(id); - if (!snapshot) { - console.log("failed lo load snapshot for " + id); - return; - } - var diagnostics = cache.getDiagnostics(id, snapshot, function () { - return services - .getCompilerOptionsDiagnostics() - .concat(services.getSyntacticDiagnostics(id)) - .concat(services.getSemanticDiagnostics(id)); + if (options.check) { + cache.walkTree(function (id) { + var snapshot = servicesHost.getScriptSnapshot(id); + if (!snapshot) { + context.error(red("failed lo load snapshot for " + id)); + return; + } + var diagnostics = cache.getDiagnostics(id, snapshot, function () { + return services + .getCompilerOptionsDiagnostics() + .concat(services.getSyntacticDiagnostics(id)) + .concat(services.getSemanticDiagnostics(id)); + }); + printDiagnostics(context, diagnostics); }); - printDiagnostics(diagnostics); - }); + } cache.diagnosticsDone(); }, }; diff --git a/package.json b/package.json index d180bec..9c87003 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,7 @@ "scripts": { "prebuild": "rimraf dist/*", "build": "rollup -c", - "lint": "tslint -c ./tslint.json src/*.ts", - "postinstall": "typings install" + "lint": "tslint -c ./tslint.json src/*.ts" }, "dependencies": { "colors": "^1.1.2", @@ -33,7 +32,8 @@ "tslib": "^1.5.0" }, "peerDependencies": { - "typescript": "^2.0" + "typescript": "^2.0", + "tslib": "^1.5.0" }, "devDependencies": { "@alexlur/rollup-plugin-typescript": "^0.8.1", diff --git a/rollup.config.js b/rollup.config.js index 724cc81..84f1099 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -13,7 +13,8 @@ export default { 'typescript', 'lodash', 'graphlib', - 'object-hash' + 'object-hash', + 'colors/safe' ], plugins: [ diff --git a/src/cache.ts b/src/cache.ts index bd6fdc9..8da7928 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -1,9 +1,10 @@ +import { IContext } from "./context"; import * as ts from "typescript"; import * as graph from "graphlib"; import * as hash from "object-hash"; import * as _ from "lodash"; -import * as colors from "colors/safe"; import { RollingCache } from "./rollingcache"; +import * as fs from "fs-extra"; export interface ICode { @@ -32,14 +33,14 @@ export class Cache { private cacheVersion = "1"; private dependencyTree: graph.Graph; - private types: ITypeSnapshot[]; - private typesDirty = false; + private ambientTypes: ITypeSnapshot[]; + private ambientTypesDirty = false; private cacheDir: string; private codeCache: RollingCache; private typesCache: RollingCache; private diagnosticsCache: RollingCache; - constructor(private host: ts.LanguageServiceHost, cache: string, private options: ts.CompilerOptions, rootFilenames: string[]) + constructor(private host: ts.LanguageServiceHost, cache: string, private options: ts.CompilerOptions, rootFilenames: string[], private context: IContext) { this.cacheDir = `${cache}/${hash.sha1({ version: this.cacheVersion, rootFilenames, options: this.options })}`; @@ -50,11 +51,17 @@ export class Cache this.dependencyTree = new graph.Graph({ directed: true }); this.dependencyTree.setDefaultNodeLabel((_node: string) => { return { dirty: false }; }); - this.types = _ + this.ambientTypes = _ .filter(rootFilenames, (file) => _.endsWith(file, ".d.ts")) .map((id) => { return { id, snapshot: this.host.getScriptSnapshot(id) }; }); } + public clean() + { + this.context.info(`cleaning cache: ${this.cacheDir}`); + fs.emptyDirSync(this.cacheDir); + } + public walkTree(cb: (id: string) => void | false): void { const acyclic = graph.alg.isAcyclic(this.dependencyTree); @@ -65,23 +72,29 @@ export class Cache return; } + this.context.info("import tree has cycles"); + _.each(this.dependencyTree.nodes(), (id: string) => cb(id)); } public setDependency(importee: string, importer: string): void { // importee -> importer + this.context.debug(`${importee} -> ${importer}`); this.dependencyTree.setEdge(importer, importee); } public compileDone(): void { let typeNames = _ - .filter(this.types, (snaphot) => snaphot.snapshot !== undefined) + .filter(this.ambientTypes, (snaphot) => snaphot.snapshot !== undefined) .map((snaphot) => this.makeName(snaphot.id, snaphot.snapshot!)); // types dirty if any d.ts changed, added or removed - this.typesDirty = !this.typesCache.match(typeNames); + this.ambientTypesDirty = !this.typesCache.match(typeNames); + + if (this.ambientTypesDirty) + this.context.info("ambient types changed, redoing all diagnostics"); _.each(typeNames, (name) => this.typesCache.touch(name)); } @@ -99,13 +112,16 @@ export class Cache if (!this.codeCache.exists(name) || this.isDirty(id, snapshot, false)) { - console.log(`compile cache miss: ${id}`); + this.context.debug(`fresh transpile for: ${id}`); + let data = transform(); this.codeCache.write(name, data); this.markAsDirty(id, snapshot); return data; } + this.context.debug(`old transpile for: ${id}`); + let data = this.codeCache.read(name); this.codeCache.write(name, data); return data; @@ -117,13 +133,16 @@ export class Cache if (!this.diagnosticsCache.exists(name) || this.isDirty(id, snapshot, true)) { - console.log(`diag cache miss: ${id}`); + this.context.debug(`fresh diagnostics for: ${id}`); + let data = this.convert(check()); this.diagnosticsCache.write(name, data); this.markAsDirty(id, snapshot); return data; } + this.context.debug(`old diagnostics for: ${id}`); + let data = this.diagnosticsCache.read(name); this.diagnosticsCache.write(name, data); return data; @@ -131,6 +150,7 @@ export class Cache private markAsDirty(id: string, _snapshot: ts.IScriptSnapshot): void { + this.context.debug(`changed: ${id}`); this.dependencyTree.setNode(id, { dirty: true }); } @@ -145,7 +165,7 @@ export class Cache if (!checkImports || label.dirty) return label.dirty; - if (this.typesDirty) + if (this.ambientTypesDirty) return true; let dependencies = graph.alg.dijkstra(this.dependencyTree, id); @@ -159,7 +179,7 @@ export class Cache let dirty = l === undefined ? true : l.dirty; if (dirty) - console.log(colors.gray(`dirty: ${id} -> ${node}`)); + this.context.debug(`import changed: ${id} -> ${node}`); return dirty; }); diff --git a/src/context.ts b/src/context.ts new file mode 100644 index 0000000..3ca05be --- /dev/null +++ b/src/context.ts @@ -0,0 +1,61 @@ +interface Message +{ + message: string; +} + +export interface IRollupContext +{ + warn(message: Message | string): void; + error(message: Message | string): void; +} + +export interface IContext +{ + warn(message: string): void; + error(message: string): void; + info(message: string): void; + debug(message: string): void; +} + +export enum VerbosityLevel +{ + Error = 0, + Warning, + Info, + Debug, +} + +export class ConsoleContext implements IContext +{ + constructor(private verbosity: VerbosityLevel, private prefix: string = "") + { + } + + public warn(message: string): void + { + if (this.verbosity < VerbosityLevel.Warning) + return; + console.log(`${this.prefix}${message}`); + } + + public error(message: string): void + { + if (this.verbosity < VerbosityLevel.Error) + return; + console.log(`${this.prefix}${message}`); + } + + public info(message: string): void + { + if (this.verbosity < VerbosityLevel.Info) + return; + console.log(`${this.prefix}${message}`); + } + + public debug(message: string): void + { + if (this.verbosity < VerbosityLevel.Debug) + return; + console.log(`${this.prefix}${message}`); + } +} diff --git a/src/index.ts b/src/index.ts index 363e62c..1e824d5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +import { IContext, ConsoleContext, IRollupContext, VerbosityLevel } from "./context"; import { LanguageServiceHost } from "./host"; import { Cache, ICode, IDiagnostics } from "./cache"; import * as ts from "typescript"; @@ -26,9 +27,7 @@ function findFile(cwd: string, filename: string) let fp = cwd ? (cwd + "/" + filename) : filename; if (fs.existsSync(fp)) - { return fp; - } const segs = cwd.split(path.sep); let len = segs.length; @@ -38,9 +37,7 @@ function findFile(cwd: string, filename: string) cwd = segs.slice(0, len).join("/"); fp = cwd + "/" + filename; if (fs.existsSync(fp)) - { return fp; - } } return null; @@ -73,37 +70,42 @@ function parseTsConfig() return configParseResult; } -interface Message -{ - message: string; -} -interface Context -{ - warn(message: Message): void; - error(message: Message): void; -} -function printDiagnostics(diagnostics: IDiagnostics[]) +function printDiagnostics(context: IContext, diagnostics: IDiagnostics[]) { _.each(diagnostics, (diagnostic) => { if (diagnostic.fileLine) - console.log(`${diagnostic.fileLine}: ${colors.yellow(diagnostic.flatMessage)}`); + context.warn(`${diagnostic.fileLine}: ${colors.yellow(diagnostic.flatMessage)}`); else - console.log(colors.yellow(diagnostic.flatMessage)); + context.warn(colors.yellow(diagnostic.flatMessage)); }); }; interface IOptions { - include?: string; - exclude?: string; + include: string; + exclude: string; + check: boolean; + verbose: number; + clean: boolean; + cacheRoot: string; } export default function typescript (options: IOptions) { options = { ... options }; - const filter = createFilter(options.include || [ "*.ts+(|x)", "**/*.ts+(|x)" ], options.exclude || [ "*.d.ts", "**/*.d.ts" ]); + _.defaults(options, + { + check: true, + verbose: VerbosityLevel.Info, + clean: false, + cacheRoot: `${process.cwd()}/.rts2_cache`, + include: [ "*.ts+(|x)", "**/*.ts+(|x)" ], + exclude: [ "*.d.ts", "**/*.d.ts" ], + }); + + const filter = createFilter(options.include, options.exclude); let parsedConfig = parseTsConfig(); @@ -111,7 +113,12 @@ export default function typescript (options: IOptions) const services = ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); - const cache = new Cache(servicesHost, `${process.cwd()}/.rts2_cache`, parsedConfig.options, parsedConfig.fileNames); + const context = new ConsoleContext(options.verbose, ""); + + const cache = new Cache(servicesHost, options.cacheRoot, parsedConfig.options, parsedConfig.fileNames, context); + + if (options.clean) + cache.clean(); return { @@ -149,7 +156,7 @@ export default function typescript (options: IOptions) return undefined; }, - transform(this: Context, code: string, id: string): ICode | undefined + transform(this: IRollupContext, code: string, id: string): ICode | undefined { if (!filter(id)) return undefined; @@ -178,26 +185,29 @@ export default function typescript (options: IOptions) { cache.compileDone(); - cache.walkTree((id: string) => + if (options.check) { - const snapshot = servicesHost.getScriptSnapshot(id); - - if (!snapshot) + cache.walkTree((id: string) => { - console.log(colors.red(`failed lo load snapshot for ${id}`)); - return; - } + const snapshot = servicesHost.getScriptSnapshot(id); - const diagnostics = cache.getDiagnostics(id, snapshot, () => - { - return services - .getCompilerOptionsDiagnostics() - .concat(services.getSyntacticDiagnostics(id)) - .concat(services.getSemanticDiagnostics(id)); + if (!snapshot) + { + context.error(colors.red(`failed lo load snapshot for ${id}`)); + return; + } + + const diagnostics = cache.getDiagnostics(id, snapshot, () => + { + return services + .getCompilerOptionsDiagnostics() + .concat(services.getSyntacticDiagnostics(id)) + .concat(services.getSemanticDiagnostics(id)); + }); + + printDiagnostics(context, diagnostics); }); - - printDiagnostics(diagnostics); - }); + } cache.diagnosticsDone(); },