[Call for ideas] Customizing editor tooling behavior from code

We’ve spent some time thinking about how we can allow customizing some of the editor tooling behavior. This is so that primarily library authors could improve the DX of using their libraries by hiding irrelevant modules/values from autocomplete, and so on. But, we need your help to come up with the most important customizations, and what would help improving the DX most.

Here are a few ideas me and @cristianoc have had. Ignore the names of attributes etc for now, we’ll make sure they’re good before we build anything. Let’s focus on the function of them to start.

Values and types

// `someFunc` will only appear in autocomplete _inside_ of the file it's defined in, but never outside.
// Useful as a sort of editor level %%private, but where making the function private isn't possible for various reasons
@editor.autocomplete.ignoreOutsideOfFile
let someFunc = () => {...}

// This function will now appear at the top of the autocomplete list. Any items in the same priority will be sorted alphabetically.
@editor.autocomplete.priority(1)
let otherFunc = () => {...}

These examples above could be applied to values/types equally.

Files

@@editor.autocomplete.hide

The above annotation would be required to be at the top of a file, and would hide that file entirely from autocomplete. Useful for hiding internal or sub modules. For instance, one fairly established pattern is to have a main module MyLib and then have sub modules MyLib__SubModule, MyLib__SubModule2 etc, that are then linked into MyLib like MyLib.SubModule. In this scenario, you typically don’t ever want the user to see MyLib__SubModule in the autocomplete list.

This is also highly relevant for codegen tools, where some/all of the code you codegen might not be something you want the user to access directly.

Please note that all of these things would only be at the editor level. You could still access them if you type them out, and hovers etc will work as usual. They’ll just be hidden from the autocomplete list, that’s all.

Your thoughts?

What are your thoughts and ideas around what type of customizations we could allow to help improve DX? Feel free to post any thoughts, we’re trying to familiarize ourselves with the problem space. Some things will be harder/lower prio than others to do, but we do want to get all of the potential use cases on the table.

Thank you in advance, we’re looking forward to hear your ideas!

3 Likes

Don’t want to sound like a broken record, but sane access control system would solve this, along with few other issues, more naturally.

4 Likes

Would this customisation include formatting?

One nice DX with JS and Tailwind is the Prettier plugin that will format the Tailwind classes into Tailwinds recommended order. This makes it easier to quickly locate certain classes because you know they will always be in the same order everywhere. I can imagine adding a @format_tailwind decorator to some classes in order to have them formatted according to Tailwinds rules.

However, extra formatters should not override the formatting behaviour of the built-in formatter.

I fully agree with that, but:

  1. There’s currently no ongoing work to build that
  2. Not sure if there’s consensus around how it should look
  3. It’s probably not trivial to build
  4. It’ll likely be several major versions before it would land, even if building it was picked up by someone today

So, I don’t see them as mutually exclusive in any way. Rather, the proposed functionality would be something that’s fairly simple and fast to ship, would work in all versions of ReScript, and only rely on the editor tooling itself. It would mean the DX could be improved much sooner. If/when a better access control system lands, it’ll be easy to switch to just that if that covers all the cases.

3 Likes

No, the formatter is its own thing and lives in the compiler now, so it wouldn’t be covered by this. This would be strictly for the editor tooling itself (that does call into the formatter, but isn’t the formatter per se).

What you’re talking about though can probably be implemented fairly easily with a dedicated VSCode extension. I’ve built similar things before where I leverage prettier to format GraphQL code inside of ReScript as a step after the ReScript formatter running. But, getting a bit off topic here.

For files bsconfig has the public-property which combined with interface files sounds like a cleaner and more common approach for exposing a public API to me. One might consider introducing private rather than or in addition to public.

5 Likes

Using bsconfig sounds like a cleaner solution to me as well.

3 Likes

Drop a very random idea here

Given that the ReScript code is DCE friendly (at least for es6 output), I can imagine colocating the snippet code right next to the library code.

@editor.snippet
@react.component
let basic = () => {
   <div>{React.string("Hello, world")}</div>
}

And when the user hits the tab on its completion item, the editor inlines the implementation part instead of the name. (A language server can provide code snippets if I know correctly)

However, this can add unnecessary bytes if the user doesn’t have tools to do DCE, such as bundler.

1 Like

Out of curiosity, would it be possible to solve this with a priority-tag in doc comments instead? This is subjective, but I can’t help but feel a bit iffy about mixing these things with application code.

For example:

