Skip to content

Commit

Permalink
feat: add lint for naming design rules (#106)
Browse files Browse the repository at this point in the history
  • Loading branch information
1 parent 86b70f5 commit 3535789
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 0 deletions.
37 changes: 37 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Test & Lint

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]

jobs:
test:
name: Test and Lint
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20'

- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest

- name: Install dependencies
run: bun install

- name: Run Design Rule Lint
run: bun run lint:design

- name: Run Type Check
run: bunx tsc --noEmit

- name: Run Tests
run: bun test
Binary file modified bun.lockb
Binary file not shown.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"typescript": "^5.4.5"
},
"dependencies": {
"@types/glob": "^8.1.0",
"glob": "^11.0.0",
"nanoid": "^5.0.7",
"zod": "^3.23.6"
}
Expand Down
87 changes: 87 additions & 0 deletions scripts/lint_design_rules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import fs from "fs"
import path from "path"
import { glob } from "glob"

// Regex patterns for finding Zod objects and enums
const zodObjectPattern = /z\.object\(\s*{([^}]*)}\s*\)/g
const zodEnumPattern = /z\.enum\(\s*\[(([^[\]]*))]\s*\)/g
const propertyPattern = /\s*(\w+)\s*:/g
const enumValuePattern = /"([^"]+)"|'([^']+)'/g

function isCamelCase(str: string): boolean {
return /^[a-z][a-zA-Z0-9]*$/.test(str) && /[A-Z]/.test(str)
}

function isLowerSnakeCase(str: string): boolean {
return /^[a-z0-9]+(_[a-z0-9]+)*$/.test(str)
}

function checkFile(filePath: string): boolean {
let hasViolations = false
const content = fs.readFileSync(filePath, "utf-8")

// Check z.object() properties for camelCase
let match: RegExpExecArray | null
while ((match = zodObjectPattern.exec(content)) !== null) {
const objectContent = match[1]
if (!objectContent) continue

let propMatch: RegExpExecArray | null
while ((propMatch = propertyPattern.exec(objectContent)) !== null) {
const propertyName = propMatch[1]
if (propertyName && isCamelCase(propertyName)) {
console.error(
`Error: Found camelCase property "${propertyName}" in Zod object in file: ${filePath}`
)
hasViolations = true
}
}
propertyPattern.lastIndex = 0
}

// Reset regex lastIndex
zodObjectPattern.lastIndex = 0

// Check z.enum() values for snake_case
while ((match = zodEnumPattern.exec(content)) !== null) {
const enumContent = match[1]
if (!enumContent) continue

let enumMatch: RegExpExecArray | null
while ((enumMatch = enumValuePattern.exec(enumContent)) !== null) {
const enumValue = enumMatch[1] || enumMatch[2] // Handle both single and double quotes
if (enumValue && !isLowerSnakeCase(enumValue)) {
console.error(
`Error: Found non-snake_case enum value "${enumValue}" in file: ${filePath}`
)
hasViolations = true
}
}
enumValuePattern.lastIndex = 0
}

return hasViolations
}

function main() {
const srcDir = path.join(__dirname, "..", "src")
const files = glob.sync("**/*.ts", { cwd: srcDir })

let hasViolations = false

for (const file of files) {
const fullPath = path.join(srcDir, file)
if (checkFile(fullPath)) {
hasViolations = true
}
}

if (hasViolations) {
console.error("\nLinting failed! Please fix the above violations.")
process.exit(1)
} else {
console.log("Design rules check passed!")
}
}

main()
12 changes: 12 additions & 0 deletions src/test/test_violations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { z } from "zod"

// This file contains intentional violations for testing the lint script
export const test_camel_case = z.object({
myProperty: z.string(), // Should fail: camelCase
another_prop: z.string() // Should pass: snake_case
})

export const test_enum_case = z.enum([
"goodCase", // Should fail: not snake_case
"snake_case_ok" // Should pass: snake_case
])

0 comments on commit 3535789

Please sign in to comment.