Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion protocol/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ export interface PanelState {
isLoading?: boolean;
formatLoading?: boolean;
hasParseErrors?: boolean;
blockedByMetaschema?: boolean;
noFileSelected?: boolean;
}

Expand Down
35 changes: 34 additions & 1 deletion test/vscode/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ suite('Extension Test Suite', () => {
const vscodeJsonDiagnostics = diagnostics.filter(diagnostic =>
diagnostic.source === 'json' || diagnostic.source === 'JSON');

assert.strictEqual(vscodeJsonDiagnostics.length, 0,
assert.strictEqual(vscodeJsonDiagnostics.length, 0,
'VS Code built-in JSON validation should be disabled');

const sourcemetaDiagnostics = diagnostics.filter(diagnostic =>
Expand All @@ -207,4 +207,37 @@ suite('Extension Test Suite', () => {
assert.ok(sourcemetaDiagnostics.length > 0,
'Sourcemeta Studio should still report metaschema errors');
});

test('Should run linter even when metaschema validation fails', async function() {
this.timeout(15000);

const extension = vscode.extensions.getExtension('sourcemeta.sourcemeta-studio');
if (extension && !extension.isActive) {
await extension.activate();
}

const fixtureDir = path.join(__dirname, '..', '..', '..', 'test', 'vscode', 'fixtures');
const schemaPath = path.join(fixtureDir, 'invalid-metaschema.json');

const document = await vscode.workspace.openTextDocument(vscode.Uri.file(schemaPath));
await vscode.window.showTextDocument(document);

await vscode.commands.executeCommand('sourcemeta-studio.openPanel');

await new Promise(resolve => setTimeout(resolve, 5000));

const diagnostics = vscode.languages.getDiagnostics(document.uri);

const metaschemaDiagnostics = diagnostics.filter(diagnostic =>
diagnostic.source === 'Sourcemeta Studio (Metaschema)');

const lintDiagnostics = diagnostics.filter(diagnostic =>
diagnostic.source === 'Sourcemeta Studio (Lint)');

assert.ok(metaschemaDiagnostics.length > 0,
'Should have metaschema errors for invalid schema');

assert.ok(lintDiagnostics.length > 0,
'Should still have lint diagnostics even when metaschema fails');
});
});
41 changes: 4 additions & 37 deletions vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,6 @@ function handleWebviewMessage(message: WebviewToExtensionMessage): void {
return;
}

if (currentPanelState.blockedByMetaschema) {
vscode.window.showErrorMessage('Cannot format schema: Metaschema validation failed. Fix metaschema errors first.');
return;
}

// Send format loading state only, preserve existing lint/metaschema state
panelManager.updateContent({
...currentPanelState,
Expand Down Expand Up @@ -273,44 +268,17 @@ async function updatePanelContent(): Promise<void> {
diagnosticManager.clearDiagnostics(lastActiveTextEditor.document.uri);
}

// Run metaschema first, if metaschema reports errors, block other commands
try {
const version = await commandExecutor.getVersion();
cachedCliVersion = version;

const metaschemaRawResult = await commandExecutor.metaschema(fileInfo.absolutePath);
const metaschemaResult = parseMetaschemaResult(metaschemaRawResult.output, metaschemaRawResult.exitCode);

if (metaschemaResult.errors && metaschemaResult.errors.length > 0) {
const blockedState: PanelState = {
fileInfo,
cliVersion: cachedCliVersion,
extensionVersion,
lintResult: { raw: '', health: null, errors: [] },
formatResult: { output: '', exitCode: null },
metaschemaResult,
isLoading: false,
hasParseErrors: hasJsonParseErrors({ raw: '', health: null }, metaschemaResult),
blockedByMetaschema: true
};
currentPanelState = blockedState;
panelManager.updateContent(blockedState);

if (lastActiveTextEditor) {
diagnosticManager.updateMetaschemaDiagnostics(
lastActiveTextEditor.document.uri,
metaschemaResult.errors
);
}

return;
}

const [lintOutput, formatResult] = await Promise.all([
const [metaschemaRawResult, lintOutput, formatResult] = await Promise.all([
commandExecutor.metaschema(fileInfo.absolutePath),
commandExecutor.lint(fileInfo.absolutePath),
commandExecutor.formatCheck(fileInfo.absolutePath)
]);

const metaschemaResult = parseMetaschemaResult(metaschemaRawResult.output, metaschemaRawResult.exitCode);
const lintResult = parseLintResult(lintOutput);

const parseErrors = hasJsonParseErrors(lintResult, metaschemaResult);
Expand All @@ -323,8 +291,7 @@ async function updatePanelContent(): Promise<void> {
formatResult,
metaschemaResult,
isLoading: false,
hasParseErrors: parseErrors,
blockedByMetaschema: false
hasParseErrors: parseErrors
};
currentPanelState = finalState;
panelManager.updateContent(finalState);
Expand Down
18 changes: 5 additions & 13 deletions webview/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,6 @@ function App() {
};
}, []);

useEffect(() => {
if (state?.blockedByMetaschema) {
setActiveTab('metaschema');
setActiveTabInState('metaschema');
}
}, [state?.blockedByMetaschema]);

const handleTabChange = (tab: TabType) => {
setActiveTab(tab);
setActiveTabInState(tab);
Expand All @@ -61,10 +54,9 @@ function App() {
return (
<div className="flex flex-col h-screen p-5">
<FileInfo fileInfo={state.fileInfo} />
<HealthBar
lintResult={state.lintResult}
isLoading={state.isLoading}
blockedByMetaschema={state.blockedByMetaschema}
<HealthBar
lintResult={state.lintResult}
isLoading={state.isLoading}
noFileSelected={state.noFileSelected}
/>
<Tabs activeTab={activeTab} onTabChange={handleTabChange} state={state} />
Expand All @@ -76,8 +68,8 @@ function App() {
<LoadingSpinner fileInfo={state.fileInfo} />
) : (
<>
{activeTab === 'lint' && <LintTab lintResult={state.lintResult} blocked={!!state.blockedByMetaschema} noFileSelected={state.noFileSelected} />}
{activeTab === 'format' && <FormatTab formatResult={state.formatResult} fileInfo={state.fileInfo} hasParseErrors={state.hasParseErrors} blocked={!!state.blockedByMetaschema} noFileSelected={state.noFileSelected} />}
{activeTab === 'lint' && <LintTab lintResult={state.lintResult} noFileSelected={state.noFileSelected} />}
{activeTab === 'format' && <FormatTab formatResult={state.formatResult} fileInfo={state.fileInfo} hasParseErrors={state.hasParseErrors} noFileSelected={state.noFileSelected} />}
{activeTab === 'metaschema' && <MetaschemaTab metaschemaResult={state.metaschemaResult} noFileSelected={state.noFileSelected} />}
</>
)}
Expand Down
19 changes: 1 addition & 18 deletions webview/src/components/FormatTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ export interface FormatTabProps {
formatResult: CommandResult;
fileInfo: FileInfo | null;
hasParseErrors?: boolean | undefined;
blocked?: boolean | undefined;
noFileSelected?: boolean | undefined;
}

export function FormatTab({ formatResult, fileInfo, hasParseErrors, blocked, noFileSelected }: FormatTabProps) {
export function FormatTab({ formatResult, fileInfo, hasParseErrors, noFileSelected }: FormatTabProps) {
const handleFormatSchema = () => {
formatSchema();
};
Expand Down Expand Up @@ -49,22 +48,6 @@ export function FormatTab({ formatResult, fileInfo, hasParseErrors, blocked, noF
);
}

if (blocked) {
return (
<div className="text-center py-10 px-5">
<div className="flex justify-center mb-4">
<AlertCircle size={48} className="text-(--error)" strokeWidth={1.5} />
</div>
<div className="text-lg font-semibold text-(--vscode-fg) mb-2">
Cannot Format Schema
</div>
<div className="text-[13px] text-(--vscode-muted) max-w-md mx-auto">
Metaschema validation failed. Fix the metaschema errors first before attempting to format.
</div>
</div>
);
}

if (isYaml) {
return (
<div className="text-center py-10 px-5">
Expand Down
13 changes: 5 additions & 8 deletions webview/src/components/HealthBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ import type { LintResult } from '../../../protocol/types';
export interface HealthBarProps {
lintResult: LintResult;
isLoading?: boolean | undefined;
blockedByMetaschema?: boolean | undefined;
noFileSelected?: boolean | undefined;
}

export function HealthBar({ lintResult, isLoading, blockedByMetaschema, noFileSelected }: HealthBarProps) {
export function HealthBar({ lintResult, isLoading, noFileSelected }: HealthBarProps) {
const errorCount = lintResult.errors?.length || 0;

let health: number;

if (noFileSelected || blockedByMetaschema) {
if (noFileSelected) {
health = 0;
} else if (lintResult.health !== null && lintResult.health !== undefined) {
health = lintResult.health;
Expand All @@ -21,22 +20,20 @@ export function HealthBar({ lintResult, isLoading, blockedByMetaschema, noFileSe
} else {
health = 0;
}

const getHealthColor = (health: number): string => {
if (health >= 80) return 'var(--success)';
if (health >= 50) return 'var(--warning)';
return 'var(--error)';
};

const showUnknown = !blockedByMetaschema && !noFileSelected && (isLoading || (lintResult.health === null && lintResult.errors === undefined));
const showUnknown = !noFileSelected && (isLoading || (lintResult.health === null && lintResult.errors === undefined));

return (
<div className="mb-5">
<div className="text-(--vscode-fg) text-xs mb-1.5 font-semibold">
Schema Health: {noFileSelected ? (
<span className="text-(--vscode-muted)">N/A</span>
) : blockedByMetaschema ? (
<span className="text-(--vscode-muted)">N/A</span>
) : showUnknown ? (
<span className="text-(--vscode-muted)">?%</span>
) : (
Expand Down
20 changes: 2 additions & 18 deletions webview/src/components/LintTab.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import type { LintResult, Position } from '../../../protocol/types';
import { goToPosition } from '../message';
import { RawOutput } from './RawOutput';
import { CheckCircle, AlertCircle, FileQuestion } from 'lucide-react';
import { CheckCircle, FileQuestion } from 'lucide-react';

export interface LintTabProps {
lintResult: LintResult;
blocked?: boolean | undefined;
noFileSelected?: boolean | undefined;
}

export function LintTab({ lintResult, blocked, noFileSelected }: LintTabProps) {
export function LintTab({ lintResult, noFileSelected }: LintTabProps) {
const handleGoToPosition = (position: Position) => {
goToPosition(position);
};
Expand All @@ -30,21 +29,6 @@ export function LintTab({ lintResult, blocked, noFileSelected }: LintTabProps) {
);
}

if (blocked) {
return (
<div className="text-center py-10 px-5">
<div className="flex justify-center mb-4">
<AlertCircle size={48} className="text-(--error)" strokeWidth={1.5} />
</div>
<div className="text-lg font-semibold text-(--vscode-fg) mb-2">Cannot Lint Schema</div>
<div className="text-[13px] text-(--vscode-muted) max-w-md mx-auto">
Metaschema validation failed. Fix the metaschema errors first before running lint.
Check the Metaschema tab for more details.
</div>
</div>
);
}

return (
<div>
{errors.length === 0 ? (
Expand Down
5 changes: 2 additions & 3 deletions webview/src/components/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export function Tabs({ activeTab, onTabChange, state }: TabsProps) {
const lintStatus = calculateLintStatus(state.lintResult.errors?.length || 0, state.lintResult.health, state.isLoading, state.noFileSelected);
const formatStatus = calculateFormatStatus(state.formatResult.exitCode, state.formatLoading, state.fileInfo?.isYaml, state.noFileSelected);
const metaschemaStatus = calculateMetaschemaStatus(state.metaschemaResult.exitCode, state.isLoading, state.noFileSelected);
const lintDisabled = !!state.blockedByMetaschema;

const Tab = ({
id,
Expand Down Expand Up @@ -50,8 +49,8 @@ export function Tabs({ activeTab, onTabChange, state }: TabsProps) {

return (
<div className="flex border-b border-(--vscode-border) mb-5">
<Tab id="lint" label="Lint" Icon={lintStatus.Icon} color={lintStatus.color} disabled={lintDisabled} />
<Tab id="format" label="Format" Icon={formatStatus.Icon} color={formatStatus.color} disabled={lintDisabled} />
<Tab id="lint" label="Lint" Icon={lintStatus.Icon} color={lintStatus.color} />
<Tab id="format" label="Format" Icon={formatStatus.Icon} color={formatStatus.color} />
<Tab id="metaschema" label="Metaschema" Icon={metaschemaStatus.Icon} color={metaschemaStatus.color} />
</div>
);
Expand Down