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.

329 lines (278 loc) 13.7 kB
var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })(); var _createDecoratedClass = (function () { function defineProperties(target, descriptors, initializers) { for (var i = 0; i < descriptors.length; i++) { var descriptor = descriptors[i]; var decorators = descriptor.decorators; var key = descriptor.key; delete descriptor.key; delete descriptor.decorators; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor || descriptor.initializer) descriptor.writable = true; if (decorators) { for (var f = 0; f < decorators.length; f++) { var decorator = decorators[f]; if (typeof decorator === 'function') { descriptor = decorator(target, key, descriptor) || descriptor; } else { throw new TypeError('The decorator for method ' + descriptor.key + ' is of the invalid type ' + typeof decorator); } } if (descriptor.initializer !== undefined) { initializers[key] = descriptor; continue; } } Object.defineProperty(target, key, descriptor); } } return function (Constructor, protoProps, staticProps, protoInitializers, staticInitializers) { if (protoProps) defineProperties(Constructor.prototype, protoProps, protoInitializers); if (staticProps) defineProperties(Constructor, staticProps, staticInitializers); return Constructor; }; })(); /* * 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. */ 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 _nuclideAnalytics2; function _nuclideAnalytics() { return _nuclideAnalytics2 = require('../../nuclide-analytics'); } var _FlowServiceFactory2; function _FlowServiceFactory() { return _FlowServiceFactory2 = require('./FlowServiceFactory'); } var _commonsNodePromise2; function _commonsNodePromise() { return _commonsNodePromise2 = require('../../commons-node/promise'); } var _nuclideDiagnosticsProviderBase2; function _nuclideDiagnosticsProviderBase() { return _nuclideDiagnosticsProviderBase2 = require('../../nuclide-diagnostics-provider-base'); } var _atom2; function _atom() { return _atom2 = require('atom'); } var _assert2; function _assert() { return _assert2 = _interopRequireDefault(require('assert')); } var _constants2; function _constants() { return _constants2 = require('./constants'); } var _nuclideLogging2; function _nuclideLogging() { return _nuclideLogging2 = require('../../nuclide-logging'); } var logger = (0, (_nuclideLogging2 || _nuclideLogging()).getLogger)(); /** * Currently, a diagnostic from Flow is an object with a "message" property. * Each item in the "message" array is an object with the following fields: * - path (string) File that contains the error. * - descr (string) Description of the error. * - line (number) Start line. * - endline (number) End line. * - start (number) Start column. * - end (number) End column. * - code (number) Presumably an error code. * The message array may have more than one item. For example, if there is a * type incompatibility error, the first item in the message array blames the * usage of the wrong type and the second blames the declaration of the type * with which the usage disagrees. Note that these could occur in different * files. */ // Use `atom$Range | void` rather than `?atom$Range` to exclude `null`, so that the type is // compatible with the `range` property, which is an optional property rather than a nullable // property. function extractRange(message) { // It's unclear why the 1-based to 0-based indexing works the way that it // does, but this has the desired effect in the UI, in practice. var range = message.range; if (range == null) { return undefined; } else { return new (_atom2 || _atom()).Range([range.start.line - 1, range.start.column - 1], [range.end.line - 1, range.end.column]); } } function extractPath(message) { return message.range == null ? undefined : message.range.file; } // A trace object is very similar to an error object. function flowMessageToTrace(message) { return { type: 'Trace', text: message.descr, filePath: extractPath(message), range: extractRange(message) }; } function flowMessageToDiagnosticMessage(diagnostic) { var flowMessage = diagnostic.messageComponents[0]; // The Flow type does not capture this, but the first message always has a path, and the // diagnostics package requires a FileDiagnosticMessage to have a path. var path = extractPath(flowMessage); (0, (_assert2 || _assert()).default)(path != null, 'Expected path to not be null or undefined'); var diagnosticMessage = { scope: 'file', providerName: 'Flow', type: diagnostic.level === 'error' ? 'Error' : 'Warning', text: flowMessage.descr, filePath: path, range: extractRange(flowMessage) }; // When the message is an array with multiple elements, the second element // onwards comprise the trace for the error. if (diagnostic.messageComponents.length > 1) { diagnosticMessage.trace = diagnostic.messageComponents.slice(1).map(flowMessageToTrace); } return diagnosticMessage; } var FlowDiagnosticsProvider = (function () { function FlowDiagnosticsProvider(shouldRunOnTheFly, busySignalProvider) { var _this = this; var ProviderBase = arguments.length <= 2 || arguments[2] === undefined ? (_nuclideDiagnosticsProviderBase2 || _nuclideDiagnosticsProviderBase()).DiagnosticsProviderBase : arguments[2]; _classCallCheck(this, FlowDiagnosticsProvider); this._busySignalProvider = busySignalProvider; var utilsOptions = { grammarScopes: new Set((_constants2 || _constants()).JS_GRAMMARS), shouldRunOnTheFly: shouldRunOnTheFly, onTextEditorEvent: function onTextEditorEvent(editor) { return _this._runDiagnostics(editor); }, onNewUpdateSubscriber: function onNewUpdateSubscriber(callback) { return _this._receivedNewUpdateSubscriber(callback); } }; this._providerBase = new ProviderBase(utilsOptions); this._requestSerializer = new (_commonsNodePromise2 || _commonsNodePromise()).RequestSerializer(); this._flowRootToFilePaths = new Map(); } _createDecoratedClass(FlowDiagnosticsProvider, [{ key: '_runDiagnostics', value: function _runDiagnostics(textEditor) { var _this2 = this; this._busySignalProvider.reportBusy('Flow: Waiting for diagnostics', function () { return _this2._runDiagnosticsImpl(textEditor); }).catch(function (e) { return logger.error(e); }); } }, { key: '_runDiagnosticsImpl', decorators: [(0, (_nuclideAnalytics2 || _nuclideAnalytics()).trackTiming)('flow.run-diagnostics')], value: _asyncToGenerator(function* (textEditor) { var file = textEditor.getPath(); if (!file) { return; } var flowService = (0, (_FlowServiceFactory2 || _FlowServiceFactory()).getFlowServiceByNuclideUri)(file); (0, (_assert2 || _assert()).default)(flowService); var result = yield this._requestSerializer.run(flowService.flowFindDiagnostics(file, /* currentContents */null)); if (result.status === 'outdated') { return; } var diagnostics = result.result; if (!diagnostics) { return; } var flowRoot = diagnostics.flowRoot; var messages = diagnostics.messages; var pathsToInvalidate = this._getPathsToInvalidate(flowRoot); /* * TODO Consider optimizing for the common case of only a single flow root * by invalidating all instead of enumerating the files. */ this._providerBase.publishMessageInvalidation({ scope: 'file', filePaths: pathsToInvalidate }); var pathsForRoot = new Set(); this._flowRootToFilePaths.set(flowRoot, pathsForRoot); for (var message of messages) { /* * Each message consists of several different components, each with its * own text and path. */ for (var messageComponent of message.messageComponents) { if (messageComponent.range != null) { pathsForRoot.add(messageComponent.range.file); } } } this._providerBase.publishMessageUpdate(this._processDiagnostics(messages, file)); }) }, { key: '_getPathsToInvalidate', value: function _getPathsToInvalidate(flowRoot) { var filePaths = this._flowRootToFilePaths.get(flowRoot); if (!filePaths) { return []; } return Array.from(filePaths); } }, { key: '_receivedNewUpdateSubscriber', value: function _receivedNewUpdateSubscriber(callback) { // Every time we get a new subscriber, we need to push results to them. This // logic is common to all providers and should be abstracted out (t7813069) // // Once we provide all diagnostics, instead of just the current file, we can // probably remove the activeTextEditor parameter. var activeTextEditor = atom.workspace.getActiveTextEditor(); if (activeTextEditor) { var matchesGrammar = (_constants2 || _constants()).JS_GRAMMARS.indexOf(activeTextEditor.getGrammar().scopeName) !== -1; if (matchesGrammar) { this._runDiagnostics(activeTextEditor); } } } }, { key: 'onMessageUpdate', value: function onMessageUpdate(callback) { return this._providerBase.onMessageUpdate(callback); } }, { key: 'onMessageInvalidation', value: function onMessageInvalidation(callback) { return this._providerBase.onMessageInvalidation(callback); } }, { key: 'dispose', value: function dispose() { this._providerBase.dispose(); } }, { key: '_processDiagnostics', value: function _processDiagnostics(diagnostics, currentFile) { // convert array messages to Error Objects with Traces var fileDiagnostics = diagnostics.map(flowMessageToDiagnosticMessage); var filePathToMessages = new Map(); // This invalidates the errors in the current file. If Flow, when running in this root, has // reported errors for this file, this invalidation is not necessary because the path will be // explicitly invalidated. However, if Flow has reported an error in this root from another root // (as sometimes happens when Flow roots contain symlinks to other Flow roots), and it also does // not report that same error when running in this Flow root, then we want the error to // disappear when this file is opened. // // This isn't a perfect solution, since it can still leave diagnostics up in other files, but // this is a corner case and doing this is still better than doing nothing. // // I think that whenever this happens, it's a bug in Flow. It seems strange for Flow to report // errors in one place when run from one root, and not report errors in that same place when run // from another root. But such is life. filePathToMessages.set(currentFile, []); for (var diagnostic of fileDiagnostics) { var path = diagnostic.filePath; var diagnosticArray = filePathToMessages.get(path); if (!diagnosticArray) { diagnosticArray = []; filePathToMessages.set(path, diagnosticArray); } diagnosticArray.push(diagnostic); } return { filePathToMessages: filePathToMessages }; } }, { key: 'invalidateProjectPath', value: function invalidateProjectPath(projectPath) { var pathsToInvalidate = new Set(); for (var flowRootEntry of this._flowRootToFilePaths) { var _flowRootEntry = _slicedToArray(flowRootEntry, 2); var flowRoot = _flowRootEntry[0]; var filePaths = _flowRootEntry[1]; if (!flowRoot.startsWith(projectPath)) { continue; } for (var filePath of filePaths) { pathsToInvalidate.add(filePath); } this._flowRootToFilePaths.delete(flowRoot); } this._providerBase.publishMessageInvalidation({ scope: 'file', filePaths: Array.from(pathsToInvalidate) }); } }]); return FlowDiagnosticsProvider; })(); module.exports = FlowDiagnosticsProvider; /** * Maps flow root to the set of file paths under that root for which we have * ever reported diagnostics. */