Can we manually force linting even if the document hasn't changed?

I’m working on a project with Codemirror 6 where I’m editing a JSON5 structure, and I have a custom linter that checks the values in the editor against related data that lives outside the editor.

For example, the value at a particular path in the JSON document must match one of the values in another list that is not part of the document.

// The document inside CodeMirror
{
   value: "abc"
}

And this this might be the other list that is maintained outside the editor. The linter checks that value in the editor is one of these values.

[ "abc", "def" ]

This works great for cases where I’m changing the document itself.

Now I’m working on forcing the lint to rerun when the values in the external list change, but the document itself hasn’t changed. With the above example, say if the external list changes to ['a', 'def'] then the abc value is no longer in the list so the linter will flag that.

But forceLinting doesn’t seem to handle this case. I took a look at the code, and it appears that forceLinting only actually forces it if this.set is true, which in turn only gets set if a state update comes in where docChanged is set.

Do you have any recommendations on how to deal with this? The obvious change IMO is to add an argument to forceLinting that will force the lint even if the document hasn’t changed, but maybe there’s a better way. Thanks!

p.s. I’m planning to publish my JSON5-specific work for CodeMirror (lezer parser, CodeMirror support, etc.) in a separate repo soon so all can use it :slight_smile:

1 Like

With @codemirror/lint 0.19.2, you should be able to reconfigure your linter (just update a compartment with a different linter call) to cause the lint plugin to schedule a new lint pass.

1 Like

Perfect, I was already doing that anyway. Confirmed it works with 0.19.2. Thank you!

Having encountered this today in the same circumstances (a lint source which depends on external data), it would be nice if forceLinting (or something similar) could actually be used to force the linting to run even if the document hasn’t changed.

The best way to handle that is probably to create a new lint function with access to the new data when the data changes, and wrap your lint config in a compartment that you can reconfigure with the new function.

Having been using reconfigure to force a linter to re-run when the doc hasn’t changed (in this case it needs to run when the selection changes, as the current cursor position affects which diagnostics the user should see while they’re editing), I’ve found that reconfiguring the linter causes all the facets to be reconfigured, which means facets which use compute with state field dependencies run too often.

The only solution I can see at the moment is an extra boolean option for forceLinting as described at the start of this thread, but if there’s an alternative I’d be happy to try it.

compute should be pure, so rerunning it shouldn’t really do anything. But also, facets that didn’t change by a reconfiguration shouldn’t have their compute even run. If you have a situation where this does happen, I’d be interested in looking at a reduced example.

Here’s an example of a compute that runs when the state field that it uses updates.

Maybe there’s something special about EditorView.contentAttributes that makes it behave this way?

If a dependency changes, recomputing the facet value is the right thing to do, isn’t it?

If a dependency changes, recomputing the facet value is the right thing to do, isn’t it?

Yes, this is behaving as I’d like it to behave :slightly_smiling_face:

The problem that’s relevant to this thread is that it also runs when any other facet is reconfigured, and it would be good to avoid that if possible, by not needing to reconfigure a facet containing the linter in order to force it to run.

That’s not something the script you linked shows, and in fact there are automated tests verifying that precisely this doesn’t happen, at least not in general. So if it is happening for you, could you provide a script that actually shows it occurring?

I’ll have a go at an example; in the meantime this is where conf is set to null on a StateEffect.reconfigure and this is where reconfigure is called on each facet when conf is null.

I’ve been experimenting (demo), and I think the summary so far is that:

  1. The get function of EditorView.contentAttributes.compute is called in two situations:
    a) when the value of a state field it depends on changes.
    b) when another compartment is reconfigured (note: I was talking about facets being reconfigured before, which maybe doesn’t make sense - I meant the compartment containing the facet).
  2. The output of the get function of EditorView.contentAttributes.compute (i.e. the value of the facet) is only used when the value of a state field it depends on changes. I’m not sure whether that’s the same for other facets.

Does that makes sense, and is that the expected behaviour?

I see what you mean now. That call was indeed not necessary. Attached patch avoids it. (But, in case that wasn’t clear, you really shouldn’t have side effects in these getters.)

1 Like

I just ran into this issue too. In our case we have our own parser and store a parsed version of the input as a facet. How the input is parsed can be changed from extern, which causes the facet to have a new value.
I’ll try reconfiguring the linter when the parse settings change, but to me it seems being able to specify to re-run the linter when dependencies change (fields, facets) but make for a nicer API.

1 Like

As a workaround, I manually set plugin.set = true in a custom version of forceLinting, and it has the desired effect:

export function forceLinting(view: EditorView) {
  const plugin = view.plugin(lintPlugin);
  if (plugin) {
    plugin.set = true;
    plugin.force();
  }
}