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
44 changes: 44 additions & 0 deletions src/signals/path-matchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,37 @@
"build.gradle.kts",
"pom.xml",
]);

Check notice on line 57 in src/signals/path-matchers.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.

Check notice on line 57 in src/signals/path-matchers.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Open PR queue is busy

This repo has a busy open PR queue in the local Gittensory cache.
const DOCS_EXTENSIONS: ReadonlySet<string> = new Set(["md", "mdx", "markdown", "rst", "adoc", "asciidoc"]);

// Exact basenames (lowercased) that are unambiguously build/CI config files regardless of directory.
const CONFIG_FILE_NAMES: ReadonlySet<string> = new Set([
"dockerfile",
"makefile",
".editorconfig",
".nvmrc",
".npmrc",
".browserslistrc",
]);

// Filename prefixes that identify build, lint, test-runner, and environment config files.
const CONFIG_FILE_PREFIXES: readonly string[] = [
"tsconfig",
"jsconfig",
"jest.config",
"vitest.config",
"vite.config",
"webpack.config",
"rollup.config",
"postcss.config",
"tailwind.config",
"next.config",
".env",
".eslint",
".prettier",
".babel",
];

/** Machine-generated output (codegen, protobuf, source maps, typegen). */
export function isGeneratedFile(path: string): boolean {
const norm = normalize(path);
Expand Down Expand Up @@ -97,6 +125,20 @@
return DEPENDENCY_MANIFEST_NAMES.has(basename(path));
}

/**
* Build, lint, test-runner, and environment configuration files. Distinct from dependency manifests
* (which declare external dependencies) and source code. Config-only diffs are lower-effort than
* genuine source changes, so slop signals can weight them differently (#561).
*/
export function isConfigFile(path: string): boolean {
const base = basename(path);
if (CONFIG_FILE_NAMES.has(base)) return true;
if (CONFIG_FILE_PREFIXES.some((prefix) => base.startsWith(prefix))) return true;
if (/\.(config|rc)\.[a-z0-9]+$/i.test(base)) return true;
// `.stylelintrc`-style: dot-prefixed name with no extension after "rc"; `custom.rc`: dotted rc extension.
return base.endsWith(".rc") || /^\.[^.]+rc$/i.test(base);
}

/**
* Files that masquerade as substantive source/work but are machine-produced or imported — the set a
* padded diff inflates its size with. Lockfiles, dependency manifests, and docs are legitimate change
Expand All @@ -112,6 +154,7 @@
| "vendored"
| "lockfile"
| "dependency_manifest"
| "config"
| "test"
| "docs"
| "source"
Expand All @@ -128,6 +171,7 @@
if (isVendoredFile(path)) return "vendored";
if (isLockfile(path)) return "lockfile";
if (isDependencyManifestFile(path)) return "dependency_manifest";
if (isConfigFile(path)) return "config";
if (isTestFile(path) || isTestPath(path)) return "test";
if (isDocsFile(path)) return "docs";
if (isCodeFile(path)) return "source";
Expand Down
38 changes: 37 additions & 1 deletion test/unit/path-matchers.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { describe, expect, it } from "vitest";
import {

Check notice on line 2 in test/unit/path-matchers.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.

Check notice on line 2 in test/unit/path-matchers.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Open PR queue is busy

This repo has a busy open PR queue in the local Gittensory cache.
classifyChangedFile,
isDependencyManifestFile,
isConfigFile,
isDocsFile,
isGeneratedFile,
isLockfile,
Expand Down Expand Up @@ -140,6 +141,38 @@
});
});

describe("isConfigFile", () => {
it("matches config files by exact basename (case-insensitive)", () => {
for (const path of ["Dockerfile", "frontend/Makefile", ".editorconfig", "ci/.nvmrc", ".npmrc"]) {
expect(isConfigFile(path)).toBe(true);
}
});

it("matches config files by known filename prefix", () => {
for (const path of ["tsconfig.build.json", "vitest.config.ts", ".env.local", ".eslintrc.json", ".prettierrc.js"]) {
expect(isConfigFile(path)).toBe(true);
}
});

it("matches config files by the .config.ext or .rc.ext pattern", () => {
for (const path of ["babel.config.cjs", "stylelint.config.mjs", "lint-staged.rc.js"]) {
expect(isConfigFile(path)).toBe(true);
}
});

it("matches bare .rc suffix config files", () => {
for (const path of [".stylelintrc", ".huskyrc", "config/custom.rc"]) {
expect(isConfigFile(path)).toBe(true);
}
});

it("does not classify source, test, doc, or lockfiles as config", () => {
for (const path of ["src/app.ts", "README.md", "package.json", "package-lock.json", "test/unit/app.test.ts"]) {
expect(isConfigFile(path)).toBe(false);
}
});
});

describe("classifyChangedFile", () => {
it("classifies each representative path into its category", () => {
const cases: Array<[string, ReturnType<typeof classifyChangedFile>]> = [
Expand All @@ -148,6 +181,8 @@
["vendor/lib.go", "vendored"],
["package-lock.json", "lockfile"],
["package.json", "dependency_manifest"],
["tsconfig.json", "config"],
["vitest.config.ts", "config"],
["test/unit/app.test.ts", "test"],
["README.md", "docs"],
["src/app.ts", "source"],
Expand All @@ -158,9 +193,10 @@
}
});

it("prioritizes padding categories over test/source so they are never counted as effort", () => {
it("prioritizes padding categories over config/test/source so they are never counted as effort", () => {
expect(classifyChangedFile("__generated__/schema.test.ts")).toBe("generated");
expect(classifyChangedFile("vendor/pkg/index.test.js")).toBe("vendored");
expect(classifyChangedFile("dist/bundle.min.js")).toBe("minified");
expect(classifyChangedFile("vendor/tsconfig.json")).toBe("vendored");
});
});
Loading