Brackets Blog

code the web

New Linting API

One of the more popular types of Brackets extensions are linters (or code quality checkers). Brackets ships with JSLint support built in, but you can currently find extensions for CSSLint, W3CValidation, JSHint, and even JSON. Brackets 31 introduces an improved API for better linting integration.

When I first experimented with writing Brackets extensions I wrote a JSHint extension as a way to get a feel for the process. After I did that I quickly wrote a CSSLinter and a W3CValidator. As you can imagine, these extensions were virtually the exact same code just with different linting engines in the back end.

My code basically revolved around:

  • Adding a menu option for my linter
  • Detecting when the menu option was used
  • Determining if the file matched what I could lint
  • Asking the Brackets editor for the current text
  • Passing that text to my linter
  • And finally – writing out the results to a panel at the bottom

Of the steps above, literally the only thing changing was the call to the linter being used. Here is an example from the CSSLinter extension. I’m not sharing all the code, just the core JavaScript file. The other bits include a few HTML templates, a style sheet for my results, and the linter itself.


/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, brackets, $, window, CSSLint, Mustache */
define(function (require, exports, module) {
    'use strict';
    var AppInit = brackets.getModule("utils/AppInit"),
        Commands = brackets.getModule("command/Commands"),
        CommandManager = brackets.getModule("command/CommandManager"),
        DocumentManager = brackets.getModule("document/DocumentManager"),
        EditorManager = brackets.getModule("editor/EditorManager"),
        ExtensionUtils = brackets.getModule("utils/ExtensionUtils"),
        LanguageManager = brackets.getModule("language/LanguageManager"),
        Menus = brackets.getModule("command/Menus"),
        PanelManager = brackets.getModule("view/PanelManager");
    var panelHtml = require("text!templates/bottom-panel.html"),
        tableHtml = require("text!templates/csslint-table.html");
    require("csslint/csslint");
    // Commands
    var VIEW_HIDE_CSSLINT = "csslint.run";
    //Determines if we are enabled or not. Previously we based this on if we could
    //see the panel, but now the panel will be hidden on non-CSS files
    var cssLintEnabled = false; var $csslint;
    function isCSSDoc(fileEntry) {
        console.dir(fileEntry);
        var language = LanguageManager.getLanguageForPath(fileEntry.file.fullPath);
        // Maybe in the future LESS
        return (language.getId() === "css" || language.getId() === "sass");
    }
    function _handleLint() {
        var messages, results;
        var editor = EditorManager.getCurrentFullEditor();
        if (!editor) { $csslint.hide(); EditorManager.resizeEditor(); return; }
        if (!isCSSDoc(editor.document)) { $csslint.hide(); EditorManager.resizeEditor(); return; }
           else { $csslint.show(); EditorManager.resizeEditor(); }
        var text = editor.document.getText();
        results = CSSLint.verify(text);
        messages = results.messages;
        if (results.messages.length) {
            var $selectedRow;
            var html = Mustache.render(tableHtml, {reportList: results.messages});
            $("#csslint .resizable-content").empty().append(html);
            $("#csslint .resizable-content").find("tr").on("click", function (e) {
              if ($selectedRow) { $selectedRow.removeClass("selected"); }
              $(this).addClass("selected");
              $selectedRow = $(this);
              var lineTd = $(this).find("td.line");
              var line = lineTd.text();
              var col = lineTd.data("col");
              var editor = EditorManager.getCurrentFullEditor();
              editor.setCursorPos(line - 1, col - 1);
              EditorManager.focusEditor();
             });
        }
        else {
            $("#csslint .resizable-content").empty().append("<p>No issues.</p>");
        }
    }
    function _handleShowCSSLint() {
        if (cssLintEnabled) {
            cssLintEnabled = false;
            CommandManager.get(VIEW_HIDE_CSSLINT).setChecked(false);
            $(DocumentManager).off("currentDocumentChange documentSaved", null, _handleLint);
            // if visible, hide
            $csslint.hide();
            EditorManager.resizeEditor();
        }
        else {
            cssLintEnabled = true;
            CommandManager.get(VIEW_HIDE_CSSLINT).setChecked(true);
            $(DocumentManager).on("currentDocumentChange documentSaved", _handleLint);
            _handleLint();
        }
    }
    CommandManager.register("Enable CSSLint", VIEW_HIDE_CSSLINT, _handleShowCSSLint);
    AppInit.htmlReady(function () {
        var s;
        ExtensionUtils.loadStyleSheet(module, "csslint.css");
        s = Mustache.render(panelHtml);
        $csslint = PanelManager.createBottomPanel("csslint.display.csslint", $(s), 200);
        var menu = Menus.getMenu(Menus.AppMenuBar.VIEW_MENU);
        menu.addMenuItem(VIEW_HIDE_CSSLINT, "", Menus.AFTER);
        $('#csslint .csslint-close').click(function () {
            CommandManager.execute(VIEW_HIDE_CSSLINT);
        });
      });
 });

