Skip to content

Commit 06a46ab

Browse files
authored
chore: bump to 0.3.0, update benchmarks and docs for release (#23)
1 parent 3227543 commit 06a46ab

4 files changed

Lines changed: 148 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,40 @@ All notable changes to this project will be documented in this file.
44

55
This project follows [Semantic Versioning](https://semver.org/).
66

7+
## [0.3.0] - 2026-03-26
8+
9+
### Added
10+
11+
- **575 tests** across 7 suites (up from 105):
12+
- 247 indentation tests covering all 32 sections of the indent spec
13+
- 139 syntax highlighting tests for all token types
14+
- 39 grammar accuracy and performance tests
15+
- 25 code folding tests
16+
- 20 autocompletion tests (10 with documented skips)
17+
- 10 full-file integration tests
18+
- Parse accuracy benchmark script (`test/benchmark.mjs`)
19+
20+
### Fixed
21+
22+
- **35 grammar bugs identified, 29 fixed** across 8 root causes:
23+
- Bare `rescue` inside method body without `begin`
24+
- Lambda without params (`-> { body }`)
25+
- Setter assignment via dot (`self.email = value`)
26+
- Constants as method calls (`URI(url)`, `Integer("42")`)
27+
- Scope resolution assignment (`Foo::BAR = 1`)
28+
- Block param destructuring (`|(k, v), acc|`)
29+
- `super` with bare arguments
30+
- Proc call syntax (`proc_var.(args)`)
31+
- Backslash line continuation
32+
- Pattern binding in `case/in`
33+
- `private def`, `protected def`, `private_class_method def` now correctly recognized as block openers for indentation
34+
- Mixed `do/end` + `{}` block nesting no longer confuses the deindent scanner
35+
- `super` highlighted as keyword
36+
37+
### Changed
38+
39+
- Parse accuracy improved significantly (85-91% → 93-98%) across all benchmark files
40+
741
## [0.2.0] - 2026-03-26
842

943
### Added

README.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
Ruby language support for [CodeMirror 6](https://codemirror.net/), built on a [Lezer](https://lezer.codemirror.net/) grammar.
44

5+
Targets **Ruby 3.0+** syntax (including endless methods and basic pattern matching).
6+
57
[**Live Demo**](https://jeanpaulsio.github.io/codemirror-lang-ruby/)
68

79
## Install
@@ -29,13 +31,13 @@ Tested against popular open source Ruby projects (large, representative files):
2931

3032
| Project | File | Lines | Accuracy |
3133
|---------|------|-------|----------|
32-
| [Fastlane](https://github.com/fastlane/fastlane) | runner.rb | 379 | **91.3%** |
33-
| [Grape](https://github.com/ruby-grape/grape) | api.rb | 166 | **90.1%** |
34-
| [Jekyll](https://github.com/jekyll/jekyll) | site.rb | 577 | **88.4%** |
35-
| [Devise](https://github.com/heartcombo/devise) | devise.rb | 534 | **87.9%** |
36-
| [Sidekiq](https://github.com/sidekiq/sidekiq) | config.rb | 321 | **87.5%** |
37-
| [Rails](https://github.com/rails/rails) | query_methods.rb | 2291 | **86.6%** |
38-
| [Faker](https://github.com/faker-ruby/faker) | internet.rb | 579 | **85.3%** |
34+
| [Faker](https://github.com/faker-ruby/faker) | internet.rb | 579 | **98.6%** |
35+
| [Devise](https://github.com/heartcombo/devise) | devise.rb | 534 | **98.1%** |
36+
| [Jekyll](https://github.com/jekyll/jekyll) | site.rb | 577 | **97.6%** |
37+
| [Fastlane](https://github.com/fastlane/fastlane) | runner.rb | 379 | **95.0%** |
38+
| [Rails](https://github.com/rails/rails) | query_methods.rb | 2291 | **94.2%** |
39+
| [Grape](https://github.com/ruby-grape/grape) | api.rb | 166 | **94.0%** |
40+
| [Sidekiq](https://github.com/sidekiq/sidekiq) | config.rb | 321 | **93.5%** |
3941

4042
## What's supported
4143

@@ -61,13 +63,14 @@ Tested against popular open source Ruby projects (large, representative files):
6163
- **Inline rescue as a standalone expression**`value = foo rescue nil` works (rescue in assignments), but `foo rescue bar` as a standalone expression outside of assignment context is not supported.
6264
- **Newline as statement separator** — Ruby uses newlines to separate statements, but the grammar is whitespace-insensitive. An expression followed by `if` on the next line may be parsed as a conditional modifier (e.g., `x = 1\nif cond` parses as `x = (1 if cond)`).
6365
- **Symbol-key shorthand in method calls**`foo(name: "value")` with symbol-key syntax is not yet supported. Use rocket syntax `foo(:name => "value")` as a workaround.
66+
- **Line-based indentation** — The indent engine uses regex line scanning rather than tree-based analysis. Most patterns work well, but complex multi-line expressions (e.g., multi-line method args followed by a body, trailing commas inside nested delimiters) may not indent perfectly.
6467

6568
## Development
6669

6770
```bash
6871
npm install # Install dependencies
6972
npm run build # Build grammar + bundle to dist/
70-
npm test # Run all 105 grammar tests
73+
npm test # Run 105 grammar tests (575 total across all suites)
7174
npm run lint # TypeScript type check
7275
npm run demo:build # Build the demo page
7376
```

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codemirror-lang-ruby",
3-
"version": "0.2.0",
3+
"version": "0.3.0",
44
"description": "Ruby language support for CodeMirror 6, built on a Lezer grammar",
55
"type": "module",
66
"main": "dist/index.cjs",

test/benchmark.mjs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Parse accuracy benchmark against real-world Ruby projects
2+
// Downloads files from GitHub and measures error-free line percentage
3+
4+
import {rubyLanguage} from "../dist/index.js"
5+
const parser = rubyLanguage.parser
6+
7+
const FILES = [
8+
{
9+
project: "Fastlane",
10+
file: "runner.rb",
11+
url: "https://raw.githubusercontent.com/fastlane/fastlane/master/fastlane/lib/fastlane/runner.rb",
12+
},
13+
{
14+
project: "Grape",
15+
file: "api.rb",
16+
url: "https://raw.githubusercontent.com/ruby-grape/grape/master/lib/grape/api.rb",
17+
},
18+
{
19+
project: "Jekyll",
20+
file: "site.rb",
21+
url: "https://raw.githubusercontent.com/jekyll/jekyll/master/lib/jekyll/site.rb",
22+
},
23+
{
24+
project: "Devise",
25+
file: "devise.rb",
26+
url: "https://raw.githubusercontent.com/heartcombo/devise/main/lib/devise.rb",
27+
},
28+
{
29+
project: "Sidekiq",
30+
file: "config.rb",
31+
url: "https://raw.githubusercontent.com/sidekiq/sidekiq/main/lib/sidekiq/config.rb",
32+
},
33+
{
34+
project: "Rails",
35+
file: "query_methods.rb",
36+
url: "https://raw.githubusercontent.com/rails/rails/main/activerecord/lib/active_record/relation/query_methods.rb",
37+
},
38+
{
39+
project: "Faker",
40+
file: "internet.rb",
41+
url: "https://raw.githubusercontent.com/faker-ruby/faker/main/lib/faker/default/internet.rb",
42+
},
43+
]
44+
45+
async function fetchFile(url) {
46+
const res = await fetch(url)
47+
if (!res.ok) throw new Error(`Failed to fetch ${url}: ${res.status}`)
48+
return await res.text()
49+
}
50+
51+
function measureAccuracy(code) {
52+
const tree = parser.parse(code)
53+
const lines = code.split("\n")
54+
const totalLines = lines.length
55+
56+
// Find which lines contain error nodes
57+
const errorLines = new Set()
58+
tree.iterate({
59+
enter(node) {
60+
if (node.type.isError) {
61+
// Mark all lines this error spans
62+
const startLine = code.slice(0, node.from).split("\n").length
63+
const endLine = code.slice(0, node.to).split("\n").length
64+
for (let i = startLine; i <= endLine; i++) {
65+
errorLines.add(i)
66+
}
67+
}
68+
},
69+
})
70+
71+
const cleanLines = totalLines - errorLines.size
72+
const accuracy = (cleanLines / totalLines) * 100
73+
return { totalLines, errorLines: errorLines.size, cleanLines, accuracy }
74+
}
75+
76+
console.log("Fetching files and measuring parse accuracy...\n")
77+
78+
const results = []
79+
for (const entry of FILES) {
80+
try {
81+
const code = await fetchFile(entry.url)
82+
const stats = measureAccuracy(code)
83+
results.push({ ...entry, ...stats })
84+
console.log(
85+
`${entry.project.padEnd(12)} ${entry.file.padEnd(22)} ${String(stats.totalLines).padStart(5)} lines ${stats.accuracy.toFixed(1)}%`
86+
)
87+
} catch (e) {
88+
console.log(`${entry.project.padEnd(12)} FAILED: ${e.message}`)
89+
}
90+
}
91+
92+
// Sort by accuracy descending
93+
results.sort((a, b) => b.accuracy - a.accuracy)
94+
95+
console.log("\n--- README table (sorted by accuracy) ---\n")
96+
console.log("| Project | File | Lines | Accuracy |")
97+
console.log("|---------|------|-------|----------|")
98+
for (const r of results) {
99+
console.log(
100+
`| ${r.project} | ${r.file} | ${r.totalLines} | **${r.accuracy.toFixed(1)}%** |`
101+
)
102+
}

0 commit comments

Comments
 (0)