UNPKG

@phoenix-plugin-registry/com.github.mrmeku.closure-linter

Version:

Lint javascript files according to the Google style guide and automatically fix linting errors.

237 lines (211 loc) 8.07 kB
define(function(require, exports, module) { 'use strict'; var EXTENSION_ID = 'com.github.mrmeku.closure-linter', GJSLINT_ID = 'gjslint', FIXJSSTYLE_ID = 'fixjsstyle', FIXJSSTYLE_ONSAVE_ID = FIXJSSTYLE_ID + '.onSave', FIXJSSTYLE_SHORTCUT = [{ key: 'Ctrl-Shift-J', platform: 'win' }, { key: 'Cmd-Shift-J', platform: 'mac' }], LINT_ERROR_REGEXP = new RegExp('^Line (\\d+), ([E,W])(:[\\d]+: (.*))$'), CONFIG_FILE_NAME = '.gjslintrc', DEFAULT_CONFIG = { flags: { gjslint: '--quiet --nosummary --strict', fixjsstyle: '--strict' } }; var AppInit = brackets.getModule('utils/AppInit'), DocumentManager = brackets.getModule('document/DocumentManager'), CommandManager = brackets.getModule('command/CommandManager'), CodeInspection = brackets.getModule('language/CodeInspection'), EditorManager = brackets.getModule('editor/EditorManager'), ExtensionUtils = brackets.getModule('utils/ExtensionUtils'), FileUtils = brackets.getModule('file/FileUtils'), FileSystem = brackets.getModule('filesystem/FileSystem'), Menus = brackets.getModule('command/Menus'), NodeDomain = brackets.getModule('utils/NodeDomain'), PreferencesManager = brackets.getModule('preferences/PreferencesManager'), ProjectManager = brackets.getModule('project/ProjectManager'); // Access to node process with hooks to closure linter. var closureLinter = new NodeDomain('closureLinter', ExtensionUtils.getModulePath(module, 'node/closureLinterDomain')); // Make DocumentManager into a jquery object for event binding. var $DocumentManager = $(DocumentManager); // Register commands to be made into menu items. var fixjsstyleCommand = CommandManager.register( 'Fixjsstyle', FIXJSSTYLE_ID, fixjsstyleAsync), fixjsstyleOnSaveCommand = CommandManager.register( 'Fixjsstyle On Save', FIXJSSTYLE_ONSAVE_ID, function() { setFixjsstyleOnSave(!this.getChecked()); }); function _loadConfigHelper(rootPath, currentPath, deferredResult) { var configFilePath = currentPath + CONFIG_FILE_NAME; var file = FileSystem.getFileForPath(configFilePath); FileUtils .readAsText(file) .then( function(content) { try { var config = JSON.parse(content); $.extend(DEFAULT_CONFIG, config); deferredResult.resolve(config); } catch (e) { console.error(EXTENSION_ID + ': invalid json ' + configFilePath); deferredResult.reject(e); } }, function(err) { if (rootPath === currentPath) { deferredResult.resolve(DEFAULT_CONFIG); } else { currentPath = FileUtils.getParentPath(currentPath); _loadConfigHelper(rootPath, currentPath, deferredResult); } } ); } /** * Loads closure-linter configuration for the specified file. * * The configuration file should have name .gjslint. If the specified file * is outside the current project root, then defaultConfiguration is used. * Otherwise, the configuration file is looked up starting from the directory * where the specified file is located, going up to the project root, * but no further. * * @param {string} fullPath Absolute path for the file linted. * * @return {$.Promise} Promise to return JSHint configuration object. * * @see <a href="http://www.jshint.com/docs/options/">JSHint option * reference</a>. */ function _loadConfig(fullPath) { var projectRoot = ProjectManager.getProjectRoot(), deferredResult = new $.Deferred(); if (!projectRoot || !fullPath) { return deferredResult.reject().promise(); } var rootPath = projectRoot.fullPath, currentPath = FileUtils.getParentPath(fullPath); _loadConfigHelper(rootPath, currentPath, deferredResult); return deferredResult.promise(); } /** * Makes an asynchronous call to gjslint on the potentially unsaved text. * @param {string} text Text content of the potentially unsaved file. * @param {string} fullPath Path to the potentially unsaved file being linted. * @return {$.Promise} Promise to return an object mapping to an array of * linting errors. */ function gjslintAsync(text, fullPath) { var deferred = new $.Deferred(); _loadConfig(fullPath).then( function(config) { var flags = config.flags.gjslint; closureLinter.exec('gjslint', text, fullPath, flags) .done(function(stdout) { deferred.resolve({errors: parseGjslintStdout(stdout)}); }) .fail(function(error, message) { deferred.reject(error, message); }); }, function() { deferred.reject(); } ); return deferred.promise(); } /** * Creates array of linting error objects from stdout of gjslint. * @param {string} output Stdout produced by gjslint. * @return {Array.<Object>} Array of brackets lint error objects. */ function parseGjslintStdout(stdout) { var lines = stdout.split('\n'), lintingErrors = []; lines.forEach(function(line) { var match = LINT_ERROR_REGEXP.exec(line); if (match) { lintingErrors.push({ pos: {line: parseInt(match[1], 10) - 1}, message: match[2] + match[3], type: match[2] === 'E' ? CodeInspection.Type.ERROR : CodeInspection.Type.WARNING }); } }); return lintingErrors; } /** * Makes an asyncrhonous call to fixjsstyle on the potentially unsaved text. * @return {$.Promise} Promise to return text with fixed style. */ function fixjsstyleAsync() { var editor = EditorManager.getCurrentFullEditor(), fullPath = EditorManager.getCurrentlyViewedPath(); if (editor && fullPath) { var document = editor.document, text = document.getText(), cursor = editor.getCursorPos(), scroll = editor.getScrollPos(), flags; // Keep a reference of document during async command. document.addRef(); _loadConfig(fullPath).then( function(config) { var flags = config.flags.fixjsstyle; closureLinter.exec('fixjsstyle', text, fullPath, flags) .done(function(styledText) { if (styledText !== text) { // Replace the text and reset the viewport/cursor. editor.document.setText(styledText); editor.setScrollPos(scroll.x, scroll.y); editor.setCursorPos(cursor.line, cursor.ch); } }) .always(function() { // Release document reference since we are done with it. document.releaseRef(); }); }); } } /** * Turns Fixjsstyle On Save command on or off. * @param {Boolean} checked True turns command on and vice versa. */ function setFixjsstyleOnSave(checked) { $DocumentManager[checked ? 'on' : 'off']( 'documentSaved', fixjsstyleAsync); fixjsstyleOnSaveCommand.setChecked(checked); PreferencesManager.set(FIXJSSTYLE_ONSAVE_ID, checked); PreferencesManager.save(); } AppInit.appReady(function() { // Set up fixjsstyle menu items. var editMenu = Menus.getMenu(Menus.AppMenuBar.EDIT_MENU); editMenu.addMenuDivider(); editMenu.addMenuItem(FIXJSSTYLE_ID, FIXJSSTYLE_SHORTCUT); editMenu.addMenuItem(FIXJSSTYLE_ONSAVE_ID); // Restore previous menu preferences. setFixjsstyleOnSave(PreferencesManager.get(FIXJSSTYLE_ONSAVE_ID)); // Register gjslint to lint javascript and html files. CodeInspection.register('javascript', { name: GJSLINT_ID, scanFileAsync: gjslintAsync }); CodeInspection.register('html', { name: GJSLINT_ID, scanFileAsync: gjslintAsync }); }); });