mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-11 09:15:31 +02:00
review feedback: scope fetch lint to web_src, add detection tests
- Move `no-restricted-globals`/`-properties` rules to the web_src block; the underlying restrictions only apply to browser code, so the eslint-disable on `fetch` in the build script can go away. - Extract `buildLanguageDescriptions` so the assembled list is callable from tests, and add coverage for matchFilename across the extended Linguist + manual rules. Co-Authored-By: Claude (Opus 4.7) <noreply@anthropic.com>
This commit is contained in:
parent
5764fc2546
commit
1bff063446
@ -570,8 +570,6 @@ export default defineConfig([
|
||||
'no-redeclare': [0], // must be disabled for typescript overloads
|
||||
'no-regex-spaces': [2],
|
||||
'no-restricted-exports': [0],
|
||||
'no-restricted-globals': [2, ...restrictedGlobals],
|
||||
'no-restricted-properties': [2, ...restrictedProperties],
|
||||
'no-restricted-imports': [2, {paths: [
|
||||
{name: 'jquery', message: 'Use the global $ instead', allowTypeImports: true},
|
||||
]}],
|
||||
@ -1022,5 +1020,9 @@ export default defineConfig([
|
||||
{
|
||||
files: ['web_src/**/*'],
|
||||
languageOptions: {globals: {...globals.browser, ...globals.jquery}},
|
||||
rules: {
|
||||
'no-restricted-globals': [2, ...restrictedGlobals],
|
||||
'no-restricted-properties': [2, ...restrictedProperties],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
@ -44,7 +44,7 @@ type CmLanguage = {
|
||||
};
|
||||
|
||||
async function main() {
|
||||
const res = await fetch(linguistUrl); // eslint-disable-line no-restricted-globals -- node build script, not browser code
|
||||
const res = await fetch(linguistUrl);
|
||||
if (!res.ok) throw new Error(`fetch ${linguistUrl} failed: ${res.status}`);
|
||||
const linguist = parseYaml(await res.text()) as Record<string, LinguistEntry>;
|
||||
|
||||
|
||||
84
web_src/js/modules/codeeditor/main.test.ts
Normal file
84
web_src/js/modules/codeeditor/main.test.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import {buildLanguageDescriptions, importCodemirror} from './main.ts';
|
||||
|
||||
test('matchFilename — language detection covers extended rules', async () => {
|
||||
const cm = await importCodemirror();
|
||||
const list = buildLanguageDescriptions(cm);
|
||||
const match = (filename: string) =>
|
||||
cm.language.LanguageDescription.matchFilename(list, filename)?.name;
|
||||
|
||||
expect(match('.bashrc')).toBe('Shell');
|
||||
expect(match('.zshrc')).toBe('Shell');
|
||||
expect(match('.envrc')).toBe('Shell');
|
||||
expect(match('foo.zsh')).toBe('Shell');
|
||||
expect(match('foo.bats')).toBe('Shell');
|
||||
expect(match('PKGBUILD')).toBe('Shell');
|
||||
|
||||
expect(match('.gitconfig')).toBe('Properties files');
|
||||
expect(match('.editorconfig')).toBe('Properties files');
|
||||
expect(match('.npmrc')).toBe('Properties files');
|
||||
expect(match('foo.cfg')).toBe('Properties files');
|
||||
expect(match('foo.conf')).toBe('Properties files');
|
||||
expect(match('nginx.conf')).toBe('Nginx');
|
||||
|
||||
expect(match('Cargo.lock')).toBe('TOML');
|
||||
expect(match('Pipfile')).toBe('TOML');
|
||||
expect(match('poetry.lock')).toBe('TOML');
|
||||
|
||||
expect(match('Containerfile')).toBe('Dockerfile');
|
||||
expect(match('Containerfile.test')).toBe('Dockerfile');
|
||||
expect(match('Dockerfile')).toBe('Dockerfile');
|
||||
expect(match('Dockerfile.dev')).toBe('Dockerfile');
|
||||
|
||||
expect(match('Brewfile')).toBe('Ruby');
|
||||
expect(match('Vagrantfile')).toBe('Ruby');
|
||||
expect(match('Gemfile')).toBe('Ruby');
|
||||
expect(match('foo.gemspec')).toBe('Ruby');
|
||||
expect(match('foo.rake')).toBe('Ruby');
|
||||
expect(match('foo.ru')).toBe('Ruby');
|
||||
|
||||
expect(match('foo.psgi')).toBe('Perl');
|
||||
expect(match('foo.pyi')).toBe('Python');
|
||||
expect(match('Snakefile')).toBe('Python');
|
||||
|
||||
expect(match('foo.webmanifest')).toBe('JSON');
|
||||
expect(match('foo.geojson')).toBe('JSON');
|
||||
expect(match('composer.lock')).toBe('JSON');
|
||||
expect(match('bun.lock')).toBe('JSON');
|
||||
|
||||
expect(match('foo.tcc')).toBe('C++');
|
||||
expect(match('foo.tpp')).toBe('C++');
|
||||
expect(match('foo.cppm')).toBe('C++');
|
||||
expect(match('foo.ixx')).toBe('C++');
|
||||
|
||||
expect(match('foo.xhtml')).toBe('HTML');
|
||||
expect(match('foo.jsh')).toBe('Java');
|
||||
expect(match('.Rprofile')).toBe('R');
|
||||
|
||||
expect(match('Makefile')).toBe('Makefile');
|
||||
expect(match('Makefile.am')).toBe('Makefile');
|
||||
expect(match('BSDmakefile')).toBe('Makefile');
|
||||
expect(match('GNUmakefile')).toBe('Makefile');
|
||||
expect(match('foo.mk')).toBe('Makefile');
|
||||
|
||||
expect(match('.env')).toBe('Dotenv');
|
||||
expect(match('.env.local')).toBe('Dotenv');
|
||||
|
||||
expect(match('foo.md')).toBe('Markdown');
|
||||
expect(match('foo.mdown')).toBe('Markdown');
|
||||
expect(match('foo.json5')).toBe('JSON5');
|
||||
expect(match('tsconfig.json')).toBe('JSON');
|
||||
|
||||
// Smoke tests for languages that already worked, to guard against regressions.
|
||||
expect(match('foo.go')).toBe('Go');
|
||||
expect(match('foo.rs')).toBe('Rust');
|
||||
expect(match('foo.ts')).toBe('TypeScript');
|
||||
expect(match('foo.py')).toBe('Python');
|
||||
expect(match('foo.html')).toBe('HTML');
|
||||
expect(match('foo.css')).toBe('CSS');
|
||||
expect(match('foo.lua')).toBe('Lua');
|
||||
|
||||
// Genuinely ambiguous extensions left unhighlighted on purpose.
|
||||
expect(match('foo.cgi')).toBeUndefined();
|
||||
expect(match('foo.fcgi')).toBeUndefined();
|
||||
expect(match('foo.fish')).toBeUndefined();
|
||||
});
|
||||
@ -44,7 +44,7 @@ export type CodemirrorEditor = {
|
||||
|
||||
export type CodemirrorModules = Awaited<ReturnType<typeof importCodemirror>>;
|
||||
|
||||
async function importCodemirror() {
|
||||
export async function importCodemirror() {
|
||||
const [autocomplete, commands, language, languageData, lint, search, state, view, highlight, indentMarkers, vscodeKeymap] = await Promise.all([
|
||||
import('@codemirror/autocomplete'),
|
||||
import('@codemirror/commands'),
|
||||
@ -73,6 +73,48 @@ const escapeRegex = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const filenameUnion = (filenames: string[]) =>
|
||||
filenames.length ? new RegExp(`^(${filenames.map(escapeRegex).join('|')})$`) : undefined;
|
||||
|
||||
export function buildLanguageDescriptions(cm: CodemirrorModules): LanguageDescription[] {
|
||||
const markdown = linguistLanguages.find((l) => l.name === 'Markdown');
|
||||
const dockerfile = linguistLanguages.find((l) => l.name === 'Dockerfile');
|
||||
const list: LanguageDescription[] = [
|
||||
...buildBaseLanguages(cm),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'Markdown', extensions: markdown?.extensions ?? ['md', 'markdown', 'mkd'],
|
||||
load: async () => (await import('@codemirror/lang-markdown')).markdown({codeLanguages: list}),
|
||||
}),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'Dockerfile', extensions: dockerfile?.extensions ?? ['dockerfile', 'containerfile'],
|
||||
filename: /^(Containerfile|Dockerfile)(\..+)?$/i,
|
||||
load: async () => new cm.language.LanguageSupport(cm.language.StreamLanguage.define((await import('@codemirror/legacy-modes/mode/dockerfile')).dockerFile)),
|
||||
}),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'Elixir', extensions: ['ex', 'exs'],
|
||||
load: async () => (await import('codemirror-lang-elixir')).elixir(),
|
||||
}),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'Nix', extensions: ['nix'],
|
||||
load: async () => (await import('@replit/codemirror-lang-nix')).nix(),
|
||||
}),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'Svelte', extensions: ['svelte'],
|
||||
load: async () => (await import('@replit/codemirror-lang-svelte')).svelte(),
|
||||
}),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'Makefile', extensions: ['mk', 'mak', 'make'], filename: /^(GNU|BSD)?[Mm]akefile(\..+)?$/,
|
||||
load: async () => new cm.language.LanguageSupport(cm.language.StreamLanguage.define((await import('@codemirror/legacy-modes/mode/shell')).shell)),
|
||||
}),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'Dotenv', extensions: ['env'], filename: /^\.env(\..*)?$/,
|
||||
load: async () => new cm.language.LanguageSupport(cm.language.StreamLanguage.define((await import('@codemirror/legacy-modes/mode/shell')).shell)),
|
||||
}),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'JSON5', extensions: ['json5', 'jsonc'],
|
||||
load: async () => (await import('@codemirror/lang-json')).json(),
|
||||
}),
|
||||
];
|
||||
return list;
|
||||
}
|
||||
|
||||
let baseLanguagesCache: LanguageDescription[] | null = null;
|
||||
function buildBaseLanguages(cm: CodemirrorModules): LanguageDescription[] {
|
||||
if (baseLanguagesCache) return baseLanguagesCache;
|
||||
@ -119,45 +161,7 @@ export async function createCodeEditor(textarea: HTMLTextAreaElement, filenameIn
|
||||
const previewableExts = new Set(config.previewableExtensions || []);
|
||||
const lineWrapExts = config.lineWrapExtensions || [];
|
||||
const cm = await importCodemirror();
|
||||
const markdown = linguistLanguages.find((l) => l.name === 'Markdown');
|
||||
const dockerfile = linguistLanguages.find((l) => l.name === 'Dockerfile');
|
||||
|
||||
const languageDescriptions: LanguageDescription[] = [
|
||||
...buildBaseLanguages(cm),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'Markdown', extensions: markdown?.extensions ?? ['md', 'markdown', 'mkd'],
|
||||
load: async () => (await import('@codemirror/lang-markdown')).markdown({codeLanguages: languageDescriptions}),
|
||||
}),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'Dockerfile', extensions: dockerfile?.extensions ?? ['dockerfile', 'containerfile'],
|
||||
filename: /^(Containerfile|Dockerfile)(\..+)?$/i,
|
||||
load: async () => new cm.language.LanguageSupport(cm.language.StreamLanguage.define((await import('@codemirror/legacy-modes/mode/dockerfile')).dockerFile)),
|
||||
}),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'Elixir', extensions: ['ex', 'exs'],
|
||||
load: async () => (await import('codemirror-lang-elixir')).elixir(),
|
||||
}),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'Nix', extensions: ['nix'],
|
||||
load: async () => (await import('@replit/codemirror-lang-nix')).nix(),
|
||||
}),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'Svelte', extensions: ['svelte'],
|
||||
load: async () => (await import('@replit/codemirror-lang-svelte')).svelte(),
|
||||
}),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'Makefile', extensions: ['mk', 'mak', 'make'], filename: /^(GNU|BSD)?[Mm]akefile(\..+)?$/,
|
||||
load: async () => new cm.language.LanguageSupport(cm.language.StreamLanguage.define((await import('@codemirror/legacy-modes/mode/shell')).shell)),
|
||||
}),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'Dotenv', extensions: ['env'], filename: /^\.env(\..*)?$/,
|
||||
load: async () => new cm.language.LanguageSupport(cm.language.StreamLanguage.define((await import('@codemirror/legacy-modes/mode/shell')).shell)),
|
||||
}),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'JSON5', extensions: ['json5', 'jsonc'],
|
||||
load: async () => (await import('@codemirror/lang-json')).json(),
|
||||
}),
|
||||
];
|
||||
const languageDescriptions = buildLanguageDescriptions(cm);
|
||||
const matchedLang = cm.language.LanguageDescription.matchFilename(languageDescriptions, config.filename);
|
||||
|
||||
const container = document.createElement('div');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user