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
23 changes: 16 additions & 7 deletions javascript/packages/linter/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ export class CLI {
}
}

protected adjustPattern(pattern: string | undefined, configGlobPattern: string): string {
protected adjustPattern(pattern: string | undefined, configGlobPatterns: string[]): string {
if (!pattern) {
return configGlobPattern
return configGlobPatterns.length === 1 ? configGlobPatterns[0] : `{${configGlobPatterns.join(',')}}`
}

const resolvedPattern = resolve(pattern)
Expand All @@ -77,7 +77,15 @@ export class CLI {
const stats = statSync(resolvedPattern)

if (stats.isDirectory()) {
return configGlobPattern
const relativeDir = relative(this.projectPath, resolvedPattern)

if (relativeDir) {
const scopedPatterns = configGlobPatterns.map(pattern => `${relativeDir}/${pattern}`)

return scopedPatterns.length === 1 ? scopedPatterns[0] : `{${scopedPatterns.join(',')}}`
}

return configGlobPatterns.length === 1 ? configGlobPatterns[0] : `{${configGlobPatterns.join(',')}}`
} else if (stats.isFile()) {
return relative(this.projectPath, resolvedPattern)
}
Expand All @@ -96,10 +104,11 @@ export class CLI {
}

const filesConfig = config.getFilesConfigForTool('linter')
const configGlobPattern = filesConfig.include && filesConfig.include.length > 0
? (filesConfig.include.length === 1 ? filesConfig.include[0] : `{${filesConfig.include.join(',')}}`)
: '**/*.html.erb'
const adjustedPattern = this.adjustPattern(pattern, configGlobPattern)
const configGlobPatterns = filesConfig.include && filesConfig.include.length > 0
? filesConfig.include
: ['**/*.html.erb']

const adjustedPattern = this.adjustPattern(pattern, configGlobPatterns)

let files = await glob(adjustedPattern, {
cwd: this.projectPath,
Expand Down
4 changes: 2 additions & 2 deletions javascript/packages/linter/src/custom-rule-loader.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { pathToFileURL } from "url"
import { glob } from "glob"
import { glob } from "tinyglobby"

import type { RuleClass } from "./types.js"

Expand Down Expand Up @@ -52,7 +52,7 @@ export class CustomRuleLoader {
const files = await glob(pattern, {
cwd: this.baseDir,
absolute: true,
nodir: true
onlyFiles: true
})

allFiles.push(...files)
Expand Down
84 changes: 84 additions & 0 deletions javascript/packages/linter/test/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,90 @@ describe("CLI Output Formatting", () => {
})
})

describe("Directory Scoping (issue #1045)", () => {
const { mkdirSync, writeFileSync, rmSync, existsSync } = require("fs")
const { join } = require("path")
const tempDir = "test/fixtures/directory-scoping-test"

function runLinterFromPath(filePath: string): { output: string, exitCode: number } {
try {
const { execSync } = require("child_process")

const output = execSync(`bin/herb-lint ${filePath} --no-timing 2>&1`, {
encoding: "utf-8",
env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: undefined, GITHUB_ACTIONS: undefined }
})

return { output: output.trim(), exitCode: 0 }
} catch (error: any) {
const stderr = error.stderr ? error.stderr.toString().trim() : ""
const stdout = error.stdout ? error.stdout.toString().trim() : ""
const combined = (stdout + "\n" + stderr).trim()

return { output: combined || stderr || stdout, exitCode: error.status }
}
}

test("only processes files within the specified directory", () => {
try {
// Create directory structure:
// tempDir/
// .herb.yml
// public/
// file.html.erb (should NOT be processed)
// app/
// views/
// file.html.erb (should be processed)
mkdirSync(join(tempDir, "public"), { recursive: true })
mkdirSync(join(tempDir, "app/views"), { recursive: true })

writeFileSync(join(tempDir, ".herb.yml"), dedent`
version: 0.8.7
linter:
enabled: true
`)

writeFileSync(join(tempDir, "public/file.html.erb"), `<img src="test.png" alt="test">\n`)
writeFileSync(join(tempDir, "app/views/file.html.erb"), `<div>clean file</div>\n`)

const { output, exitCode } = runLinterFromPath(join(tempDir, "app/views"))

expect(output).toContain("Checked 1 file")
expect(output).not.toContain("public/file.html.erb")
expect(exitCode).toBe(0)
} finally {
if (existsSync(tempDir)) {
rmSync(tempDir, { recursive: true, force: true })
}
}
})

test("processes all files when run from project root", () => {
try {
mkdirSync(join(tempDir, "public"), { recursive: true })
mkdirSync(join(tempDir, "app/views"), { recursive: true })

writeFileSync(join(tempDir, ".herb.yml"), dedent`
version: 0.8.7
linter:
enabled: true
`)

writeFileSync(join(tempDir, "public/file.html.erb"), `<div>public file</div>\n`)
writeFileSync(join(tempDir, "app/views/file.html.erb"), `<div>views file</div>\n`)

const { output, exitCode } = runLinterFromPath(tempDir)

expect(output).toContain("Checked 2 files")
expect(exitCode).toBe(0)
} finally {
if (existsSync(tempDir)) {
rmSync(tempDir, { recursive: true, force: true })
}
}
})
})

describe("Custom Rules from Project Root (issue #908)", () => {
const { mkdirSync, writeFileSync, rmSync, existsSync } = require("fs")
const { join } = require("path")
Expand Down
Loading