Skip to content

Commit

Permalink
feat(parser): only format on non tree-sitter error (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
gorillamoe authored Feb 25, 2025
1 parent a0c1d41 commit 240a3b9
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 14 deletions.
Binary file modified bun.lockb
Binary file not shown.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mistweaverco/kulala-fmt",
"version": "2.6.2",
"version": "2.7.0",
"type": "module",
"scripts": {
"build": "rollup -c",
Expand All @@ -16,7 +16,7 @@
"access": "public"
},
"dependencies": {
"@mistweaverco/tree-sitter-kulala": "1.8.0",
"@mistweaverco/tree-sitter-kulala": "1.8.1",
"tree-sitter": "0.22.4",
"prettier": "^3.5.2"
},
Expand Down
28 changes: 27 additions & 1 deletion src/lib/parser/DocumentParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,26 @@ const traverseNodes = (
node.children.forEach((child) => traverseNodes(child, callback));
};

const documentHasErrors = (node: SyntaxNode): boolean => {
const recursiveNodeWalk = (
node: SyntaxNode,
callback: (node: SyntaxNode) => boolean,
): boolean => {
if (callback(node)) {
return true; // Return true if the callback returns true
}
for (const child of node.children) {
if (recursiveNodeWalk(child, callback)) {
return true; // Return true if a child node has an error
}
}
return false; // Return false if no error is found in this subtree
};
return recursiveNodeWalk(node, (n) => {
return n.hasError; // Return true if the node has an error
});
};

// formatUrl takes a URL string and retuns an formatted string
// with the URL parts separated by new lines
// For example, given the URL `https://httpbin.org/get?foo=bar&baz=qux`
Expand All @@ -79,14 +99,20 @@ const formatUrl = (url: string): string => {
return `${base}?${query.split("&").join("\n &")}`.replace(/\n\s*\n/g, "\n");
};

const parse = (content: string): Document => {
// Returns a Document object if the content is valid .http syntax,
// otherwise null.
const parse = (content: string): Document | null => {
const parser = new Parser();
const language = Kulala as Language;
parser.setLanguage(language);

const tree = parser.parse(content);
const documentNode = tree.rootNode;

if (documentHasErrors(documentNode)) {
return null;
}

const variables: Variable[] = [];
const blocks: Block[] = [];

Expand Down
43 changes: 32 additions & 11 deletions src/lib/parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,35 @@ const getOpenAPISpecAsJSON = (filepath: string): OpenAPISpec => {
return JSON.parse(fs.readFileSync(filepath, "utf-8")) as OpenAPISpec;
};

const isFileValid = async (
const isAlreadyPretty = async (
filepath: string,
options: { body: boolean },
): Promise<[boolean, string, string]> => {
): Promise<[boolean | null, string, string]> => {
const content = fs.readFileSync(filepath, "utf-8");
const document = DocumentParser.parse(content);
if (!document) {
return [null, content, ""];
}
const build = await DocumentBuilder.build(document, options.body);
return [content === build, content, build];
};

const fixFile = async (
const makeFilePretty = async (
filepath: string,
formatBody: boolean,
): Promise<boolean> => {
): Promise<boolean | null> => {
const content = fs.readFileSync(filepath, "utf-8");
const document = DocumentParser.parse(content);
// if document is null, that means we had an error parsing the file
if (!document) {
return null;
}
const build = await DocumentBuilder.build(document, formatBody);
const isValid = content === build;
if (!isValid) {
const isAlreadyPretty = content === build;
if (!isAlreadyPretty) {
fs.writeFileSync(filepath, build, "utf-8");
}
return !isValid;
return isAlreadyPretty;
};

/**
Expand All @@ -63,18 +70,25 @@ export const check = async (
if (!extensions) {
extensions = [".http", ".rest"];
}
let errorHappened = false;
const files = fileWalker(dirPath, extensions);
for (const file of files) {
const [isValid, content, build] = await isFileValid(file, options);
if (!isValid) {
console.log(chalk.red(`Invalid file: ${file}`));
const [isPretty, content, build] = await isAlreadyPretty(file, options);
if (isPretty === false) {
console.log(chalk.yellow(`File not pretty: ${file}`));
if (options.verbose) {
Diff(build, content);
}
} else if (isPretty === null) {
console.log(chalk.red(`Error parsing file: ${file}`));
errorHappened = true;
} else {
console.log(chalk.green(`Valid file: ${file}`));
}
}
if (errorHappened) {
process.exit(1);
}
};

export const format = async (
Expand All @@ -88,15 +102,22 @@ export const format = async (
if (!extensions) {
extensions = [".http", ".rest"];
}
let errorHappened = false;
const files = fileWalker(dirPath, extensions);
for (const file of files) {
const neededFix = await fixFile(file, options.body);
const neededFix = await makeFilePretty(file, options.body);
if (neededFix) {
console.log(chalk.yellow(`Formatted file: ${file}`));
} else if (neededFix === null) {
console.log(chalk.red(`Error parsing file: ${file}`));
errorHappened = true;
} else {
console.log(chalk.green(`Valid file: ${file}`));
}
}
if (errorHappened) {
process.exit(1);
}
};

const convertFromOpenAPI = async (files: string[]): Promise<void> => {
Expand Down

0 comments on commit 240a3b9

Please sign in to comment.