Input Validation with CodeMirror 6

Hi,
Is it possible (or even a good idea) to use codemirror as an input validation tool?
My use case is to highlight errors a user makes while typing (invalid boolean operations), and show a tooltip with the error that matched.

As far as I understand, I can define tokens using regex in a LezerLanguage, and if those tokens match I should be able to highlight the errors with the EditorView and a HighlightStyle.

Is there any example on how to do that? I’m currently struggling with using codemirror (v6) in Vue3.js and I could not really see anything similar in the Examples.

The syntax tree produced by a language written as a Lezer parser will contain error nodes at the points where the parser couldn’t match the document to the grammar. You could ‘validate’ the document by searching for such nodes, and, indeed, show markers where they occur (though they may be zero-length when the parser ‘inserted’ a phantom token to correct the error, so just styling the text they cover isn’t enough).

Is there a way to change the error-correction behaviour? I have a similar use case to davidH, and am finding that lezer’s error correction results in things which are highlighted as if they are correct when they aren’t, because the parser has guessed the structure rather than marking the entire subtree as invalid.

No. Lezer will try to parse as much as possible, and won’t mark subtrees as invalid. If you want linting that works differently you’ll have to run another code analyzer on the content.

Ok, thanks!

@benw could u plz tell me how you have written the validation code inside the grammar file? I am also having the grammar code which correctly works for syntax highlighting but doesn’t work correctly for expression validation. I am sharing my grammar file code:

@top BuilderExpression { Expression }

@tokens {
  ComparisonOperator { ">" | "<" | ">=" | "<=" | "==" | "=" | "!=" }
  PlusMinus { "+" | "-" }
  StarObelus { "*" | "/" }
  NotQuoteChar { !["] }
  String { "\"" NotQuoteChar* "\"" }
  Digit { '0' | $[1-9] }
  TransformerName { "min_max_scaler" | "standard_scale" }
  FunctionName { "contains" | "concat" | "indexOf" | "lastIndexOf" | "slice" | "split" | "splitByLengths" | "length" | "match" | "startsWith" | "endsWith" | "replace" | "trim" | "toUppercase" | "toLowercase" | "toNumber" | "toText" | "if" | "or" | "not" | "and" }
  Space { " " }
  RoundTerm { ")" }
} 

Expression {
  Expression "or" Comparison
  | Comparison
}

Comparison {
  Comparison Item
  | Item
}

Item {
  AddSubtract
}

Round {
  RoundTerm
}

AddSubtract {
  Term
}

TimesDivide { 
  SpaceFactor
}

SpaceFactor {
  Space Factor
}

Term {
  TimesDivide
}

Factor {
  Parentheses
  | Value
}

Parentheses {
  "(" Expression ")"
}

Value {
  Function
  | DataFrame
  | Column
  | String
  | Number
  | Bool
  | Round
  | StarObelus
  | PlusMinus
  | ComparisonOperator
}

Function {
  Transformer
  | RegularFunction
}

Transformer {
  TransformerName "(" Expression ("," Expression)* ")"
}

RegularFunction {
  FunctionName "(" Expression ("," Expression)* ")"
}

DataFrame {
  "[" String "," String "," String* "]"
}

Column {
  "[" String "]"
}

Number {
  Float
  | Integer
}

Bool {
  "true"
  | "false"
}

Float {
  Integer "." Integer
}

Integer {
  Digit+
}

@detectDelim