I won’t go over the code line by line, but even if you’ve never seen the code behind a Brackets extension before, you can probably recognize the various parts that handle each of the points I made above. Not terribly difficult of course and the total line count (again, for this part of the extension) is only 134 lines.

Brackets Sprint 31 makes this incredibly simpler. Extension writers now have a way to register their own linting code for a file format. This is done via a CodeInspection module that ships with Brackets (and is used by the built in JSLint support). By using CodeInspection.register, you can specify that for a particular file type, your code should be run for linting. Note that currently there is no way to handle multiple linters so if you use multiple extensions for one file type, the last one will probably win, but support for handling that better will come in the future.

So what does this mean for code? Here is the new version of the CSSLinter:


/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, brackets, $, window, CSSLint, Mustache */
define(function (require, exports, module) {
    'use strict';
    var AppInit = brackets.getModule("utils/AppInit"),
        CodeInspection = brackets.getModule("language/CodeInspection");
    require("csslint/csslint");
    function cssLinter(text, fullPath) {
        var results;
        results = CSSLint.verify(text);
        if (results.messages.length) {
            var result = { errors: [] };
            for(var i=0, len=results.messages.length; i<len; i++) {
                var messageOb = results.messages[i];
                //default
                var type = CodeInspection.Type.WARNING;
                if(messageOb.type === "error") {
                     type = CodeInspection.Type.ERROR;
                }
                else if(messageOb.type === "warning") {
                    type = CodeInspection.Type.WARNING;
                }
                result.errors.push({pos: {line:messageOb.line-1, ch:messageOb.col}, message:messageOb.message, type:type });
            }
            return result;
        }
        else {
            //no errors
            return null;
        }
    }
    AppInit.appReady(function () { CodeInspection.register("css", { name: "CSSLint", scanFile: cssLinter });});
}); 

54 lines. Round about 1/3 of the size of the previous version. Oh, and I also got to get rid of the external style sheets and the HTML templates. Brackets now handles all of that for me. Let’s look at an example in the UI. I’ve got a CSS file open and I’ve intentionally broken it.

Note that I removed a closing } from the h1, h2, etc CSS declaration and I used a bad CSS style called “frak”. In the bottom right corner I’ve called out the yellow icon. That’s my CSS linter working automatically and detecting that something is wrong. If you click on that icon, the issues are displayed:

All of the design you see here was done by Brackets itself. Even better, when the folks who handle UX at Brackets decide to make improvements, my extension will get it automatically.

You can check this out today in the latest build of Brackets. If you haven’t installed my extension yet, remember that you can do it via the handy graphical extension manager.

5 Comments

  1. Awesome.
    This is one of my FAVOURITE code editors, I would never use another one.
    So glad I found this, and the “themes” & “beatify” plugins are very useful too.
    Thank you for open-sourcing this!

  2. Congrats on this. This is a great addition to Brackets.

    I wonder if instead of putting linter results on a panel at the bottom, we could have an icon in the gutter for that line where the problem is. Putting the cursor on the line could show the problem sentence in the status bar area somewhere. Or show a tooltip if you hover. Is that possible using the Brackets API? If so any pointers/examples on how to get access to that gutter and/or show tooltips?

    • Peter Flynn says:

      That’s definitely on the road map — the nice thing about the new linter API is that we can make a bunch of upgrades like that and all extensions using the API will get the benefits for free.

      But if you’re interested and skipping ahead sooner, there are several extensions that do JS linting in real time as you type and include inline/gutter markers. You can find the Interactive Linter extension in Extension Manager, while the older Continuous Compilation extension isn’t available there yet and would have to be downloaded manually (but I believe it still works).

  3. Renier, I’d suggest putting those recommendations on the Issues list at the Brackets github page.

  4. [...] seen how easy it is to extend Brackets. There’s more we didn’t cover, like the Linting API and NodeJS integration, but this article should be more than enough to get you started. As a [...]

  5. […] seen how easy it is to extend Brackets. There’s more we didn’t cover, like the Linting API and NodeJS integration, but this article should be more than enough to get you started. As a […]

  6. Jimmi says:

    Nice api.. User can use this online tool to validate json, xml, javascript Code Beautify

2 Trackbacks

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*


six − = 5

You may use these HTML tags and attributes <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>