UNPKG

atom-nuclide

Version:

A unified developer experience for web and mobile development, built as a suite of features on top of Atom to provide hackability and the support of an active community.

475 lines (413 loc) 16.2 kB
Object.defineProperty(exports, '__esModule', { value: true }); /* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { var callNext = step.bind(null, 'next'); var callThrow = step.bind(null, 'throw'); function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(callNext, callThrow); } } callNext(); }); }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } var _fuzzaldrin2; function _fuzzaldrin() { return _fuzzaldrin2 = require('fuzzaldrin'); } var _semver2; function _semver() { return _semver2 = _interopRequireDefault(require('semver')); } var _nuclideLogging2; function _nuclideLogging() { return _nuclideLogging2 = require('../../nuclide-logging'); } var logger = (0, (_nuclideLogging2 || _nuclideLogging()).getLogger)(); var _FlowHelpers2; function _FlowHelpers() { return _FlowHelpers2 = require('./FlowHelpers'); } var _FlowProcess2; function _FlowProcess() { return _FlowProcess2 = require('./FlowProcess'); } var _FlowVersion2; function _FlowVersion() { return _FlowVersion2 = require('./FlowVersion'); } var _astToOutline2; function _astToOutline() { return _astToOutline2 = require('./astToOutline'); } var _diagnosticsParser2; function _diagnosticsParser() { return _diagnosticsParser2 = require('./diagnosticsParser'); } /** Encapsulates all of the state information we need about a specific Flow root */ var FlowRoot = (function () { function FlowRoot(root, execInfoContainer) { var _this = this; _classCallCheck(this, FlowRoot); this._root = root; this._execInfoContainer = execInfoContainer; this._process = new (_FlowProcess2 || _FlowProcess()).FlowProcess(root, execInfoContainer); this._version = new (_FlowVersion2 || _FlowVersion()).FlowVersion(function () { return _this._flowGetVersion(); }); this._process.getServerStatusUpdates().filter(function (state) { return state === 'not running'; }).subscribe(function () { return _this._version.invalidateVersion(); }); } _createClass(FlowRoot, [{ key: 'dispose', value: function dispose() { this._process.dispose(); } }, { key: 'allowServerRestart', value: function allowServerRestart() { this._process.allowServerRestart(); } }, { key: 'getPathToRoot', value: function getPathToRoot() { return this._root; } }, { key: 'getServerStatusUpdates', value: function getServerStatusUpdates() { return this._process.getServerStatusUpdates(); } }, { key: 'flowFindDefinition', value: _asyncToGenerator(function* (file, currentContents, line, column) { var options = {}; // We pass the current contents of the buffer to Flow via stdin. // This makes it possible for get-def to operate on the unsaved content in // the user's editor rather than what is saved on disk. It would be annoying // if the user had to save before using the jump-to-definition feature to // ensure he or she got accurate results. options.stdin = currentContents; var args = ['get-def', '--json', '--path', file, line, column]; try { var result = yield this._process.execFlow(args, options); if (!result) { return null; } var json = parseJSON(args, result.stdout); if (json.path) { return { file: json.path, point: { line: json.line - 1, column: json.start - 1 } }; } else { return null; } } catch (e) { return null; } }) /** * If currentContents is null, it means that the file has not changed since * it has been saved, so we can avoid piping the whole contents to the Flow * process. */ }, { key: 'flowFindDiagnostics', value: _asyncToGenerator(function* (file, currentContents) { yield this._forceRecheck(file); var options = {}; var args = undefined; if (currentContents) { options.stdin = currentContents; // Currently, `flow check-contents` returns all of the errors in the // project. It would be nice if it would use the path for filtering, as // currently the client has to do the filtering. args = ['check-contents', '--json', file]; } else { // We can just use `flow status` if the contents are unchanged. args = ['status', '--json', file]; } var result = undefined; try { // Don't log errors if the command returns a nonzero exit code, because status returns nonzero // if it is reporting any issues, even when it succeeds. result = yield this._process.execFlow(args, options, /* waitForServer */true); if (!result) { return null; } } catch (e) { // This codepath will be exercised when Flow finds type errors as the // exit code will be non-zero. Note this codepath could also be exercised // due to a logical error in Nuclide, so we try to differentiate. if (e.exitCode !== undefined) { result = e; } else { logger.error(e); return null; } } var json = undefined; try { json = parseJSON(args, result.stdout); } catch (e) { return null; } return (0, (_diagnosticsParser2 || _diagnosticsParser()).flowStatusOutputToDiagnostics)(this._root, json); }) }, { key: 'flowGetAutocompleteSuggestions', value: _asyncToGenerator(function* (file, currentContents, line, column, prefix, activatedManually) { // We may want to make this configurable, but if it is ever higher than one we need to make sure // it works properly when the user manually activates it (e.g. with ctrl+space). See // https://github.com/atom/autocomplete-plus/issues/597 // // If this is made configurable, consider using autocomplete-plus' minimumWordLength setting, as // per https://github.com/atom/autocomplete-plus/issues/594 var minimumPrefixLength = 1; // Allows completions to immediately appear when we are completing off of object properties. // This also needs to be changed if minimumPrefixLength goes above 1, since after you type a // single alphanumeric character, autocomplete-plus no longer includes the dot in the prefix. var prefixHasDot = prefix.indexOf('.') !== -1; // If it is just whitespace and punctuation, ignore it (this keeps us // from eating leading dots). var replacementPrefix = /^[\s.]*$/.test(prefix) ? '' : prefix; if (!activatedManually && !prefixHasDot && replacementPrefix.length < minimumPrefixLength) { return []; } var options = {}; var args = ['autocomplete', '--json', file]; options.stdin = (0, (_FlowHelpers2 || _FlowHelpers()).insertAutocompleteToken)(currentContents, line, column); try { var result = yield this._process.execFlow(args, options); if (!result) { return []; } var json = parseJSON(args, result.stdout); var resultsArray = undefined; if (Array.isArray(json)) { // Flow < v0.20.0 resultsArray = json; } else { // Flow >= v0.20.0. The output format was changed to support more detailed failure // information. resultsArray = json.result; } var candidates = resultsArray.map(function (item) { return (0, (_FlowHelpers2 || _FlowHelpers()).processAutocompleteItem)(replacementPrefix, item); }); return (0, (_fuzzaldrin2 || _fuzzaldrin()).filter)(candidates, replacementPrefix, { key: 'displayText' }); } catch (e) { return []; } }) }, { key: 'flowGetType', value: _asyncToGenerator(function* (file, currentContents, line_, column_, includeRawType) { var line = line_; var column = column_; var options = {}; options.stdin = currentContents; line++; column++; var args = ['type-at-pos', '--json', '--path', file, line, column]; if (includeRawType) { args.push('--raw'); } var output = undefined; try { var result = yield this._process.execFlow(args, options); if (!result) { return null; } output = result.stdout; if (output === '') { // if there is a syntax error, Flow returns the JSON on stderr while // still returning a 0 exit code (t8018595) output = result.stderr; } } catch (e) { return null; } var json = undefined; try { json = parseJSON(args, output); } catch (e) { return null; } var type = json.type; var rawType = json.raw_type; if (!type || type === '(unknown)' || type === '') { if (type === '') { // This should not happen. The Flow team believes it's an error in Flow // if it does. I'm leaving the condition here because it used to happen // before the switch to JSON and I'd rather log something than have the // user experience regress in case I'm wrong. logger.error('Received empty type hint from `flow type-at-pos`'); } return null; } return { type: type, rawType: rawType }; }) }, { key: 'flowGetCoverage', value: _asyncToGenerator(function* (path) { // The coverage command doesn't actually have the required information until Flow v0.28. For // earlier versions, we have to fall back on dump-types, which is slower especially in // pathological cases. We can remove this entirely when we want to stop supporting versions // earlier than v0.28. var version = yield this._version.getVersion(); // Fall back to dump types if we don't know the version var useDumpTypes = version == null || (_semver2 || _semver()).default.lte(version, '0.27.0'); return useDumpTypes ? (yield this._getCoverageViaDumpTypes(path)) : (yield this._getCoverageViaCoverage(path)); }) }, { key: '_getCoverageViaDumpTypes', value: _asyncToGenerator(function* (path) { var args = ['dump-types', '--json', path]; var result = undefined; try { result = yield this._process.execFlow(args, {}); } catch (e) { return null; } if (result == null) { return null; } var json = undefined; try { json = parseJSON(args, result.stdout); } catch (e) { // The error is already logged in parseJSON return null; } var allEntries = json; var uncoveredEntries = allEntries.filter(function (item) { return item.type === '' || item.type === 'any'; }); var uncoveredRanges = uncoveredEntries.map(function (item) { return (0, (_FlowHelpers2 || _FlowHelpers()).flowCoordsToAtomCoords)(item.loc); }); var uncoveredCount = uncoveredEntries.length; var totalCount = allEntries.length; var coveredCount = totalCount - uncoveredCount; return { percentage: totalCount === 0 ? 100 : coveredCount / totalCount * 100, uncoveredRanges: uncoveredRanges }; }) }, { key: '_getCoverageViaCoverage', value: _asyncToGenerator(function* (path) { var args = ['coverage', '--json', path]; var result = undefined; try { result = yield this._process.execFlow(args, {}); } catch (e) { return null; } if (result == null) { return null; } var json = undefined; try { json = parseJSON(args, result.stdout); } catch (e) { // The error is already logged in parseJSON return null; } var expressions = json.expressions; var uncoveredCount = expressions.uncovered_count; var coveredCount = expressions.covered_count; var totalCount = uncoveredCount + coveredCount; var uncoveredRanges = expressions.uncovered_locs.map((_FlowHelpers2 || _FlowHelpers()).flowCoordsToAtomCoords); return { percentage: totalCount === 0 ? 100 : coveredCount / totalCount * 100, uncoveredRanges: uncoveredRanges }; }) }, { key: '_forceRecheck', value: _asyncToGenerator(function* (file) { try { yield this._process.execFlow(['force-recheck', file], /* options */{}, // Make an attempt to force a recheck, but if the server is busy don't insist. /* waitsForServer */false, /* suppressErrors */true); return true; } catch (e) { // This command was introduced in Flow v0.23, so silently swallow errors to avoid logspam on // earlier versions, until we want to break support for earlier version. return false; } }) }, { key: '_flowGetVersion', value: _asyncToGenerator(function* () { var args = ['version', '--json']; var json = undefined; try { var result = yield (_FlowProcess2 || _FlowProcess()).FlowProcess.execFlowClient(args, this._root, this._execInfoContainer); if (result == null) { return null; } json = parseJSON(args, result.stdout); } catch (e) { logger.warn(e); return null; } return json.semver; }) // This static function takes an optional FlowRoot instance so that *if* it is part of a Flow // root, it can use the appropriate flow-bin installation (which may be the only Flow // installation) but if it lives outside of a Flow root, outlining still works using the system // Flow. }], [{ key: 'flowGetOutline', value: _asyncToGenerator(function* (root, currentContents, execInfoContainer) { var options = { stdin: currentContents }; var flowRootPath = root == null ? null : root.getPathToRoot(); var args = ['ast']; var json = undefined; try { var result = yield (_FlowProcess2 || _FlowProcess()).FlowProcess.execFlowClient(args, flowRootPath, execInfoContainer, options); if (result == null) { return null; } json = parseJSON(args, result.stdout); } catch (e) { logger.warn(e); return null; } try { return (0, (_astToOutline2 || _astToOutline()).astToOutline)(json); } catch (e) { // Traversing the AST is an error-prone process and it's hard to be sure we've handled all the // cases. Fail gracefully if it does not work. logger.error(e); return null; } }) }]); return FlowRoot; })(); exports.FlowRoot = FlowRoot; function parseJSON(args, value) { try { return JSON.parse(value); } catch (e) { logger.warn('Invalid JSON result from flow ' + args.join(' ') + '. JSON:\n\'' + value + '\'.'); throw e; } } // The path to the directory where the .flowconfig is -- i.e. the root of the Flow project.