UNPKG

@bhsd/codemirror-mediawiki

Version:

Modified CodeMirror mode based on wikimedia/mediawiki-extensions-CodeMirror

189 lines (188 loc) 7.82 kB
import { ensureSyntaxTree } from '@codemirror/language'; import { cssLanguage } from '@codemirror/lang-css'; import { javascriptLanguage } from '@codemirror/lang-javascript'; import { sanitizeInlineStyle } from '@bhsd/common'; import { getWikiLinter, getJsLinter, getCssLinter, getJsonLinter, getLuaLinter } from './linter'; import { posToIndex } from './hover'; /** * 获取Linter选项 * @param opt Linter选项 * @param runtime 是否为运行时选项 */ const getOpt = (opt, runtime) => typeof opt === 'function' ? opt(runtime) : opt; /** * 获取指定行列的位置 * @param doc 文档 * @param line 行号 * @param column 列号 * @param from 子语言起始位置 */ const pos = (doc, line, column, from = 0) => { if (from === 0) { return posToIndex(doc, { line: line - 1, character: column - 1 }); } const lineDesc = doc.lineAt(from); return posToIndex(doc, { line: lineDesc.number + line - 2, character: (line === 1 ? from - lineDesc.from : 0) + column - 1, }); }; const getRange = (doc, line, column, endLine, endColumn, f = 0, t = Infinity) => { const start = pos(doc, line, column, f); return { from: start, to: endLine === undefined ? Math.min(t, start + 1) : pos(doc, endLine, endColumn, f), }; }; const wikiLintSource = async (wikiLint, text, opt, doc, f = 0, t) => (await wikiLint(text, opt)) .map(({ severity, code, message, range: r, from, to, data = [], source }) => ({ source: source, severity: severity === 2 ? 'warning' : 'error', message: source === 'Stylelint' ? message : `${message} (${code})`, actions: data.map(({ title, range, newText }) => ({ name: title, apply(view) { view.dispatch({ changes: { from: posToIndex(doc, range.start), to: posToIndex(doc, range.end), insert: newText, }, }); }, })), ...from === undefined ? getRange(doc, r.start.line + 1, r.start.character + 1, r.end.line + 1, r.end.character + 1, f, t) : { from: from + f, to: (to ?? from) + f }, })); export const getWikiLintSource = async (opt, v) => { const wikiLint = await getWikiLinter(await getOpt(opt), v); const lintSource = async ({ doc }) => wikiLintSource(wikiLint, doc.toString(), await getOpt(opt, true), doc); if (wikiLint.fixer) { lintSource.fixer = (_, rule) => wikiLint.fixer('', rule); } return lintSource; }; const jsLintSource = (esLint, code, opt, doc, f = 0, t) => esLint(code, opt) .map(({ ruleId, message, severity, line, column, endLine, endColumn, fix, suggestions = [] }) => { const diagnostic = { source: 'ESLint', message: message + (ruleId ? ` (${ruleId})` : ''), severity: severity === 1 ? 'warning' : 'error', ...getRange(doc, line, column, endLine, endColumn, f, t), }; if (fix || suggestions.length > 0) { diagnostic.actions = [ ...fix ? [{ name: 'fix', fix }] : [], ...suggestions.map(suggestion => ({ name: 'suggestion', fix: suggestion.fix })), ].map(({ name, fix: { range: [from, to], text } }) => ({ name, apply(view) { view.dispatch({ changes: { from: from + f, to: to + f, insert: text } }); }, })); } return diagnostic; }); export const getJsLintSource = async (opt) => { const esLint = await getJsLinter(); const lintSource = async ({ doc }) => jsLintSource(esLint, doc.toString(), await getOpt(opt), doc); lintSource.fixer = (doc, rule) => esLint.fixer(doc.toString(), rule); return lintSource; }; const cssLintSource = async (styleLint, code, opt, doc, f = 0, t) => { let option = opt ?? {}; if (!('extends' in option || 'rules' in option)) { option = { rules: option }; } return (await styleLint(code, option)) .map(({ text, severity, line, column, endLine, endColumn, fix }) => { const diagnostic = { source: 'Stylelint', message: text, severity, ...getRange(doc, line, column, endLine, endColumn, f, t), }; if (fix) { diagnostic.actions = [ { name: 'fix', apply(view) { view.dispatch({ changes: { from: fix.range[0] + f, to: fix.range[1] + f, insert: fix.text }, }); }, }, ]; } return diagnostic; }); }; export const getCssLintSource = async (opt) => { const styleLint = await getCssLinter(); const lintSource = async ({ doc }) => cssLintSource(styleLint, doc.toString(), await getOpt(opt), doc); lintSource.fixer = async (doc, rule) => styleLint.fixer(doc.toString(), rule); return lintSource; }; export const getVueLintSource = async (opt) => { const styleLint = await getCssLinter(), esLint = await getJsLinter(); return async (state) => { const { doc } = state, option = await getOpt(opt, true) ?? {}, js = option['js'], css = option['css']; return [ ...(await Promise.all(cssLanguage.findRegions(state).map(async ({ from, to }) => { const node = ensureSyntaxTree(state, from)?.resolve(from, 1); if (node?.name === 'AttributeValue') { return (await cssLintSource(styleLint, `a {${sanitizeInlineStyle(state.sliceDoc(from, to))}}`, css, doc, from - 3, to + 1)).filter(({ from: f, to: t }) => f <= to && t >= from) .map((diagnostic) => { diagnostic.from = Math.max(diagnostic.from, from); diagnostic.to = Math.min(diagnostic.to, to); return diagnostic; }); } return node ? cssLintSource(styleLint, state.sliceDoc(from, to), css, doc, from, to) : []; }))).flat(), ...javascriptLanguage.findRegions(state) .flatMap(({ from, to }) => jsLintSource(esLint, state.sliceDoc(from, to), js, doc, from, to)), ]; }; }; export const getHTMLLintSource = async (opt, view, language) => { const vueLintSource = await getVueLintSource(opt), wikiLint = await getWikiLinter({ include: false, ...await getOpt(opt) }, view); return async (state) => { const { doc } = state, option = await getOpt(opt, true) ?? {}, wiki = option['wiki']; return [ ...await vueLintSource(state), ...(await Promise.all(language.findRegions(state) .map(({ from, to }) => wikiLintSource(wikiLint, state.sliceDoc(from, to), wiki, doc, from, to)))).flat(), ]; }; }; export const getJsonLintSource = () => { const jsonLint = getJsonLinter(); return ({ doc }) => { const [e] = jsonLint(doc.toString()); if (e) { const { message, severity, line, column, position } = e; let from = 0; if (position) { from = Number(position); } else if (line && column) { from = pos(doc, Number(line), Number(column)); } return [{ message, severity, from, to: from }]; } return []; }; }; export const getLuaLintSource = async () => { const luaLint = await getLuaLinter(); return async ({ doc }) => (await luaLint(doc.toString())) .map(({ line, column, end_column: endColumn, msg: message, severity }) => ({ source: 'Luacheck', message, severity: severity === 1 ? 'warning' : 'error', from: pos(doc, line, column), to: pos(doc, line, endColumn + 1), })); };