Big update to prettier-extension-monkeyc

I've posted about prettier-extension-monkeyc before, but I've added a bunch of new features that developers will probably like (well, I've been missing them, so maybe you have too).

The new features it implements for VSCode include:

  • Goto Definition. Point at a symbol, Ctrl/Cmd click, and it will take you to the definition. Or F12
  • Goto References. Right click on a symbol and select "Goto References". It will show you all the references. Or Shift-F12
  • Peek Definition/Peek References. Same as above, but in a popup window so you don't lose your place in the original document.
  • Rename Symbol. Right click on a local, function, class or module name, and select "Rename Symbol". It will rename all the references. It doesn't yet work for class members/methods.
  • Goto Symbol. Type Ctrl/Cmd-Shift-O and pick a symbol from the drop down (which has a hierarchical view of all symbols in the current file). This also appears as an outline across the top of the file.
  • Open Symbol By Name. Type Ctrl/Cmd-T, then start typing letters from a symbol name. A drop down will be populated with all matching symbols from anywhere in your project.

Older features include a prettier based formatter for monkeyc, and a monkeyc optimizer that will build/run/export an optimized version of your project.

[edit: My last couple of replies seem to have just disappeared, and the whole conversation seems to be in a jumbled order, so tldr: there's a new test-release at https://github.com/markw65/prettier-extension-monkeyc/releases/tag/v2.0.9 which seems to work for me on linux. I'll do more verification tomorrow, and push a proper update to the vscode store once I'm sure everything is working]

  • I'm curious, why barrel shouldn't be analyzed

    I've always known that the optimizer can't run safely on a barrel project (without making some radical changes), and I guess I just assumed the same applied to the analyzer. But I actually found a simple fix for the incorrect warning this morning, so I left it analyzing the barrels.

    what I should expect to see now

    I think you've found another bug.

    The actual runtime path for BarrelTest.Strings.TestString is $.BarrelTest.Rez.Strings.TestString (but Garmin's compiler doesn't allow you to use that path in a resource file, for some reason - you have to use the short form).

    Until recently, I didn't handle the short form at all, but in response to a bug report (one of yours I think), I fixed it so that references to @BarrelTest.Strings.TestString in resources belonging to the BarrelTest barrel get remapped correctly.

    But thats too specific. It looks like I need to map @Foo.Strings.Bar to $.Foo.Rez.Strings.Bar if Foo is the name of any of the barrels associated with the project, regardless of where the reference occurs.

    You can probably tell that I don't really use barrels; I've only got a couple of garmin projects that use barrels, and a couple of open source ones, and some synthetic projects I've made for tests - and evidently none of them exercise this...

  • I've pushed a fix for this one, and the earlier one to monkeyc-optimizer if you want to try it.

    While I was working on the first issue, I realized that visit refs and defs needs an update to deal with multi-project workspaces. If there are multiple apps in the workspace that use the same Barrel, then Find References will only find the references in the Barrel (if the barrel is in the workspace), or the references in the barrel and one of the apps (if the barrel isn't in the workspace).

    Ignoring barrel projects would help with that, but wouldn't fix it entirely, so I need to fix that so that it finds every project that refers to the current file, and then finds all the refs across all those projects.

  • v2.0.99 is out

    Fixes issues with references to Barrel resources, and makes fixes refs and defs to search across projects (eg if the symbol you click on is in a Barrel project, and there are two other projects that use the barrel in the workspace, it will now find all the refs from all three projects). 

  • There's a little cosmetic issue while reporting about missing symbols, that I started to see in 2.0.99 (probably, due to proper barrels analysis).

    With

    const LONG_NOT_EXISTING_RESOURCE = Rez.Strings.LongNotExistingStringResource1234567890123456789012345678901234567890;
    

    anywhere in the project I see a bit ugly error message

    Undefined symbol Rez.Strings
        .LongNotExistingStringResource1234567890123456789012345678901234567890

    that is multilined, while all others aren't.

    The same result is for running analysis from command line:

    ERROR: Undefined symbol Rez.Strings
        .LongNotExistingStringResource1234567890123456789012345678901234567890 at ...

    I expect this resource to be missing at this stage, and it is not a big deal, but I think it's still worth reporting.

  • I have a generic formatAst that converts ast nodes to strings using prettier, and I used that for a lot of error messages. I had noticed similar issues for certain kinds of nodes in the past, and added a special formatAstLongLines that overrides prettier's line length. But I only used it in a few places where I'd noticed problems. I just went through every use of formatAst, and every one except for printing the optimized source is for error messages; so I switched them all to the long lines version...

  • It seems, "Go to definition" sometimes doesn't work for typedefs and in typedefs themselves:

    import Toybox.Lang;
    
    class TestClass {
        typedef Type as Number;
    
        typedef AnotherType as Type or TestEnum;
    
        enum TestEnum {
            VALUE,
        }
    
        function initialize(value as Type, anotherValue as AnotherType) {}
    }
    

    "Find references" works for "Type", "AnotherType" and "TestEnum".

  • This was introduced in 2.0.97, when I made it look for overriding definitions too. That broke lookup of types declared in classes (because I wasn't thinking). The fix will be in the next release.

  • ... and while writing tests I discovered that I was not allowing renames for typedefs in classes, which was pointless. So I've fixed that too.

  • Sorry for asking such questions here, but you may be one of the few people outside the CIQ team, who can answer them.

    Could you, please, also explain, why there is no warning about undefined "AnotherClass" in "ClassThatExtendsClassFromModule", and the code compiles with strict checking, but there is "Cannot find symbol ':AnotherClass' on type 'self'" for "ClassThatDoesntExtendClassFromModule" (and PMC knows this too and gives the same warning as a result of live analysis)?

    module SomeModule {
        class ClassFromModule {
        }
        class AnotherClass {
            function func() as Void {}
        }
    }
    
    class ClassThatExtendsClassFromModule extends SomeModule.ClassFromModule {
        function initialize() {
            (new AnotherClass()).func();
        }
    }
    
    class ClassThatDoesntExtendClassFromModule {
        function initialize() {
            (new AnotherClass()).func();
        }
    }
    

    Does extending class from the module also adds implicit import of all its classes, functions and modules within class' scope?

  • Does extending class from the module also adds implicit import of all its classes, functions and modules within class' scope

    Sort of.

    For non-static methods in a class, when called non-statically (and unfortunately, it is possible to call non-static methods statically), the way things are looked up is as follows:

    First look in the class itself, if not found, look in the parent class, if not found continue up the parent chain until you reach Object.

    If it still wasn't found, search the module containing the class, and all containing modules out to the global scope.

    If it still wasn't found, do the same for the module containing the parent class, and then repeat all the way out to Object.

    So in your example, it looks for AnotherClass in ClassThatExtendsClassFromModule, then ClassFromModule, then Object, then the global scope, then SomeModule, and thats where it finds it.

    Note that this is a *very* expensive lookup. new SomeModule.AnotherClass would be much faster (even faster if you also "import SomeModule;" - but my optimizer will add the import automatically if you write it as SomeModule.AnotherClass); but the bare AnotherClass does generate the smallest code.

    Also note that because Toybox.Lang.Object is in the parent chain of every class, this has surprising consequences for non-static class methods:

    // no imports
    
    class Foo {
      function works() {
        System.println("Works!");
      }
      static function fails() {
        System.println("Undefined symbol System");
      }
    }
    
    function fails() {
      System.println("Undefined symbol System");
    }

    Since Object is defined inside Lang, all symbols defined in Lang are available in non-static methods, as are all symbols defined in Toybox - which includes every Toybox module.

    Note again that I'm talking about what the runtime does - exactly what Garmin's type checker allows has varied over time.