Skip to content

Commit

Permalink
feat: integrate new ParserJS with Spectral
Browse files Browse the repository at this point in the history
  • Loading branch information
magicmatatjahu committed Oct 5, 2022
1 parent dd44872 commit 4fb40bf
Show file tree
Hide file tree
Showing 16 changed files with 38,797 additions and 408 deletions.
5 changes: 5 additions & 0 deletions craco.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ module.exports = {
test: /\.yml$/i,
loader: 'raw-loader',
});

webpackConfig.resolve.alias = webpackConfig.resolve.alias || {};
webpackConfig.resolve.alias['nimma/fallbacks'] = require.resolve('./node_modules/nimma/dist/legacy/cjs/fallbacks/index.js');
webpackConfig.resolve.alias['nimma/legacy'] = require.resolve('./node_modules/nimma/dist/legacy/cjs/index.js');

return webpackConfig;
}
}
Expand Down
38,731 changes: 38,456 additions & 275 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/components/Editor/EditorDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import toast from 'react-hot-toast';
import { FaEllipsisH } from 'react-icons/fa';
import { hasErrorDiagnostic } from '@asyncapi/parser/esm/utils';

import {
ConvertModal,
Expand All @@ -20,7 +21,7 @@ export const EditorDropdown: React.FunctionComponent<EditorDropdownProps> = () =
const parserState = state.useParserState();

const language = editorState.language.get();
const hasParserErrors = parserState.errors.get().length > 0;
const hasParserErrors = hasErrorDiagnostic(parserState.diagnostics.get());

const importFileButton = (
<label
Expand Down
12 changes: 6 additions & 6 deletions src/components/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import React, { useEffect, useState } from 'react';

import { AsyncAPIDocument } from '@asyncapi/parser';

import { NavigationService } from '../services';
import state from '../state';

import type { OldAsyncAPIDocument as AsyncAPIDocument } from '@asyncapi/parser/esm';

interface NavigationProps {
className?: string;
}
Expand Down Expand Up @@ -166,7 +166,7 @@ const MessagesNavigation: React.FunctionComponent<NavigationSectionProps> = ({
language,
hash,
}) => {
const messages = Object.keys(spec.components().messages() || {}).map(
const messages = Object.keys(spec.components()?.messages() || {}).map(
messageName => (
<li
key={messageName}
Expand Down Expand Up @@ -215,7 +215,7 @@ const SchemasNavigation: React.FunctionComponent<NavigationSectionProps> = ({
language,
hash,
}) => {
const schemas = Object.keys(spec.components().schemas() || {}).map(
const schemas = Object.keys(spec.components()?.schemas() || {}).map(
schemaName => (
<li
key={schemaName}
Expand Down Expand Up @@ -344,7 +344,7 @@ export const Navigation: React.FunctionComponent<NavigationProps> = ({
hash={hash}
/>
</li>
{spec.hasComponents() && spec.components().hasMessages() && (
{spec.hasComponents() && spec.components()?.hasMessages() && (
<li className="mb-4">
<MessagesNavigation
spec={spec}
Expand All @@ -354,7 +354,7 @@ export const Navigation: React.FunctionComponent<NavigationProps> = ({
/>
</li>
)}
{spec.hasComponents() && spec.components().hasSchemas() && (
{spec.hasComponents() && spec.components()?.hasSchemas() && (
<li className="mb-4">
<SchemasNavigation
spec={spec}
Expand Down
3 changes: 2 additions & 1 deletion src/components/Template/HTMLWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { useState, useEffect } from 'react';
import { AsyncAPIDocument } from '@asyncapi/parser';
import { AsyncApiComponentWP } from '@asyncapi/react-component';

import { NavigationService, SpecificationService } from '../../services';
import state from '../../state';

import type { OldAsyncAPIDocument as AsyncAPIDocument } from '@asyncapi/parser/esm';

interface HTMLWrapperProps {}

export const HTMLWrapper: React.FunctionComponent<HTMLWrapperProps> = () => {
Expand Down
219 changes: 194 additions & 25 deletions src/components/Terminal/ProblemsTab.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,231 @@
import React from 'react';
import React, { useCallback, useRef, useState } from 'react';
import { VscError, VscWarning, VscInfo, VscLightbulb, VscSearch, VscClose } from 'react-icons/vsc';

import { NavigationService } from '../../services';
import { debounce } from '../../helpers';
import state from '../../state';

import type { Diagnostic } from '@asyncapi/parser/esm';

interface ProblemsTabProps {}

export const ProblemsTab: React.FunctionComponent<ProblemsTabProps> = () => {
const parserState = state.useParserState();
const errors = parserState.errors.get();
const diagnostics = parserState.diagnostics.get();

const errorClassName = errors.length ? 'bg-red-500' : 'bg-gray-400';
const errorDiagnostics = diagnostics.filter(diagnostic => diagnostic.severity === 0);
const warningDiagnostics = diagnostics.filter(diagnostic => diagnostic.severity === 1);
const infoDiagnostics = diagnostics.filter(diagnostic => diagnostic.severity === 2);
const hintDiagnostics = diagnostics.filter(diagnostic => diagnostic.severity === 3);

return (
<div>
<span className="text-xs">Problems</span>
<span className={`inline-block rounded-full px-1.5 py-0.5 ml-1.5 -mt-2 text-xs ${errorClassName}`}>
{errors.length || 0}
</span>
<div className='flex flex-row items-center'>
<span className="text-xs">Diagnostics</span>
<ul className='flex flex-row items-center'>
{errorDiagnostics.length > 0 && (
<li>
<span className='inline-block rounded-full px-1.5 ml-1.5 text-xs bg-red-500'>
{errorDiagnostics.length}
</span>
</li>
)}
{warningDiagnostics.length > 0 && (
<li>
<span className='inline-block rounded-full px-1.5 ml-1 text-xs bg-yellow-500'>
{warningDiagnostics.length}
</span>
</li>
)}
{infoDiagnostics.length > 0 && (
<li>
<span className='inline-block rounded-full px-1.5 ml-1 text-xs bg-blue-500'>
{infoDiagnostics.length}
</span>
</li>
)}
{hintDiagnostics.length > 0 && (
<li>
<span className='inline-block rounded-full px-1.5 ml-1 text-xs bg-blue-500'>
{hintDiagnostics.length}
</span>
</li>
)}
</ul>
</div>
);
};

interface SeverityIconProps {
severity: Diagnostic['severity']
}

const SeverityIcon: React.FunctionComponent<SeverityIconProps> = ({ severity }) => {
switch (severity) {
case 1: return (
<div className='flex flex-row items-center justify-center'>
<VscWarning className='text-yellow-500 w-4 h-4' />
</div>
);
case 2: return (
<div className='flex flex-row items-center justify-center'>
<VscInfo className='text-blue-500 w-4 h-4' />
</div>
);
case 3: return (
<div className='flex flex-row items-center justify-center'>
<VscLightbulb className='text-green-500 w-4 h-4' />
</div>
);
default: return (
<div className='flex flex-row items-center justify-center'>
<VscError className='text-red-500 w-4 h-4' />
</div>
);
}
};

type ActiveSeverity = 0 | 1 | 2 | 3 | 'all';

interface SeverityButtonsProps {
diagnostics: Diagnostic[];
active: ActiveSeverity;
setActive: React.Dispatch<React.SetStateAction<ActiveSeverity>>;
}

const SeverityButtons: React.FunctionComponent<SeverityButtonsProps> = ({ diagnostics, active, setActive: defaultSetActive }) => {
const errorDiagnostics = diagnostics.filter(diagnostic => diagnostic.severity === 0);
const warningDiagnostics = diagnostics.filter(diagnostic => diagnostic.severity === 1);
const infoDiagnostics = diagnostics.filter(diagnostic => diagnostic.severity === 2);
const hintDiagnostics = diagnostics.filter(diagnostic => diagnostic.severity === 3);

const activeBg = 'bg-gray-900';
const notActiveBg = 'bg-gray-700';

const setActive = useCallback((type: ActiveSeverity) => {
defaultSetActive(type === active ? 'all' : type);
}, [active]);

return (
<ul className='flex flex-row items-center'>
<button
type="button"
className={`disabled:cursor-not-allowed w-full inline-flex justify-center rounded-l-md border border-transparent shadow-xs px-2 py-1 ${active === 0 ? activeBg : notActiveBg} text-xs font-medium text-white hover:bg-gray-900 disabled:bg-gray-700 focus:outline-none focus:ring-1 focus:ring-offset-1 focus:ring-gray-700`}
onClick={() => setActive(0)}
disabled={errorDiagnostics.length === 0}
>
<div className='flex flex-row items-center justify-center'>
<SeverityIcon severity={0} />
<span className='ml-1'>{errorDiagnostics.length}</span>
</div>
</button>
<button
type="button"
className={`disabled:cursor-not-allowed w-full inline-flex justify-center border border-transparent shadow-xs px-2 py-1 ml-px ${active === 1 ? activeBg : notActiveBg} text-xs font-medium text-white hover:bg-gray-900 disabled:bg-gray-700 focus:outline-none focus:ring-1 focus:ring-offset-1 focus:ring-gray-700`}
onClick={() => setActive(1)}
disabled={warningDiagnostics.length === 0}
>
<div className='flex flex-row items-center justify-center'>
<SeverityIcon severity={1} />
<span className='ml-1'>{warningDiagnostics.length}</span>
</div>
</button>
<button
type="button"
className={`disabled:cursor-not-allowed w-full inline-flex justify-center border border-transparent shadow-xs px-2 py-1 ml-px ${active === 2 ? activeBg : notActiveBg} text-xs font-medium text-white hover:bg-gray-900 disabled:bg-gray-700 focus:outline-none focus:ring-1 focus:ring-offset-1 focus:ring-gray-700`}
onClick={() => setActive(2)}
disabled={infoDiagnostics.length === 0}
>
<div className='flex flex-row items-center justify-center'>
<SeverityIcon severity={2} />
<span className='ml-1'>{infoDiagnostics.length}</span>
</div>
</button>
<button
type="button"
className={`disabled:cursor-not-allowed w-full inline-flex justify-center rounded-r-md border border-transparent shadow-xs px-2 py-1 ml-px ${active === 3 ? activeBg : notActiveBg} text-xs font-medium text-white hover:bg-gray-900 disabled:bg-gray-700 focus:outline-none focus:ring-1 focus:ring-offset-1 focus:ring-gray-700`}
onClick={() => setActive(3)}
disabled={hintDiagnostics.length === 0}
>
<div className='flex flex-row items-center justify-center'>
<SeverityIcon severity={3} />
<span className='ml-1'>{hintDiagnostics.length}</span>
</div>
</button>
</ul>
);
};

export const ProblemsTabContent: React.FunctionComponent<ProblemsTabProps> = () => {
const parserState = state.useParserState();
const errors = parserState.errors.get();
const diagnostics = parserState.diagnostics.get();

const [active, setActive] = useState<ActiveSeverity>('all');
const [search, setSearch] = useState<string>('');
const inputRef = useRef<HTMLInputElement>(null);

return (
<div className="flex-1 text-white text-xs h-full relative overflow-x-hidden overflow-y-auto">
{errors.length ? (
<div className="px-4">
{diagnostics.length ? (
<div className="px-4 pt-2">
<div className='pb-2 flex flex-row items-center'>
<SeverityButtons diagnostics={diagnostics} active={active} setActive={setActive} />
<div className='mx-3 flex-1 flex flex-row items-center justify-center rounded-md border border-transparent shadow-xs px-2 py-1 bg-gray-700 text-xs font-medium'>
<VscSearch />
<input ref={inputRef} className='w-full ml-2 bg-gray-700 border-transparent focus:border-transparent focus:ring-0 focus:outline-none' onChange={debounce((e) => setSearch(e.target.value), 250)} />
<button type='button' className={`hover:bg-gray-900 rounded-sm border border-transparent ${search ? 'opacity-100' : 'opacity-0'}`} onClick={() => {
if (inputRef.current) {
inputRef.current.value = '';
}
setSearch('');
}}>
<VscClose />
</button>
</div>
<a href='https://stoplight.io/open-source/spectral' title='Spectral website' target='_blank' rel="noreferrer" className='text-white hover:text-blue-500'>
<span>
Powered by
</span>
{' '}
<strong>
Spectral
</strong>
</a>
</div>

<table className="border-collapse w-full">
<thead>
<tr>
<th className="p-2 w-8">Line</th>
<th className="p-2 text-left">Title</th>
<th className="p-2 text-left">Details</th>
<th className="px-2 py-1 w-8">Type</th>
<th className="px-2 py-1 w-8">Line</th>
<th className="px-2 py-1 text-left">Message</th>
</tr>
</thead>
<tbody>
{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;
{diagnostics.map((diagnostic, id) => {
const { severity, message, range } = diagnostic;

if (active !== 'all' && severity !== active) {
return null;
}
if (search && !message.includes(search)) {
return null;
}

return (
<tr key={title || id} className="border-t border-gray-700">
<tr key={id} className="border-t border-gray-700">
<td className="px-2 py-1 text-right"><SeverityIcon severity={severity} /></td>
<td
className="p-2 cursor-pointer"
className="px-2 py-1 cursor-pointer"
onClick={() =>
NavigationService.scrollToEditorLine(
location?.startLine || 0,
location?.startColumn,
range.start.line + 1,
range.start.character + 1,
)
}
>
{renderedLine || '-'}
{range.start.line + 1}:{range.start.character + 1}
</td>
<td className="p-2 text-left">{title}</td>
<td className="p-2 text-left">{detail || '-'}</td>
<td className="px-2 py-1 text-left">{message}</td>
</tr>
);
})}
Expand Down
4 changes: 2 additions & 2 deletions src/components/Terminal/TerminalInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,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();
const hasErrorDiagnostics = parserState.hasErrorDiagnostics.get();
const autoSaving = settingsState.editor.autoSaving.get();
const modified = editorState.modified.get();

Expand All @@ -39,7 +39,7 @@ export const TerminalInfo: React.FunctionComponent<TerminalInfoProps> = () => {
<span>Live server</span>
</div>
)}
{errors.length ? (
{hasErrorDiagnostics ? (
<div className="ml-3">
<span className="text-red-500">
<svg
Expand Down
3 changes: 2 additions & 1 deletion src/components/Visualiser/FlowDiagram.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React, { useState, useEffect } from 'react';
import ReactFlow, { Controls as FlowControls, useStoreActions, useStoreState, Background, useZoomPanHelper, BackgroundVariant } from 'react-flow-renderer';
import { AsyncAPIDocument } from '@asyncapi/parser';

import { Controls } from './Controls';
import nodeTypes from './Nodes';
import { getElementsFromAsyncAPISpec } from './utils/node-factory';
import { calculateNodesForDynamicLayout } from './utils/node-calculator';

import type { OldAsyncAPIDocument as AsyncAPIDocument } from '@asyncapi/parser/esm';

interface FlowDiagramProps {
parsedSpec: AsyncAPIDocument;
}
Expand Down
Loading

0 comments on commit 4fb40bf

Please sign in to comment.