/** 
Sums two integers.

``res example
sum(10, 10)
``

@priority 1
*/
let sum: (int, int) => int;
1 Like

We’ve intentionally tried to stay away from something similar to JSDoc etc, and I think we’d like to hold that line still. Not sure what you mean by mixing with application code though…?

Alright!

If I’ve understood correctly, this wouldn’t have any effect on the compilation of the program, it’d merely serve as metadata? The itch, for me, is that you’d have attributes for metadata mixed with application code.

If it were up to me (without knowing all the details), I’d pick doc-comments over attributes to continue to keep the separation between the two clear.

Exactly, it’s metadata only. In fact, that’s the only thing attributes are.

I think there’s some confusion around attributes here. Attributes have 0 meaning at runtime. They are simply a structure picked up by the parser and attached to the AST, and are then carried through the compilation process, but with no effect on the generated code (and by that the runtime) unless someone acts on them.

PPXes can “find” attributes and act on them by manipulating the AST (removing nodes all together, inserting new ones, rewriting the existing ones etc). But outside tools, like the editor tooling, or GitHub - cca-io/rescript-react-intl-extractor: Extracts messages for localization from ReScript source files., can also leverage attributes to let the user add metadata precisely because it isn’t something that affects the runtime.

In fact, docstrings are also attributes. The parser sees /**My fine docstring*/ let someVal = 123 and parses that as @res.doc("My fine docstring") let someVal = 123.

I think this approach is invasive because one of the reasons of LSP is to implement compiler behavior. If at the language level the someFunc function in the Lib.res file is available for access then autocomplete should list that function.

About the priority attribute @editor.autocomplete.priority, couldn’t we apply a heuristic by loc, functions at the top have higher priority?

1 Like

Right, perhaps I worded myself poorly. There’s no confusion on my part, and I might also be alone in thinking this (which is fine!).

I’m all for better DX, even if I may be of the opinion that using attributes for it feels somewhat like a mixing of concerns. :slightly_smiling_face:

Out of curiosity, are there any particular reasons why you prefer attributes over something like a doc-string tag, apart from ease of parsing? I guess one benefit of attributes is that one can be selective in parsing them, while if you were to use docstring-tags one would have to explicitly hide them from the shown output.

I’m not very well-versed in editor tooling, but in my experience people tend to keep definitions in interfaces sorted somewhat by order of importance, so perhaps it’s something that could be approached with conventions?

type t

let make: unit => t

let add: (t, int) => t
let show: t => t

A few reasons for preferring attributes:

  • Trivial to add editor features like hover-to-see-explanation, autocomplete for attribute names (and potentially the payload as well, although we don’t have that anywhere yet)
  • We get a fairly well-defined format for the metadata that the parser will check for us automatically with no additional work on our side
  • Pattern matching on the attributes in tooling is trivial
  • Don’t have to parse/check the docstring eagerly to figure out if it has our own special annotation format

As for the proposed functionality, we won’t be pursuing this anytime soon given that very few seem to experience the issues we’d be solving (the community has spoken :smile: ).

However, a little more nuance as to why an access control system isn’t enough to solve this:

  • We have special cases like pervasives, which we must preserve public access for various reasons, but where we don’t want people using them unless they really intend to.
  • As we in the future integrate ReScript Core as the main stdlib, we’re going to have to keep the Js namespace around for a while for backwards compat/easing migration. But, we don’t want people using them.

Just hiding from autocomplete accomplishes what we want to accomplish to a large extent, with very little effort, and zero risk of breaking the existing ecosystem. However, there are other ways for us to accomplish this that doesn’t involve exposing these types of attributes. And that’s likely what we’ll pursue instead.

3 Likes

Thanks for sharing! I don’t want to add too much noise to this discussion, but I wondered how stable API’s are (i.e. functions used will likely vary, so priority might be mostly relevant for initial usage).

I don’t know what kind of possibilites that exist for editor-tooling, but implementing some sort of scoring logic with weights based on usage does have a certain nerd-snipe ring to it.

Maybe it’s me, but I’m worried it might hurt the adoption. Personally, I’m very wary of technologies with questionable design decisions. And in this case, I would prefer proper implementation, even if it would take more time, over half-backed one, since it doesn’t seem a critical feature. Also, if there are plans to implement access control system, this solution would add chore in the future. But again, maybe it’s just me making too big deal out of it.

4 Likes

Good points. Like I said in a message earlier, we won’t be pursuing this in this form.

1 Like