fix(editor): recognize Ruby task and configuration files

Add Ruby task/configuration filenames to the existing generated language associations.

Co-authored-by: ggbdpq <ggbdpq@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
小七
2026-10-03 00:39:39 -07:00
committed by GitHub
co-authored by Neil
parent c792386cdc
commit 97fd7edb39
6 changed files with 116 additions and 6 deletions
@@ -10,6 +10,12 @@ export const associationsPath = fileURLToPath(
new URL('../../src/renderer/src/lib/monaco-language-associations.json', import.meta.url)
)
// Monaco omits common Ruby task, template and configuration files.
const rubyAssociations = {
extensions: ['.rake', '.ru', '.jbuilder', '.thor'],
filenames: ['Guardfile', 'Capfile', 'Podfile', 'Brewfile', 'Vagrantfile']
}
// Read registration metadata without importing Monaco or executing its grammar loaders.
export function readMonacoAssociations() {
const entry = ts.createSourceFile(
@@ -62,6 +68,12 @@ export function readMonacoAssociations() {
if (!metadata.id) {
throw new Error(`Missing language id in ${file}`)
}
if (metadata.id === 'ruby') {
metadata.extensions = [
...new Set([...metadata.extensions, ...rubyAssociations.extensions])
]
metadata.filenames = [...new Set([...metadata.filenames, ...rubyAssociations.filenames])]
}
registrations.push(metadata)
}
ts.forEachChild(node, visit)
@@ -3,10 +3,36 @@ import { describe, expect, it } from 'vitest'
import { associationsPath, readMonacoAssociations } from './generate-monaco-associations.mjs'
describe('Monaco filename associations', () => {
it('matches every registration shipped by the installed editor entry point', () => {
it('matches the installed editor registrations and curated Orca associations', () => {
expect(
JSON.parse(readFileSync(associationsPath, 'utf8')),
'Run node config/scripts/generate-monaco-associations.mjs after upgrading Monaco'
'Run node config/scripts/generate-monaco-associations.mjs after changing associations or Monaco'
).toEqual(readMonacoAssociations())
})
it('keeps built-in Ruby aliases alongside the curated Ruby associations', () => {
expect(readMonacoAssociations().find((language) => language.id === 'ruby')).toEqual({
id: 'ruby',
extensions: expect.arrayContaining([
'.rb',
'.rbx',
'.rjs',
'.gemspec',
'.pp',
'.rake',
'.ru',
'.jbuilder',
'.thor'
]),
filenames: expect.arrayContaining([
'rakefile',
'Gemfile',
'Guardfile',
'Capfile',
'Podfile',
'Brewfile',
'Vagrantfile'
])
})
})
})
@@ -5,7 +5,11 @@ languages and loads their grammars on demand. Filename detection must not load t
editor itself: it also runs during session restoration and before the editor mounts.
`monaco-language-associations.json` contains the registration metadata from the
installed package's entry point. Regenerate it after upgrading Monaco:
installed package's entry point, plus curated Ruby associations in the generator.
Those add `.rake`, `.ru`, `.jbuilder`, `.thor`, `Guardfile`, `Capfile`, `Podfile`,
`Brewfile` and `Vagrantfile` to the existing Ruby grammar. Change these in
`config/scripts/generate-monaco-associations.mjs`, not the generated JSON.
Regenerate after changing the curated associations or upgrading Monaco:
```sh
node config/scripts/generate-monaco-associations.mjs
@@ -13,7 +17,7 @@ pnpm exec oxfmt --write src/renderer/src/lib/monaco-language-associations.json
```
The generator reads syntax trees without executing contributions or grammar loaders.
Its test compares the checked-in metadata to the installed package. The original
Its test compares the checked-in metadata to the installed package and curated associations. The original
list was verified against a clone of `microsoft/monaco-editor`, tag `v0.55.1`, commit
`516f350bdaf7a82f6731bd128a9ec86a6e5fa47d` (`src/basic-languages` and `src/language`).
@@ -124,6 +124,27 @@ describe('detectLanguage', () => {
expect(detectLanguage('C:\\repo\\force-app\\classes\\ACCOUNTSERVICE.CLS')).toBe('apex')
})
it('maps Ruby DSL extensions to the ruby language id (case-insensitive)', () => {
expect(detectLanguage('lib/tasks/devise.rake')).toBe('ruby')
expect(detectLanguage('config.ru')).toBe('ruby')
expect(detectLanguage('app/views/posts/index.json.jbuilder')).toBe('ruby')
expect(detectLanguage('lib/tasks/install.thor')).toBe('ruby')
expect(detectLanguage('C:\\repo\\lib\\tasks\\DEVISE.RAKE')).toBe('ruby')
})
it('maps Ruby DSL filenames to the ruby language id', () => {
expect(detectLanguage('rails/Guardfile')).toBe('ruby')
expect(detectLanguage('deploy/Capfile')).toBe('ruby')
expect(detectLanguage('ios/Podfile')).toBe('ruby')
expect(detectLanguage('homebrew/Brewfile')).toBe('ruby')
expect(detectLanguage('C:\\vms\\Vagrantfile')).toBe('ruby')
})
it('keeps near-miss Ruby DSL names off the ruby language id', () => {
expect(detectLanguage('report.rake.bak')).toBe('plaintext')
expect(detectLanguage('ruby.rakex')).toBe('plaintext')
})
it.each([
'templates/base.twig',
'templates/node--article.html.twig',
@@ -24,6 +24,45 @@ describe('Monaco filename detection', () => {
expect(detectLanguage(path)).toBe(language)
})
it.each([
'lib/tasks/devise.rake',
'config.ru',
'app/views/posts/index.json.jbuilder',
'lib/tasks/install.thor',
'rails/Guardfile',
'deploy/Capfile',
'ios/Podfile',
'homebrew/Brewfile',
'vms/Vagrantfile',
'C:\\repo\\lib\\tasks\\DEVISE.RAKE',
'C:\\repo\\CONFIG.RU',
'C:\\repo\\INDEX.JSON.JBUILDER',
'C:\\repo\\INSTALL.THOR',
'C:\\vms\\VAGRANTFILE',
'/home/user/folder workspace/tasks/Daily.RaKe',
'main.rb',
'main.rbx',
'main.rjs',
'package.gemspec',
'script.pp',
'Rakefile',
'Gemfile'
])('recognizes Ruby source %s', (path) => {
expect(detectLanguage(path)).toBe('ruby')
expect(detectMonacoFilenameLanguage(path.split(/[\\/]/).at(-1)!)).toBe('ruby')
})
it.each([
'report.rake.bak',
'ruby.rakex',
'Guardfile.bak',
'Guardfilex',
'tasks.rake/README',
'tasks.rake\\README'
])('keeps non-Ruby file %s on plaintext', (path) => {
expect(detectLanguage(path)).toBe('plaintext')
})
it('recognizes every unambiguous upstream extension and filename', () => {
for (const language of associations) {
for (const extension of language.extensions) {
@@ -336,8 +336,16 @@
},
{
"id": "ruby",
"extensions": [".rb", ".rbx", ".rjs", ".gemspec", ".pp"],
"filenames": ["rakefile", "Gemfile"]
"extensions": [".rb", ".rbx", ".rjs", ".gemspec", ".pp", ".rake", ".ru", ".jbuilder", ".thor"],
"filenames": [
"rakefile",
"Gemfile",
"Guardfile",
"Capfile",
"Podfile",
"Brewfile",
"Vagrantfile"
]
},
{
"id": "rust",