Skip to content

Commit

Permalink
Merge pull request #117 from flatironinstitute/linting-tests
Browse files Browse the repository at this point in the history
Move linting code to under Stanc, tests
  • Loading branch information
WardBrian authored Jul 1, 2024
2 parents 5961ba6 + d3c67b7 commit 4d983cf
Show file tree
Hide file tree
Showing 4 changed files with 275 additions and 105 deletions.
97 changes: 2 additions & 95 deletions gui/src/app/FileEditor/StanFileEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import {
} from "react";
import StanCompileResultWindow from "./StanCompileResultWindow";
import useStanc from "../Stanc/useStanc";
import TextEditor, { CodeMarker, ToolbarItem } from "./TextEditor";
import TextEditor, { ToolbarItem } from "./TextEditor";
import compileStanProgram from "../compileStanProgram/compileStanProgram";
import { StancErrors } from "../Stanc/Types";
import { stancErrorsToCodeMarkers } from "../Stanc/Linting";

type Props = {
fileName: string;
Expand Down Expand Up @@ -269,97 +269,4 @@ const stringChecksum = (str: string) => {
return hash;
};

const stancErrorsToCodeMarkers = (stancErrors: StancErrors) => {
const codeMarkers: CodeMarker[] = [];

for (const x of stancErrors.errors || []) {
const marker = stancErrorStringToMarker(x, "error");
if (marker) codeMarkers.push(marker);
}
for (const x of stancErrors.warnings || []) {
const marker = stancErrorStringToMarker(x, "warning");
if (marker) codeMarkers.push(marker);
}

return codeMarkers;
};

const stancErrorStringToMarker = (
x: string,
severity: "error" | "warning",
): CodeMarker | undefined => {
if (!x) return undefined;

// Example: Syntax error in 'main.stan', line 1, column 0 to column 1, parsing error:

let lineNumber: number | undefined = undefined;
let startColumn: number | undefined = undefined;
let endColumn: number | undefined = undefined;

const sections = x.split(",").map((x) => x.trim());
for (const section of sections) {
if (section.startsWith("line ") && lineNumber === undefined) {
lineNumber = parseInt(section.slice("line ".length));
} else if (section.startsWith("column ") && startColumn === undefined) {
const cols = section.slice("column ".length).split(" to ");
startColumn = parseInt(cols[0]);
endColumn =
cols.length > 1
? parseInt(cols[1].slice("column ".length))
: startColumn + 1;
}
}

if (
lineNumber !== undefined &&
startColumn !== undefined &&
endColumn !== undefined
) {
return {
startLineNumber: lineNumber,
startColumn: startColumn + 1,
endLineNumber: lineNumber,
endColumn: endColumn + 1,
message:
severity === "warning" ? getWarningMessage(x) : getErrorMessage(x),
severity,
};
} else {
return undefined;
}
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Adapted from https://github.com/WardBrian/vscode-stan-extension
function getWarningMessage(message: string) {
let warning = message.replace(/Warning.*column \d+: /s, "");
warning = warning.replace(/\s+/gs, " ");
warning = warning.trim();
warning = message.includes("included from")
? "Warning in included file:\n" + warning
: warning;
return warning;
}

function getErrorMessage(message: string) {
let error = message;
// cut off code snippet for display
if (message.includes("------\n")) {
error = error.split("------\n")[2];
}
error = error.trim();
error = message.includes("included from")
? "Error in included file:\n" + error
: error;

// only relevant to vscode-stan-extension:
// error = error.includes("given information about")
// ? error +
// "\nConsider updating the includePaths setting of vscode-stan-extension"
// : error;

return error;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

export default StanFileEditor;
11 changes: 1 addition & 10 deletions gui/src/app/FileEditor/TextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,10 @@ import {
} from "react";
import { highlightJsData } from "./stanLang";
import { Hyperlink, SmallIconButton } from "@fi-sci/misc";
import { CodeMarker } from "../Stanc/Linting";

type Monaco = typeof monaco;

// An interface for passing markers (squiggles) to the editor without depending on monaco types
export type CodeMarker = {
startLineNumber: number;
startColumn: number;
endLineNumber: number;
endColumn: number;
message: string;
severity: "error" | "warning" | "hint" | "info";
};

type Props = {
defaultText?: string;
text: string | undefined;
Expand Down
103 changes: 103 additions & 0 deletions gui/src/app/Stanc/Linting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { StancErrors } from "./Types";

type Position = {
startLineNumber: number;
startColumn: number;
endLineNumber: number;
endColumn: number;
};

// An interface for passing markers (squiggles) to the editor without depending on monaco types
export type CodeMarker = Position & {
message: string;
severity: "error" | "warning" | "hint" | "info";
};

export const stancErrorsToCodeMarkers = (stancErrors: StancErrors) => {
const codeMarkers = [
...(stancErrors.errors || []).map((error) =>
stancMessageToMarker(error, "error"),
),
...(stancErrors.warnings || []).map((warning) =>
stancMessageToMarker(warning, "warning"),
),
];

return codeMarkers.filter((marker) => marker !== undefined) as CodeMarker[];
};

const stancMessageToMarker = (
message: string,
severity: "error" | "warning",
): CodeMarker | undefined => {
const position = locationFromMessage(message);
if (position === undefined) return undefined;
const { startLineNumber, startColumn, endLineNumber, endColumn } = position;

return {
startLineNumber,
startColumn,
endLineNumber,
endColumn,
message:
severity === "warning"
? getWarningMessage(message)
: getErrorMessage(message),
severity,
};
};

// Adapted from https://github.com/WardBrian/vscode-stan-extension

const locationFromMessage = (message: string): Position | undefined => {
if (!message) return undefined;
// format is "in 'filename', line (#), column (#) to (line #,)? column (#)"
const start = message.matchAll(/'.*', line (\d+), column (\d+)( to)?/g);
// there will be multiple in the case of #included files
const lastMatch = Array.from(start).pop();
if (!lastMatch) {
return undefined;
}

const startLineNumber = parseInt(lastMatch[1]);
const startColumn = parseInt(lastMatch[2]) + 1;

let endLineNumber = startLineNumber;
let endColumn = startColumn;

if (lastMatch[3]) {
// " to" was matched
const end = message.match(/to (line (\d+), )?column (\d+)/);
if (end) {
if (end[1]) {
endLineNumber = parseInt(end[2]);
}
endColumn = parseInt(end[3]) + 1;
}
}

return { startLineNumber, startColumn, endLineNumber, endColumn };
};

const getWarningMessage = (message: string) => {
let warning = message.replace(/Warning.*column \d+:/s, "");
warning = warning.replace(/\s+/gs, " ");
warning = warning.trim();
return warning;
};

const getErrorMessage = (message: string) => {
let error = message;
// cut off code snippet for display
if (message.includes("------\n")) {
error = error.split("------\n")[2];
}
error = error.trim();
return error;
};

export const exportedForTesting = {
locationFromMessage,
getErrorMessage,
getWarningMessage,
};
Loading

0 comments on commit 4d983cf

Please sign in to comment.