diff options
| author | Fuwn <[email protected]> | 2026-02-03 21:07:31 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-03 21:07:31 -0800 |
| commit | b4f59a4a196b4c987adafdf4d7380025b6446b8a (patch) | |
| tree | ee8d2805e259c814b6590b03b8a1d75d5e71d41c /packages | |
| parent | feat(web): Replace NextAuth with Supabase Auth (diff) | |
| download | archived-imemio-b4f59a4a196b4c987adafdf4d7380025b6446b8a.tar.xz archived-imemio-b4f59a4a196b4c987adafdf4d7380025b6446b8a.zip | |
fix(iku): Add TypeAliasDeclaration detection and fix block scope logic
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/iku/src/checker.test.ts | 37 | ||||
| -rw-r--r-- | packages/iku/src/checker.ts | 132 | ||||
| -rw-r--r-- | packages/iku/src/cli.ts | 5 | ||||
| -rw-r--r-- | packages/iku/src/index.ts | 7 |
4 files changed, 141 insertions, 40 deletions
diff --git a/packages/iku/src/checker.test.ts b/packages/iku/src/checker.test.ts index 457d956..3116eb6 100644 --- a/packages/iku/src/checker.test.ts +++ b/packages/iku/src/checker.test.ts @@ -16,8 +16,7 @@ function createTempFile(content: string): string { function cleanupTempFile(filePath: string): void { try { unlinkSync(filePath); - } catch { - } + } catch {} } describe("checkFile", () => { @@ -68,7 +67,9 @@ const b = 2; cleanupTempFile(filePath); expect(errors).toHaveLength(1); - expect(errors[0]).toContain("Unnecessary blank line between same statement types"); + expect(errors[0]).toContain( + "Unnecessary blank line between same statement types", + ); }); it("passes for consecutive expression statements without blank lines", () => { const content = `console.log("a"); @@ -105,7 +106,9 @@ console.log(a); cleanupTempFile(filePath); expect(errors).toHaveLength(1); - expect(errors[0]).toContain("Missing blank line between different statement types"); + expect(errors[0]).toContain( + "Missing blank line between different statement types", + ); }); it("requires blank line between variable declaration and return", () => { const content = `function test() { @@ -133,7 +136,7 @@ console.log(a); cleanupTempFile(filePath); expect(errors).toHaveLength(1); - expect(errors[0]).toContain("expression -> if"); + expect(errors[0]).toContain("Missing blank line around scoped block"); }); }); describe("scoped blocks", () => { @@ -171,7 +174,7 @@ console.log(a); cleanupTempFile(filePath); expect(errors).toHaveLength(1); - expect(errors[0]).toContain("Missing blank line between scoped blocks of same type"); + expect(errors[0]).toContain("Missing blank line around scoped block"); }); it("passes when scoped for loops have blank lines between them", () => { const content = `function test(items: number[]) { @@ -205,7 +208,7 @@ console.log(a); cleanupTempFile(filePath); expect(errors).toHaveLength(1); - expect(errors[0]).toContain("Missing blank line between scoped blocks of same type"); + expect(errors[0]).toContain("Missing blank line around scoped block"); }); }); describe("nested blocks", () => { @@ -290,7 +293,9 @@ const b = 2; const errors = checkFile(filePath); cleanupTempFile(filePath); - expect(errors.some((error) => error.includes("Comment detected"))).toBe(true); + expect(errors.some((error) => error.includes("Comment detected"))).toBe( + true, + ); }); it("detects multi-line comments by default", () => { const content = `const a = 1; @@ -302,7 +307,9 @@ const b = 2; const errors = checkFile(filePath); cleanupTempFile(filePath); - expect(errors.some((error) => error.includes("Comment detected"))).toBe(true); + expect(errors.some((error) => error.includes("Comment detected"))).toBe( + true, + ); }); it("detects trailing comments by default", () => { const content = `const a = 1; // trailing comment @@ -311,7 +318,9 @@ const b = 2; const errors = checkFile(filePath); cleanupTempFile(filePath); - expect(errors.some((error) => error.includes("Comment detected"))).toBe(true); + expect(errors.some((error) => error.includes("Comment detected"))).toBe( + true, + ); }); it("allows comments when noComments is false", () => { const content = `const a = 1; @@ -322,7 +331,9 @@ const b = 2; const errors = checkFile(filePath, { noComments: false }); cleanupTempFile(filePath); - expect(errors.some((error) => error.includes("Comment detected"))).toBe(false); + expect(errors.some((error) => error.includes("Comment detected"))).toBe( + false, + ); }); it("detects JSDoc comments by default", () => { const content = `/** @@ -334,7 +345,9 @@ function test() {} const errors = checkFile(filePath); cleanupTempFile(filePath); - expect(errors.some((error) => error.includes("Comment detected"))).toBe(true); + expect(errors.some((error) => error.includes("Comment detected"))).toBe( + true, + ); }); }); }); diff --git a/packages/iku/src/checker.ts b/packages/iku/src/checker.ts index a69a232..f3e4a65 100644 --- a/packages/iku/src/checker.ts +++ b/packages/iku/src/checker.ts @@ -13,6 +13,7 @@ const defaultOptions: CheckerOptions = { type StatementCategory = | "import" | "variableDeclaration" + | "typeAlias" | "if" | "for" | "while" @@ -37,11 +38,19 @@ function getStatementCategory(node: typescript.Statement): StatementCategory { return "variableDeclaration"; } + if (typescript.isTypeAliasDeclaration(node)) { + return "typeAlias"; + } + if (typescript.isIfStatement(node)) { return "if"; } - if (typescript.isForStatement(node) || typescript.isForInStatement(node) || typescript.isForOfStatement(node)) { + if ( + typescript.isForStatement(node) || + typescript.isForInStatement(node) || + typescript.isForOfStatement(node) + ) { return "for"; } @@ -69,7 +78,11 @@ function statementHasBlock(node: typescript.Statement): boolean { return typescript.isBlock(node.thenStatement); } - if (typescript.isForStatement(node) || typescript.isForInStatement(node) || typescript.isForOfStatement(node)) { + if ( + typescript.isForStatement(node) || + typescript.isForInStatement(node) || + typescript.isForOfStatement(node) + ) { return typescript.isBlock(node.statement); } @@ -81,6 +94,10 @@ function statementHasBlock(node: typescript.Statement): boolean { return typescript.isBlock(node.statement); } + if (typescript.isTypeAliasDeclaration(node)) { + return typescript.isTypeLiteralNode(node.type); + } + return false; } @@ -94,8 +111,10 @@ function getStatementsFromBlock( const category = getStatementCategory(statement); const startPosition = statement.getStart(sourceFile); const endPosition = statement.getEnd(); - const startLine = sourceFile.getLineAndCharacterOfPosition(startPosition).line + 1; - const endLine = sourceFile.getLineAndCharacterOfPosition(endPosition).line + 1; + const startLine = + sourceFile.getLineAndCharacterOfPosition(startPosition).line + 1; + const endLine = + sourceFile.getLineAndCharacterOfPosition(endPosition).line + 1; const hasBlock = statementHasBlock(statement); statements.push({ category, startLine, endLine, hasBlock }); @@ -113,7 +132,11 @@ function countBlankLinesBetween( const lines = text.split("\n"); let blankCount = 0; - for (let lineIndex = previousEndLine; lineIndex < currentStartLine - 1; lineIndex++) { + for ( + let lineIndex = previousEndLine; + lineIndex < currentStartLine - 1; + lineIndex++ + ) { const line = lines[lineIndex]; if (line !== undefined && line.trim() === "") { @@ -132,13 +155,22 @@ function hasCommentBetween( const text = sourceFile.getFullText(); const lines = text.split("\n"); - for (let lineIndex = previousEndLine; lineIndex < currentStartLine - 1; lineIndex++) { + for ( + let lineIndex = previousEndLine; + lineIndex < currentStartLine - 1; + lineIndex++ + ) { const line = lines[lineIndex]; if (line !== undefined) { const trimmed = line.trim(); - if (trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*") || trimmed.endsWith("*/")) { + if ( + trimmed.startsWith("//") || + trimmed.startsWith("/*") || + trimmed.startsWith("*") || + trimmed.endsWith("*/") + ) { return true; } } @@ -167,33 +199,50 @@ function checkStatementSpacing( continue; } - const blankLines = countBlankLinesBetween(previous.endLine, current.startLine, sourceFile); + const blankLines = countBlankLinesBetween( + previous.endLine, + current.startLine, + sourceFile, + ); const sameCategory = previous.category === current.category; - const hasComment = hasCommentBetween(previous.endLine, current.startLine, sourceFile); + const hasComment = hasCommentBetween( + previous.endLine, + current.startLine, + sourceFile, + ); if (previous.category === "import" && current.category === "import") { if (blankLines > 0 && !(allowComments && hasComment)) { - errors.push(`${filePath}:${current.startLine}: Blank line between imports`); + errors.push( + `${filePath}:${current.startLine}: Blank line between imports`, + ); } continue; } - const bothHaveBlocks = previous.hasBlock && current.hasBlock; + const eitherHasBlock = previous.hasBlock || current.hasBlock; - if (sameCategory && !bothHaveBlocks && blankLines > 0 && !(allowComments && hasComment)) { + if (eitherHasBlock && blankLines === 0) { errors.push( - `${filePath}:${current.startLine}: Unnecessary blank line between same statement types (${current.category})`, + `${filePath}:${current.startLine}: Missing blank line around scoped block`, ); + + continue; } - if (sameCategory && bothHaveBlocks && blankLines === 0) { + if ( + sameCategory && + !eitherHasBlock && + blankLines > 0 && + !(allowComments && hasComment) + ) { errors.push( - `${filePath}:${current.startLine}: Missing blank line between scoped blocks of same type (${current.category})`, + `${filePath}:${current.startLine}: Unnecessary blank line between same statement types (${current.category})`, ); } - if (!sameCategory && blankLines === 0 && !hasComment) { + if (!sameCategory && !eitherHasBlock && blankLines === 0 && !hasComment) { errors.push( `${filePath}:${current.startLine}: Missing blank line between different statement types (${previous.category} -> ${current.category})`, ); @@ -212,7 +261,12 @@ function visitBlocks( ): void { if (typescript.isBlock(node)) { const statements = getStatementsFromBlock(node, sourceFile); - const blockErrors = checkStatementSpacing(statements, sourceFile, filePath, allowComments); + const blockErrors = checkStatementSpacing( + statements, + sourceFile, + filePath, + allowComments, + ); errors.push(...blockErrors); } @@ -222,17 +276,27 @@ function visitBlocks( }); } -function checkForComments(sourceFile: typescript.SourceFile, filePath: string): string[] { +function checkForComments( + sourceFile: typescript.SourceFile, + filePath: string, +): string[] { const errors: string[] = []; const text = sourceFile.getFullText(); function visit(node: typescript.Node): void { - const leadingComments = typescript.getLeadingCommentRanges(text, node.getFullStart()); - const trailingComments = typescript.getTrailingCommentRanges(text, node.getEnd()); + const leadingComments = typescript.getLeadingCommentRanges( + text, + node.getFullStart(), + ); + const trailingComments = typescript.getTrailingCommentRanges( + text, + node.getEnd(), + ); if (leadingComments) { for (const comment of leadingComments) { - const line = sourceFile.getLineAndCharacterOfPosition(comment.pos).line + 1; + const line = + sourceFile.getLineAndCharacterOfPosition(comment.pos).line + 1; errors.push(`${filePath}:${line}: Comment detected`); } @@ -240,7 +304,8 @@ function checkForComments(sourceFile: typescript.SourceFile, filePath: string): if (trailingComments) { for (const comment of trailingComments) { - const line = sourceFile.getLineAndCharacterOfPosition(comment.pos).line + 1; + const line = + sourceFile.getLineAndCharacterOfPosition(comment.pos).line + 1; errors.push(`${filePath}:${line}: Comment detected`); } @@ -256,7 +321,10 @@ function checkForComments(sourceFile: typescript.SourceFile, filePath: string): return uniqueErrors; } -export function checkFile(filePath: string, options: CheckerOptions = defaultOptions): string[] { +export function checkFile( + filePath: string, + options: CheckerOptions = defaultOptions, +): string[] { const program = typescript.createProgram([filePath], { target: typescript.ScriptTarget.ESNext, module: typescript.ModuleKind.ESNext, @@ -274,7 +342,12 @@ export function checkFile(filePath: string, options: CheckerOptions = defaultOpt const allowComments = !options.noComments; const errors: string[] = []; const topLevelStatements = getStatementsFromBlock(sourceFile, sourceFile); - const topLevelErrors = checkStatementSpacing(topLevelStatements, sourceFile, filePath, allowComments); + const topLevelErrors = checkStatementSpacing( + topLevelStatements, + sourceFile, + filePath, + allowComments, + ); errors.push(...topLevelErrors); visitBlocks(sourceFile, sourceFile, filePath, errors, allowComments); @@ -296,10 +369,17 @@ export function walkDirectory(directory: string): string[] { const stat = statSync(fullPath); if (stat.isDirectory()) { - if (!entry.includes("node_modules") && !entry.includes("dist") && !entry.includes(".next")) { + if ( + !entry.includes("node_modules") && + !entry.includes("dist") && + !entry.includes(".next") + ) { files.push(...walkDirectory(fullPath)); } - } else if ([".ts", ".tsx"].includes(extname(entry)) && !entry.endsWith(".d.ts")) { + } else if ( + [".ts", ".tsx"].includes(extname(entry)) && + !entry.endsWith(".d.ts") + ) { files.push(fullPath); } } diff --git a/packages/iku/src/cli.ts b/packages/iku/src/cli.ts index 8ea07a4..aea9350 100644 --- a/packages/iku/src/cli.ts +++ b/packages/iku/src/cli.ts @@ -21,7 +21,10 @@ function findMonorepoRoot(startDirectory: string): string | null { return null; } -function parseArguments(arguments_: string[]): { directory?: string; options: CheckerOptions } { +function parseArguments(arguments_: string[]): { + directory?: string; + options: CheckerOptions; +} { const options: CheckerOptions = { noComments: true, }; diff --git a/packages/iku/src/index.ts b/packages/iku/src/index.ts index d3d29ae..d175fb5 100644 --- a/packages/iku/src/index.ts +++ b/packages/iku/src/index.ts @@ -1 +1,6 @@ -export { checkFile, walkDirectory, checkDirectory, type CheckerOptions } from "./checker.js"; +export { + checkFile, + walkDirectory, + checkDirectory, + type CheckerOptions, +} from "./checker.js"; |