Skip to content

Commit

Permalink
feat: add to editor an error markers (#116)
Browse files Browse the repository at this point in the history
  • Loading branch information
magicmatatjahu authored Oct 14, 2021
1 parent 86fa4ea commit c293c7d
Show file tree
Hide file tree
Showing 12 changed files with 402 additions and 81 deletions.
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,10 @@
"eslint-plugin-sonarjs": "^0.10.0",
"react-scripts": "4.0.3",
"typescript": "^4.4.3"
},
"jest": {
"transformIgnorePatterns": [
"node_modules\/(?!(monaco-editor)\/)"
]
}
}
4 changes: 2 additions & 2 deletions src/components/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -267,10 +267,10 @@ export const Navigation: React.FunctionComponent<NavigationProps> = () => {
const spec = parserState.parsedSpec.get();

useEffect(() => {
// remove `#` char
const fn = () => {
// remove `#` char
setHash(window.location.hash.substring(1));
const h = window.location.hash.startsWith('#') ? window.location.hash.substring(1) : window.location.hash;
setHash(h);
};
fn();
window.addEventListener('hashchange', fn);
Expand Down
5 changes: 3 additions & 2 deletions src/components/Template/HTMLWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const HTMLWrapper: React.FunctionComponent<HTMLWrapperProps> = () => {
const parserState = state.useParserState();
const editorState = state.useEditorState();

const documentValid = parserState.valid.get();
const editorLoaded = editorState.editorLoaded.get();

// using "json()" for removing proxy from value
Expand All @@ -33,10 +34,10 @@ export const HTMLWrapper: React.FunctionComponent<HTMLWrapperProps> = () => {
);
}

if (!parsedSpec) {
if (!documentValid) {
return (
<div className="flex flex-1 overflow-hidden h-full justify-center items-center text-2xl mx-auto px-6 text-center">
Empty or invalid document. Please fix errors/define AsyncAPI document.
<p>Empty or invalid document. Please fix errors/define AsyncAPI document.</p>
</div>
);
}
Expand Down
4 changes: 3 additions & 1 deletion src/components/Template/Template.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ import { HTMLWrapper } from './HTMLWrapper';
interface TemplateProps {}

export const Template: React.FunctionComponent<TemplateProps> = () => {
return <HTMLWrapper />;
return (
<HTMLWrapper />
);
};
42 changes: 25 additions & 17 deletions src/components/Terminal/ProblemsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,34 @@ export const ProblemsTabContent: React.FunctionComponent<ProblemsTabProps> = ()
<table className="border-collapse w-full">
<thead>
<tr>
<th className="w-8">Line</th>
<th className="p-2 text-left">Description</th>
<th className="p-2 w-8">Line</th>
<th className="p-2 text-left">Title</th>
<th className="p-2 text-left">Details</th>
</tr>
</thead>
<tbody>
{errors.map((err: any, id) => (
<tr key={err.title || id} className="border-t border-gray-700">
<td
className="p-2 cursor-pointer text-center"
onClick={() =>
NavigationService.scrollToEditorLine(
err.location?.startLine || 0,
)
}
>
{err.location?.startLine || '-'}
</td>
<td className="p-2 text-left">{err.title}</td>
</tr>
))}
{errors.map((err: any, id) => {
const { title, detail, location } = err;
let renderedLine = err.location?.startLine;
renderedLine = renderedLine && err.location?.startColumn ? `${renderedLine}:${err.location?.startColumn}` : renderedLine;
return (
<tr key={title || id} className="border-t border-gray-700">
<td
className="p-2 cursor-pointer"
onClick={() =>
NavigationService.scrollToEditorLine(
location?.startLine || 0,
location?.startColumn,
)
}
>
{renderedLine || '-'}
</td>
<td className="p-2 text-left">{title}</td>
<td className="p-2 text-left">{detail || '-'}</td>
</tr>
);
})}
</tbody>
</table>
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/components/Terminal/TerminalInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const TerminalInfo: React.FunctionComponent<TerminalInfoProps> = () => {

const actualVersion = parserState.parsedSpec.get()?.version() || '2.0.0';
const latestVersion = SpecificationService.getLastVersion();
const documentValid = parserState.valid.get();
const errors = parserState.errors.get();

function onNonLatestClick(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
Expand Down Expand Up @@ -61,7 +62,7 @@ export const TerminalInfo: React.FunctionComponent<TerminalInfoProps> = () => {
<span>Valid</span>
</div>
)}
{actualVersion !== latestVersion && (
{actualVersion !== latestVersion && documentValid === true && (
<div className="ml-3" onClick={onNonLatestClick}>
<span className="text-yellow-500">
<svg xmlns="http://www.w3.org/2000/svg" className="inline-block h-5 w-5 mr-1 -mt-0.5" viewBox="0 0 20 20" fill="currentColor">
Expand Down
67 changes: 67 additions & 0 deletions src/services/editor.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,73 @@ export class EditorService {
}
}

static applyErrorMarkers(errors: any[] = []) {
const editor = this.getInstance();
const Monaco = window.Monaco;

if (!editor || !Monaco) {
return;
}

const model = editor.getModel();
if (!model) {
return;
}

const oldDecorations = state.editor.decorations.get();
editor.deltaDecorations(oldDecorations, []);
Monaco.editor.setModelMarkers(model, 'asyncapi', []);
if (errors.length === 0) {
return;
}

const { markers, decorations } = this.createErrorMarkers(errors, model, Monaco);
Monaco.editor.setModelMarkers(model, 'asyncapi', markers);
editor.deltaDecorations(oldDecorations, decorations);
}

static createErrorMarkers(errors: any[] = [], model: monacoAPI.editor.ITextModel, Monaco: typeof monacoAPI) {
const newDecorations: monacoAPI.editor.IModelDecoration[] = [];
const newMarkers: monacoAPI.editor.IMarkerData[] = [];
errors.forEach(err => {
const { title, detail } = err;
let location = err.location;

if (!location || location.jsonPointer === '/') {
const fullRange = model.getFullModelRange();
location = {};
location.startLine = fullRange.startLineNumber;
location.startColumn = fullRange.startColumn;
location.endLine = fullRange.endLineNumber;
location.endColumn = fullRange.endColumn;
}
const { startLine, startColumn, endLine, endColumn } = location;

const detailContent = detail ? `\n\n${detail}` : '';
newMarkers.push({
startLineNumber: startLine,
startColumn,
endLineNumber: typeof endLine === 'number' ? endLine : startLine,
endColumn: typeof endColumn === 'number' ? endColumn : startColumn,
severity: monacoAPI.MarkerSeverity.Error,
message: `${title}${detailContent}`,
});
newDecorations.push({
id: 'asyncapi',
ownerId: 0,
range: new Monaco.Range(
startLine,
startColumn,
typeof endLine === 'number' ? endLine : startLine,
typeof endColumn === 'number' ? endColumn : startColumn
),
options: { inlineClassName: 'bg-red-500-20' },
});
});

return { decorations: newDecorations, markers: newMarkers };
}

private static fileName = 'asyncapi';

private static downloadFile(content: string, fileName: string) {
Expand Down
8 changes: 6 additions & 2 deletions src/services/navigation.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ export class NavigationService {
hash = hash || window.location.hash.substring(1);
try {
const escapedHash = CSS.escape(hash);
if (!escapedHash || escapedHash === '#') {
return;
}

const items = document.querySelectorAll(
escapedHash.startsWith('#') ? escapedHash : `#${escapedHash}`,
);
Expand All @@ -53,11 +57,11 @@ export class NavigationService {
}
}

static scrollToEditorLine(startLine: number) {
static scrollToEditorLine(startLine: number, columnLine = 1) {
try {
const editor = window.Editor;
editor && editor.revealLineInCenter(startLine);
editor && editor.setPosition({ column: 1, lineNumber: startLine });
editor && editor.setPosition({ lineNumber: startLine, column: columnLine });
} catch (err) {
console.error(err);
}
Expand Down
122 changes: 66 additions & 56 deletions src/services/specification.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { parse, AsyncAPIDocument } from '@asyncapi/parser';
// @ts-ignore
import specs from '@asyncapi/specs';

import { EditorService } from './editor.service';
import { FormatService } from './format.service';
import { MonacoService } from './monaco.service';

Expand All @@ -14,9 +15,12 @@ export class SpecificationService {
const parserState = state.parser;
return parse(rawSpec)
.then(asyncApiDoc => {
parserState.parsedSpec.set(asyncApiDoc);
parserState.valid.set(true);
parserState.errors.set([]);
parserState.set({
parsedSpec: asyncApiDoc,
lastParsedSpec: asyncApiDoc,
valid: true,
errors: [],
});

MonacoService.updateLanguageConfig(asyncApiDoc);
if (this.shouldInformAboutLatestVersion(asyncApiDoc.version())) {
Expand All @@ -27,13 +31,19 @@ export class SpecificationService {
});
}

EditorService.applyErrorMarkers([]);
return asyncApiDoc;
})
.catch(err => {
const errors = this.filterErrors(err, rawSpec);
parserState.parsedSpec.set(null);
parserState.valid.set(false);
parserState.errors.set(errors);

parserState.set({
parsedSpec: null,
lastParsedSpec: parserState.parsedSpec.get() || parserState.lastParsedSpec.get(),
valid: false,
errors,
});
EditorService.applyErrorMarkers(errors);
});
}

Expand Down Expand Up @@ -61,56 +71,6 @@ export class SpecificationService {
return Object.keys(specs).pop() as string;
}

static errorHasLocation(err: any) {
return (
this.isValidationError(err) ||
this.isJsonError(err) ||
this.isYamlError(err) ||
this.isDereferenceError(err) ||
this.isUnsupportedVersionError(err)
);
}

static isValidationError(err: any) {
return (
err &&
err.type === 'https://github.com/asyncapi/parser-js/validation-errors'
);
}

static isJsonError(err: any) {
return (
err && err.type === 'https://github.com/asyncapi/parser-js/invalid-json'
);
}

static isYamlError(err: any) {
return (
err && err.type === 'https://github.com/asyncapi/parser-js/invalid-yaml'
);
}

static isUnsupportedVersionError(err: any) {
return (
err &&
err.type === 'https://github.com/asyncapi/parser-js/unsupported-version'
);
}

static isDereferenceError(err: any) {
return (
err &&
err.type === 'https://github.com/asyncapi/parser-js/dereference-error'
);
}

static isNotSupportedVersion(rawSpec: string): boolean {
if (this.notSupportedVersions.test(rawSpec.trim())) {
return true;
}
return false;
}

static shouldInformAboutLatestVersion(
version: string,
): boolean {
Expand All @@ -134,6 +94,16 @@ export class SpecificationService {
return false;
}

static errorHasLocation(err: any) {
return (
this.isValidationError(err) ||
this.isJsonError(err) ||
this.isYamlError(err) ||
this.isDereferenceError(err) ||
this.isUnsupportedVersionError(err)
);
}

private static notSupportedVersions = /('|"|)asyncapi('|"|): ('|"|)(1.0.0|1.1.0|1.2.0|2.0.0-rc1|2.0.0-rc2)('|"|)/;

private static filterErrors(err: any, rawSpec: string) {
Expand Down Expand Up @@ -171,4 +141,44 @@ export class SpecificationService {
}
return errors;
}

private static isValidationError(err: any) {
return (
err &&
err.type === 'https://github.com/asyncapi/parser-js/validation-errors'
);
}

private static isJsonError(err: any) {
return (
err && err.type === 'https://github.com/asyncapi/parser-js/invalid-json'
);
}

private static isYamlError(err: any) {
return (
err && err.type === 'https://github.com/asyncapi/parser-js/invalid-yaml'
);
}

private static isUnsupportedVersionError(err: any) {
return (
err &&
err.type === 'https://github.com/asyncapi/parser-js/unsupported-version'
);
}

private static isDereferenceError(err: any) {
return (
err &&
err.type === 'https://github.com/asyncapi/parser-js/dereference-error'
);
}

private static isNotSupportedVersion(rawSpec: string): boolean {
if (this.notSupportedVersions.test(rawSpec.trim())) {
return true;
}
return false;
}
}
Loading

0 comments on commit c293c7d

Please sign in to comment.