diff --git a/javascript/packages/linter/src/cli.ts b/javascript/packages/linter/src/cli.ts
index cf01d4237..e8c06f2b6 100644
--- a/javascript/packages/linter/src/cli.ts
+++ b/javascript/packages/linter/src/cli.ts
@@ -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)
@@ -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)
}
@@ -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,
diff --git a/javascript/packages/linter/src/custom-rule-loader.ts b/javascript/packages/linter/src/custom-rule-loader.ts
index 182da84e9..8b2f92ce8 100644
--- a/javascript/packages/linter/src/custom-rule-loader.ts
+++ b/javascript/packages/linter/src/custom-rule-loader.ts
@@ -1,5 +1,5 @@
import { pathToFileURL } from "url"
-import { glob } from "glob"
+import { glob } from "tinyglobby"
import type { RuleClass } from "./types.js"
@@ -52,7 +52,7 @@ export class CustomRuleLoader {
const files = await glob(pattern, {
cwd: this.baseDir,
absolute: true,
- nodir: true
+ onlyFiles: true
})
allFiles.push(...files)
diff --git a/javascript/packages/linter/test/cli.test.ts b/javascript/packages/linter/test/cli.test.ts
index b06e70ccf..91a9c2338 100644
--- a/javascript/packages/linter/test/cli.test.ts
+++ b/javascript/packages/linter/test/cli.test.ts
@@ -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"), `
\n`)
+ writeFileSync(join(tempDir, "app/views/file.html.erb"), `