JSON linting

This commit is contained in:
Gregory Schier
2025-11-08 15:24:31 -08:00
parent f2a63087b0
commit 054916b7af
5 changed files with 222 additions and 31 deletions

View File

@@ -20,6 +20,7 @@
}
/* Matching bracket */
.cm-matchingBracket {
@apply bg-transparent border-b border-b-text-subtle;
}
@@ -76,6 +77,16 @@
.cm-gutterElement {
@apply cursor-default;
}
.cm-lint-marker {
@apply cursor-default opacity-80 hover:opacity-100 transition-opacity;
@apply rounded-full w-[0.9em] h-[0.9em];
content: '';
&.cm-lint-marker-error {
@apply bg-danger;
}
}
}
.template-tag {
@@ -110,6 +121,7 @@
@apply font-mono text-xs;
/* Hide scrollbars */
&::-webkit-scrollbar-corner,
&::-webkit-scrollbar {
@apply hidden !important;
@@ -202,7 +214,7 @@
}
.cm-editor .cm-activeLineGutter {
@apply bg-transparent;
@apply bg-transparent text-text-subtle;
}
/* Cursor and mouse cursor for readonly mode */
@@ -226,17 +238,35 @@
}
}
.cm-tooltip-lint {
@apply font-mono text-editor rounded overflow-hidden bg-surface-highlight border border-border shadow !important;
.cm-diagnostic-error {
@apply border-l-danger px-4 py-2;
}
}
.cm-lintPoint {
&.cm-lintPoint-error {
&::after {
@apply border-b-danger;
}
}
}
.cm-tooltip.cm-tooltip-hover {
@apply shadow-lg bg-surface rounded text-text-subtle border border-border-subtle z-50 pointer-events-auto text-sm;
@apply p-1.5;
/* Style the tooltip for popping up "open in browser" and other stuff */
a, button {
@apply text-text hover:bg-surface-highlight w-full h-sm flex items-center px-2 rounded;
}
a {
@apply cursor-default !important;
&::after {
@apply text-text bg-text h-3 w-3 ml-1;
content: '';

View File

@@ -19,7 +19,7 @@ import {
indentOnInput,
syntaxHighlighting,
} from '@codemirror/language';
import { lintKeymap } from '@codemirror/lint';
import { linter, lintGutter, lintKeymap } from '@codemirror/lint';
import { search, searchKeymap } from '@codemirror/search';
import type { Extension } from '@codemirror/state';
@@ -45,6 +45,7 @@ import { renderMarkdown } from '../../../lib/markdown';
import { pluralizeCount } from '../../../lib/pluralize';
import { showGraphQLDocExplorerAtom } from '../../graphql/graphqlAtoms';
import type { EditorProps } from './Editor';
import { jsonParseLinter } from './json-lint';
import { pairs } from './pairs/extension';
import { text } from './text/extension';
import type { TwigCompletionOption } from './twig/completion';
@@ -153,6 +154,10 @@ export function getLanguageExtension({
];
}
if (language === 'json') {
extraExtensions.push(linter(jsonParseLinter()), lintGutter());
}
const maybeBase = language ? syntaxExtensions[language] : null;
const base = typeof maybeBase === 'function' ? maybeBase() : null;
if (base == null) {

View File

@@ -0,0 +1,36 @@
import type { Diagnostic } from '@codemirror/lint';
import type { EditorView } from '@codemirror/view';
import { parse as jsonLintParse } from '@prantlf/jsonlint';
const TEMPLATE_SYNTAX_REGEX = /\$\{\[[\s\S]*?]}/g;
export function jsonParseLinter() {
return (view: EditorView): Diagnostic[] => {
try {
const doc = view.state.doc.toString();
// We need lint to not break on stuff like {"foo:" ${[ ... ]}} so we'll replace all template
// syntax with repeating `1` characters, so it's valid JSON and the position is still correct.
const escapedDoc = doc.replace(TEMPLATE_SYNTAX_REGEX, '1');
jsonLintParse(escapedDoc);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
if (!('location' in err)) {
return [];
}
// const line = location?.start?.line;
// const column = location?.start?.column;
if (err.location.start.offset) {
return [
{
from: err.location.start.offset,
to: err.location.start.offset,
severity: 'error',
message: err.message,
},
];
}
}
return [];
};
}