From 97fd7edb39e9bbd1212ddef1a62848252e9a0d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E4=B8=83?= Date: Sat, 3 Oct 2026 15:39:39 +0800 Subject: [PATCH] fix(editor): recognize Ruby task and configuration files Add Ruby task/configuration filenames to the existing generated language associations. Co-authored-by: ggbdpq Co-authored-by: Neil --- .../scripts/generate-monaco-associations.mjs | 12 ++++++ .../generate-monaco-associations.test.mjs | 30 +++++++++++++- .../reference/monaco-language-associations.md | 8 +++- src/renderer/src/lib/language-detect.test.ts | 21 ++++++++++ .../src/lib/monaco-filename-language.test.ts | 39 +++++++++++++++++++ .../src/lib/monaco-language-associations.json | 12 +++++- 6 files changed, 116 insertions(+), 6 deletions(-) diff --git a/config/scripts/generate-monaco-associations.mjs b/config/scripts/generate-monaco-associations.mjs index b5c2e99c00a..ea81be3fa47 100644 --- a/config/scripts/generate-monaco-associations.mjs +++ b/config/scripts/generate-monaco-associations.mjs @@ -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) diff --git a/config/scripts/generate-monaco-associations.test.mjs b/config/scripts/generate-monaco-associations.test.mjs index 0e7563cd49a..1076ea78c8a 100644 --- a/config/scripts/generate-monaco-associations.test.mjs +++ b/config/scripts/generate-monaco-associations.test.mjs @@ -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' + ]) + }) + }) }) diff --git a/docs/reference/monaco-language-associations.md b/docs/reference/monaco-language-associations.md index a159477e176..ee985d85959 100644 --- a/docs/reference/monaco-language-associations.md +++ b/docs/reference/monaco-language-associations.md @@ -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`). diff --git a/src/renderer/src/lib/language-detect.test.ts b/src/renderer/src/lib/language-detect.test.ts index a323692e473..b8cd6e45353 100644 --- a/src/renderer/src/lib/language-detect.test.ts +++ b/src/renderer/src/lib/language-detect.test.ts @@ -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', diff --git a/src/renderer/src/lib/monaco-filename-language.test.ts b/src/renderer/src/lib/monaco-filename-language.test.ts index 013e9cc7f17..94caf1b91cc 100644 --- a/src/renderer/src/lib/monaco-filename-language.test.ts +++ b/src/renderer/src/lib/monaco-filename-language.test.ts @@ -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) { diff --git a/src/renderer/src/lib/monaco-language-associations.json b/src/renderer/src/lib/monaco-language-associations.json index 6effc005140..8a2dddfc490 100644 --- a/src/renderer/src/lib/monaco-language-associations.json +++ b/src/renderer/src/lib/monaco-language-associations.json @@ -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",