Can Komodo handle a linter add-on written in JavaScript?

I’m creating a linter add-on for CoffeeScript that is written in JavaScript. The necessary XPCOM components are coded and configured correctly and work correctly from a macro (see example below). The act of linting is not an issue because the linter currently ignores the input and returns an empty list of results.

Unfortunately, when I open a CoffeeScript file, Komodo crashes. There are no messages in the logs.

I understand that Komodo supports add-ons in both languages via XPCOM, but I find core Python code that appears to avoid honoring the XPCOM interfaces. In particular, there is lint service code that calls UnwrapObject to access method lint_with_text. There is no lint_with_text method in koILinter and it’s not clear to me why this approach is being used to avoid following the interface.

So is it reasonable to expect a linter written in JavaScript to work? Or will the calls to UnwrapObject break whatever I write?

Any thoughts on tracking down the crash?

Thanks.


This test macro works fine. I have nothing else to test in my code and I’m not aware of other Komodo parts to look into.

const language = "CoffeeScript";
const linterService = Components.classes["@activestate.com/koLintService;1"].
                    getService(Components.interfaces.koILintService);
const linterClass = Components.classes["@ervumlens.github.io/elCoffeeScriptLinter;1"];

var linter;
var linterResults;

//Test result/results
try {
    const resultClass = Components.classes["@ervumlens.github.io/elLintResult;1"];
    const resultsClass = Components.classes["@ervumlens.github.io/elLintResults;1"];

    const result = resultClass.createInstance(Components.interfaces.koILintResult);
    const results = resultsClass.createInstance(Components.interfaces.koILintResults);

    result.severity = 2;
    result.lineStart = 2;
    result.lineEnd = 3;
    result.columnStart = 3;
    result.columnEnd = 4;

    const countBefore = results.getNumResults();

    if (countBefore != 0) {
        throw new Error("Invalid number of results on create");
    }

    results.addResult(result);
    const countAfter = results.getNumResults();

    if (countAfter != 1) {
        throw new Error("Invalid number of results after addResult");
    }

    alert("Success #1");
} catch (e) {
    alert("Failed #1: " + e);
}

//Test linter directly
try {
    const linterCID = linterService.getLinter_CID_ForLanguage(language);

    if (linterCID != "@ervumlens.github.io/elCoffeeScriptLinter;1") {
        throw new Error("Wrong linter registered for CoffeeScript: " + linterCID);
    }

    linter = linterClass.getService(Components.interfaces.koILinter);

    // The linter ignores the request and returns an empty results object.
    linterResults = linter.lint(null);

    if (linterResults.getNumResults() != 0) {
        throw new Error("Dummy result is not empty.");
    }

    alert("Success #2");
} catch (e) {
    alert("Failed #2: " + e);
}

//Test linter via lint service
try {
    linter = linterService.getLinterForLanguage(language);
    linter = linterClass.getService(Components.interfaces.koILinter);

    // The linter ignores the request and returns an empty results object.
    linterResults = linter.lint(null);

    if (linterResults.getNumResults() != 0) {
        throw new Error("Dummy result is not empty.");
    }

    alert("Success #3");
} catch (e) {
    alert("Failed #3: " + e);
}

I can reproduce the Komodo crash (or a Komodo crash, anyway) with the following linter component in a new extension project. The (Python) XPCOM linter crashes when it calls a (JavaScript) Komodo XPCOM object. I don’t know if the language difference is significant. I do know I can reproduce the same crash with a custom JS XPCOM object instead of the built-in one used here.

Note the line near the end: remove that line and no crash occurs.

  • Is there anything missing here?
  • Does anything scream “Hey, you’re going to crash Komodo with this code”?
# copy this to /components/elNoOpLinter.py in a new extension
import logging

from xpcom import components
from koLintResult import KoLintResult, SEV_ERROR
from koLintResults import koLintResults

log = logging.getLogger('linter-of-death')

class ElNoOpLinter(object):
    _com_interfaces_ = [components.interfaces.koILinter]
    _reg_clsid_ = "{2FC771E6-51EB-11E5-916D-6121D5902334}"
    _reg_contractid_ = "@ervumlens.github.io/elNoOpLinter;1"
    _reg_categories_ = [
         ("category-komodo-linter", 'CoffeeScript'),
         ]

    def __init__(self):
        log.warn(">> ElNoOpLinter::__init__")
        log.warn("  >> ElNoOpLinter::__init__: loading resolver class...")

        resolverClass = components.classes["@activestate.com/koResolve;1"]

        log.warn("  << ElNoOpLinter::__init__: done.")
        log.warn("  >> ElNoOpLinter::__init__: fetching resolver impl service...")

        self.resolver = resolverClass.getService(components.interfaces.koIResolve)

        log.warn("  << ElNoOpLinter::__init__: done.")
        log.warn("<< ElNoOpLinter::__init__")

    def lint(self, request):

        log.warn(">> ElNoOpLinter::lint")

        text = request.content.encode(request.encoding.python_encoding_name)
        result = self.lint_with_text(request, text)

        log.warn("<< ElNoOpLinter::lint")
        return result

    def lint_with_text(self, request, text):
        log.warn(">> ElNoOpLinter::lint_with_text")
        log.warn("  >> ElNoOpLinter::lint_with_text: Is self.resolver == None? " + str(self.resolver == None))
        log.warn("  >> ElNoOpLinter::lint_with_text: calling uriToPath()...")

        # Comment out this line and no crash occurs
        # vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv

        self.resolver.uriToPath("file:///home/foobar")

        # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

        log.warn("  << ElNoOpLinter::lint_with_text: done.")
        log.warn("<< ElNoOpLinter::lint_with_text")

        koResults = koLintResults()
        return koResults


In contrast, I can call the same JS XPCOM object from a Python macro:

# This is a python macro

from xpcom import components

resolverClass = components.classes["@activestate.com/koResolve;1"]
resolver = resolverClass.getService(components.interfaces.koIResolve)

result = resolver.uriToPath("file:///home/foobar")

window.alert("Called 'uriToPath' without issue: " + str(result))

I’ll investigate further internally but I seem to recall you cannot call JS XPCOM from Python (only the other way around), so this may not be doable, at least not without a lot of hacking.

Not 100% sure though, will get back to you.

… will get back to you.

Thanks, nathanr.

FYI on the technical side. A macro can successfully call a Py XPCOM component that in turn calls a JS XPCOM component.

I don’t know why the linter is crashing when it runs as a linter, but it doesn’t crash when created and called in a macro.