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
JavaScript
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.
*/