@codincod/codemirror-lang-java
v0.1.1
Published
Java language support for the CodeMirror code editor
Maintainers
Readme
@codincod/codemirror-lang-java
This package implements Java language support for the CodeMirror code editor, using the Lezer grammar Marijn Haverbeke wrote for @lezer/java, with the parse fixes below on top of it. The editor half comes from @codemirror/lang-java; the two are merged here so that one package holds the grammar and the language support that has to move with it.
Written in part for CodinCod, a competitive coding platform, where it colours the editor people solve puzzles in.
This code is released under an MIT license.
Usage
import {EditorView, basicSetup} from "codemirror"
import {java} from "@codincod/codemirror-lang-java"
const view = new EditorView({
parent: document.body,
doc: `sealed interface Shape permits Circle, Square {}
record Circle(double radius) implements Shape {}
record Square(double side) implements Shape {}
class Area {
static double of(Shape s) {
return switch (s) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Square q -> q.side() * q.side();
};
}
}
`,
extensions: [basicSetup, java()]
})What this fixes
Upstream is a Java 11 grammar with two bugs older than that. Measured over
46 397 files of Guava, Apache Commons Lang, JUnit 5, the Spring Framework and
Elasticsearch, @lezer/java 1.1.3 parses 54.61% of them with no error node,
leaving 242 760 error nodes. This package parses 99.82%, leaving 1 194.
Two of the fixes are bugs rather than missing syntax, and both predate every language feature below.
defaultandsynchronizedwere not modifiers. The list of modifiers had a missing|, sokw<"default"> kw<"synchronized">was one sequence rather than two alternatives. No method declaredsynchronized, and no interface method declareddefault, would parse.Foo.classdid not parse. A class literal was written as a type before.class, which only ever matched the shapes that cannot be anything else, a primitive (int.class) and an array (String[].class). In expression position a bareDayis an identifier by the time the dot arrives, so every ordinary class literal failed.
Three more are places upstream was narrower than the language.
- Eight module keywords were reserved everywhere.
requires,exports,opens,uses,provides,to,withandtransitiveare keywords only inside a module declaration, and names anywhere else. Reserving them costint to,int withand six other ordinary declarations.toandfromare what anyone writes for the ends of an edge or a range. - A text block could not close on a line holding a quote. The content rules
required the match to continue after a quote, so
{"a":"b"}"""never closed, and a JSON literal inside a text block is most of what the feature is used for. - Type annotations were rejected on new arrays and on varargs. JLS allows
new @Nullable String[0]andObject @Nullable ... args; upstream took the annotation on a dimension but not on the element type or before the dots. This is most of what remained on a null-annotated codebase. - An annotation type could not nest an enum. Its body took classes and interfaces only, and an enum naming the modes an annotation takes is the common case.
The rest is syntax added to Java between 14 and 21, none of which upstream has:
- Switch expressions, arrow labels, multi-value labels (
case 1, 2, 3 ->),case null, default, arrow bodies that are a block, athrowor another switch, andyield. - Records, with type parameters, interfaces, compact constructors, annotated and varargs components, nested in a body or local to a method.
- Sealed types:
sealed,non-sealedandpermits. - Patterns: type patterns in
instanceofand incase, record patterns, andwhenguards.
Every word added is contextual, so record, sealed, permits, yield,
when and non are all still ordinary names, which they have to be: they were
legal identifiers for the first twenty-five years of the language.
The 69 parse tests that ship with @lezer/java pass unchanged. test/fixes.txt
holds a case for each fix above, and every one of them fails on upstream.
Measuring it yourself
npm run corpus -- path/to/java/filesNo corpus ships with the package, because none of that code is ours to redistribute. Point it at your own; it counts files with no error node and groups them by directory, so a package that drags the total down names itself.
