@shivaedev/quality 0.20.0 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/biome/declarations.json +1 -1
- package/biome/preset.json +1 -1
- package/dist/rules/built-in.d.ts +1 -1
- package/dist/rules/built-in.d.ts.map +1 -1
- package/dist/rules/built-in.js +2 -0
- package/dist/rules/built-in.js.map +1 -1
- package/dist/rules/files/distinctCase.d.ts +2 -0
- package/dist/rules/files/distinctCase.d.ts.map +1 -0
- package/dist/rules/files/distinctCase.js +47 -0
- package/dist/rules/files/distinctCase.js.map +1 -0
- package/dist/rules/files/namedAfterExport.d.ts.map +1 -1
- package/dist/rules/files/namedAfterExport.js +9 -1
- package/dist/rules/files/namedAfterExport.js.map +1 -1
- package/docs/modules.md +1 -0
- package/docs/naming.md +5 -1
- package/package.json +2 -2
- package/src/rules/built-in.ts +2 -0
- package/src/rules/files/distinctCase.ts +54 -0
- package/src/rules/files/namedAfterExport.ts +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.21.1 - 2026-10-16
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Treat `.railway/railway.ts` and its `.py` and `.go` siblings as tool config files at any project root: `files/named-after-export` leaves them alone and the shared Biome preset permits their default export. Other files in `.railway/` and `src/railway.ts` remain checked.
|
|
8
|
+
|
|
9
|
+
## 0.21.0 - 2026-10-16
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `files/distinct-case` reports two files or folders in one folder whose names, or the files they build to, differ only by case, such as `ReadingHistory.tsx` and `readingHistory.ts`. On macOS or Windows such a pair is one path, so a checkout or a build fails. A repository that upgrades records its existing findings with `quality baseline write --rule files/distinct-case`.
|
|
14
|
+
|
|
3
15
|
## 0.20.0 - 2026-10-13
|
|
4
16
|
|
|
5
17
|
### Changed
|
package/biome/declarations.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"rule": "lint/correctness/noUnusedFunctionParameters"
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
|
-
"includes": ["**/*.config.*"],
|
|
13
|
+
"includes": ["**/*.config.*", "**/.railway/railway.ts", "**/.railway/railway.py", "**/.railway/railway.go"],
|
|
14
14
|
"reason": "Tools load their config files through the default export.",
|
|
15
15
|
"rule": "lint/style/noDefaultExport"
|
|
16
16
|
},
|
package/biome/preset.json
CHANGED
package/dist/rules/built-in.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export declare const builtInRules: readonly [import("../rule.ts").Rule<"structur
|
|
|
21
21
|
}>, import("../rule.ts").Rule<"files/folder-names", {
|
|
22
22
|
readonly content?: readonly string[];
|
|
23
23
|
readonly toolOwned?: readonly string[];
|
|
24
|
-
}>, import("../rule.ts").Rule<"files/other-names", {
|
|
24
|
+
}>, import("../rule.ts").Rule<"files/distinct-case", undefined>, import("../rule.ts").Rule<"files/other-names", {
|
|
25
25
|
readonly content?: readonly string[];
|
|
26
26
|
readonly toolOwned?: readonly string[];
|
|
27
27
|
}>, import("../rule.ts").Rule<"tests/follow", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"built-in.d.ts","sourceRoot":"","sources":["../../src/rules/built-in.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"built-in.d.ts","sourceRoot":"","sources":["../../src/rules/built-in.ts"],"names":[],"mappings":"AA8BA,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8DA8Bf,CAAC"}
|
package/dist/rules/built-in.js
CHANGED
|
@@ -5,6 +5,7 @@ import { noJsdoc } from "#rules/comments/no-jsdoc.ts";
|
|
|
5
5
|
import { noLineReference } from "#rules/comments/no-line-reference.ts";
|
|
6
6
|
import { noPrReference } from "#rules/comments/no-pr-reference.ts";
|
|
7
7
|
import { noTodo } from "#rules/comments/no-todo.ts";
|
|
8
|
+
import { distinctCase } from "#rules/files/distinctCase.ts";
|
|
8
9
|
import { folderNames } from "#rules/files/folderNames.ts";
|
|
9
10
|
import { namedAfterExport } from "#rules/files/namedAfterExport.ts";
|
|
10
11
|
import { otherNames } from "#rules/files/otherNames.ts";
|
|
@@ -51,6 +52,7 @@ export const builtInRules = [
|
|
|
51
52
|
manifestsSorted,
|
|
52
53
|
namedAfterExport,
|
|
53
54
|
folderNames,
|
|
55
|
+
distinctCase,
|
|
54
56
|
otherNames,
|
|
55
57
|
testsFollow,
|
|
56
58
|
testsColocated,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"built-in.js","sourceRoot":"","sources":["../../src/rules/built-in.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AAC/E,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,6CAA6C,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,MAAM,CAAC,MAAM,YAAY,GAAG;IAC3B,QAAQ;IACR,OAAO;IACP,eAAe;IACf,aAAa;IACb,QAAQ;IACR,MAAM;IACN,mBAAmB;IACnB,UAAU;IACV,QAAQ;IACR,YAAY;IACZ,oBAAoB;IACpB,cAAc;IACd,gBAAgB;IAChB,KAAK;IACL,YAAY;IACZ,YAAY;IACZ,cAAc;IACd,iBAAiB;IACjB,eAAe;IACf,cAAc;IACd,UAAU;IACV,eAAe;IACf,gBAAgB;IAChB,WAAW;IACX,UAAU;IACV,WAAW;IACX,cAAc;IACd,UAAU;CACD,CAAC"}
|
|
1
|
+
{"version":3,"file":"built-in.js","sourceRoot":"","sources":["../../src/rules/built-in.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AAC/E,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,6CAA6C,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,MAAM,CAAC,MAAM,YAAY,GAAG;IAC3B,QAAQ;IACR,OAAO;IACP,eAAe;IACf,aAAa;IACb,QAAQ;IACR,MAAM;IACN,mBAAmB;IACnB,UAAU;IACV,QAAQ;IACR,YAAY;IACZ,oBAAoB;IACpB,cAAc;IACd,gBAAgB;IAChB,KAAK;IACL,YAAY;IACZ,YAAY;IACZ,cAAc;IACd,iBAAiB;IACjB,eAAe;IACf,cAAc;IACd,UAAU;IACV,eAAe;IACf,gBAAgB;IAChB,WAAW;IACX,YAAY;IACZ,UAAU;IACV,WAAW;IACX,cAAc;IACd,UAAU;CACD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"distinctCase.d.ts","sourceRoot":"","sources":["../../../src/rules/files/distinctCase.ts"],"names":[],"mappings":"AAkCA,eAAO,MAAM,YAAY,2DAmBvB,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { posix } from "node:path";
|
|
2
|
+
import { defineRule } from "#rule.ts";
|
|
3
|
+
const CODE = /\.(?<mode>[cm]?)[jt]sx?$/u;
|
|
4
|
+
function sourcesByPath(files) {
|
|
5
|
+
const sources = new Map();
|
|
6
|
+
function add(path, source) {
|
|
7
|
+
const known = sources.get(path) ?? [];
|
|
8
|
+
sources.set(path, known.includes(source) ? known : [...known, source]);
|
|
9
|
+
}
|
|
10
|
+
for (const file of files) {
|
|
11
|
+
add(file.replace(CODE, ".$<mode>js"), file);
|
|
12
|
+
for (let folder = posix.dirname(file); folder !== "."; folder = posix.dirname(folder)) {
|
|
13
|
+
add(folder, folder);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return sources;
|
|
17
|
+
}
|
|
18
|
+
function collisionsOf(paths) {
|
|
19
|
+
const groups = new Map();
|
|
20
|
+
for (const path of paths) {
|
|
21
|
+
const key = path.toLowerCase();
|
|
22
|
+
groups.set(key, [...(groups.get(key) ?? []), path]);
|
|
23
|
+
}
|
|
24
|
+
return new Map([...groups].filter(([, group]) => group.length > 1));
|
|
25
|
+
}
|
|
26
|
+
function message(source, others) {
|
|
27
|
+
const names = others.map((other) => `"${posix.basename(other)}"`).join(", ");
|
|
28
|
+
return `"${posix.basename(source)}" and ${names} differ only by case, in their names or in the files they build to, so they are one path on a case-insensitive filesystem such as macOS or Windows. Rename one of them.`;
|
|
29
|
+
}
|
|
30
|
+
export const distinctCase = defineRule({
|
|
31
|
+
check: ({ files }) => {
|
|
32
|
+
const sources = sourcesByPath(files);
|
|
33
|
+
const collisions = collisionsOf(sources.keys());
|
|
34
|
+
return [...collisions]
|
|
35
|
+
.filter(([key]) => !collisions.has(posix.dirname(key)))
|
|
36
|
+
.flatMap(([, group]) => {
|
|
37
|
+
const all = group.flatMap((path) => sources.get(path) ?? []).sort();
|
|
38
|
+
return all.map((source) => ({
|
|
39
|
+
file: source,
|
|
40
|
+
message: message(source, all.filter((other) => other !== source)),
|
|
41
|
+
}));
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
description: "No two paths, or the files they build to, differ only by case, so a checkout and a build work on a case-insensitive filesystem.",
|
|
45
|
+
id: "files/distinct-case",
|
|
46
|
+
});
|
|
47
|
+
//# sourceMappingURL=distinctCase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"distinctCase.js","sourceRoot":"","sources":["../../../src/rules/files/distinctCase.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"namedAfterExport.d.ts","sourceRoot":"","sources":["../../../src/rules/files/namedAfterExport.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"namedAfterExport.d.ts","sourceRoot":"","sources":["../../../src/rules/files/namedAfterExport.ts"],"names":[],"mappings":"AAoFA,eAAO,MAAM,gBAAgB;;EAU3B,CAAC"}
|
|
@@ -8,7 +8,15 @@ import { packageLayout } from "#naming/packageLayout.ts";
|
|
|
8
8
|
import { CAMEL, KEBAB, sameWord, wordsOf } from "#naming/words.ts";
|
|
9
9
|
import { defineRule } from "#rule.ts";
|
|
10
10
|
import { parse } from "#rules/syntax.ts";
|
|
11
|
-
const DEFAULT_TOOL_OWNED = [
|
|
11
|
+
const DEFAULT_TOOL_OWNED = [
|
|
12
|
+
"*.config.*",
|
|
13
|
+
"*.d.ts",
|
|
14
|
+
"*.d.mts",
|
|
15
|
+
"*.d.cts",
|
|
16
|
+
"**/.railway/railway.ts",
|
|
17
|
+
"**/.railway/railway.py",
|
|
18
|
+
"**/.railway/railway.go",
|
|
19
|
+
];
|
|
12
20
|
const TEST = /\.(?:test|spec)\.[cm]?[jt]sx?$/u;
|
|
13
21
|
const SHEBANG = "#!";
|
|
14
22
|
const SHAPES = "The file name and the names of its folders spell the export, in any order, and the file's first letter follows the export's case: listItems in items/ is items/list.ts, ItemPanel is ItemPanel.tsx or item/Panel.tsx.";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"namedAfterExport.js","sourceRoot":"","sources":["../../../src/rules/files/namedAfterExport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAsB,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC7E,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAE,UAAU,EAAiC,MAAM,UAAU,CAAC;AACrE,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC,MAAM,kBAAkB,GAAsB,
|
|
1
|
+
{"version":3,"file":"namedAfterExport.js","sourceRoot":"","sources":["../../../src/rules/files/namedAfterExport.ts"],"names":[],"mappings":"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"}
|
package/docs/modules.md
CHANGED
|
@@ -188,6 +188,7 @@ A module link opens its source. Type and value declarations with the same name a
|
|
|
188
188
|
|
|
189
189
|
| Module | Exported names |
|
|
190
190
|
| --- | --- |
|
|
191
|
+
| [rules/files/distinctCase.ts](https://github.com/ShivaeDev/platform/blob/main/packages/quality/src/rules/files/distinctCase.ts) | `distinctCase` |
|
|
191
192
|
| [rules/files/folderNames.ts](https://github.com/ShivaeDev/platform/blob/main/packages/quality/src/rules/files/folderNames.ts) | `folderNames` |
|
|
192
193
|
| [rules/files/namedAfterExport.ts](https://github.com/ShivaeDev/platform/blob/main/packages/quality/src/rules/files/namedAfterExport.ts) | `namedAfterExport` |
|
|
193
194
|
| [rules/files/otherNames.ts](https://github.com/ShivaeDev/platform/blob/main/packages/quality/src/rules/files/otherNames.ts) | `otherNames` |
|
package/docs/naming.md
CHANGED
|
@@ -60,7 +60,7 @@ The plugins report under the one Biome category `plugin`, so their findings shar
|
|
|
60
60
|
| `maxItems.ts` | `MAX_ITEMS` alone | No: constants live in a topic file with related constants |
|
|
61
61
|
| `items/listItems.ts` | `listItems` | No: the folder is the prefix, so it is `items/list.ts` |
|
|
62
62
|
|
|
63
|
-
Test files are left to the test rules. The `toolOwned` option lists `.gitignore` patterns of files whose names a tool fixes, `*.config
|
|
63
|
+
Test files are left to the test rules. The `toolOwned` option lists `.gitignore` patterns of files whose names a tool fixes, `*.config.*`, declaration files and `.railway/railway.ts` (also `.py` and `.go`) by default. The Railway paths apply at any project root; other files in `.railway/` still follow the naming rules. The shared Biome preset also permits default exports in these config paths.
|
|
64
64
|
|
|
65
65
|
### Folders
|
|
66
66
|
|
|
@@ -84,6 +84,10 @@ The `toolOwned` option lists folders a tool names, `migrations/` by default. Aut
|
|
|
84
84
|
|
|
85
85
|
The `toolOwned` option lists files a tool names: dotfiles, `package.json`, `tsconfig*.json`, `biome.json`, `*.config.*` and `migrations/` by default.
|
|
86
86
|
|
|
87
|
+
### Case
|
|
88
|
+
|
|
89
|
+
`files/distinct-case` keeps every path apart on a case-insensitive filesystem, such as macOS or Windows. Two files or folders in one folder must differ by more than case, and so must the files a build makes of them: `ReadingHistory.tsx` and `readingHistory.ts` both build to `readingHistory.js`, so they are a finding. A folder pair is one finding, not one per file inside it.
|
|
90
|
+
|
|
87
91
|
### Tests
|
|
88
92
|
|
|
89
93
|
A test sits beside the code it covers, and its name says what it covers and where it runs.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/quality",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.1",
|
|
4
4
|
"description": "Repository quality gate with typed rules, a baseline of existing debt and a registry of reasoned exceptions",
|
|
5
5
|
"homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/quality#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
],
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@biomejs/biome": "2.5.15",
|
|
56
|
-
"@shivaedev/types": "0.2.
|
|
56
|
+
"@shivaedev/types": "0.2.1",
|
|
57
57
|
"ignore": "7.0.12",
|
|
58
58
|
"sort-package-json": "4.0.0",
|
|
59
59
|
"typescript": "npm:@typescript/typescript6@6.0.2"
|
package/src/rules/built-in.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { noJsdoc } from "#rules/comments/no-jsdoc.ts";
|
|
|
5
5
|
import { noLineReference } from "#rules/comments/no-line-reference.ts";
|
|
6
6
|
import { noPrReference } from "#rules/comments/no-pr-reference.ts";
|
|
7
7
|
import { noTodo } from "#rules/comments/no-todo.ts";
|
|
8
|
+
import { distinctCase } from "#rules/files/distinctCase.ts";
|
|
8
9
|
import { folderNames } from "#rules/files/folderNames.ts";
|
|
9
10
|
import { namedAfterExport } from "#rules/files/namedAfterExport.ts";
|
|
10
11
|
import { otherNames } from "#rules/files/otherNames.ts";
|
|
@@ -52,6 +53,7 @@ export const builtInRules = [
|
|
|
52
53
|
manifestsSorted,
|
|
53
54
|
namedAfterExport,
|
|
54
55
|
folderNames,
|
|
56
|
+
distinctCase,
|
|
55
57
|
otherNames,
|
|
56
58
|
testsFollow,
|
|
57
59
|
testsColocated,
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { posix } from "node:path";
|
|
2
|
+
import { defineRule, type Finding } from "#rule.ts";
|
|
3
|
+
|
|
4
|
+
const CODE = /\.(?<mode>[cm]?)[jt]sx?$/u;
|
|
5
|
+
|
|
6
|
+
function sourcesByPath(files: readonly string[]): ReadonlyMap<string, readonly string[]> {
|
|
7
|
+
const sources = new Map<string, readonly string[]>();
|
|
8
|
+
function add(path: string, source: string) {
|
|
9
|
+
const known = sources.get(path) ?? [];
|
|
10
|
+
sources.set(path, known.includes(source) ? known : [...known, source]);
|
|
11
|
+
}
|
|
12
|
+
for (const file of files) {
|
|
13
|
+
add(file.replace(CODE, ".$<mode>js"), file);
|
|
14
|
+
for (let folder = posix.dirname(file); folder !== "."; folder = posix.dirname(folder)) {
|
|
15
|
+
add(folder, folder);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return sources;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function collisionsOf(paths: Iterable<string>): ReadonlyMap<string, readonly string[]> {
|
|
22
|
+
const groups = new Map<string, readonly string[]>();
|
|
23
|
+
for (const path of paths) {
|
|
24
|
+
const key = path.toLowerCase();
|
|
25
|
+
groups.set(key, [...(groups.get(key) ?? []), path]);
|
|
26
|
+
}
|
|
27
|
+
return new Map([...groups].filter(([, group]) => group.length > 1));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function message(source: string, others: readonly string[]): string {
|
|
31
|
+
const names = others.map((other) => `"${posix.basename(other)}"`).join(", ");
|
|
32
|
+
return `"${posix.basename(source)}" and ${names} differ only by case, in their names or in the files they build to, so they are one path on a case-insensitive filesystem such as macOS or Windows. Rename one of them.`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const distinctCase = defineRule({
|
|
36
|
+
check: ({ files }) => {
|
|
37
|
+
const sources = sourcesByPath(files);
|
|
38
|
+
const collisions = collisionsOf(sources.keys());
|
|
39
|
+
return [...collisions]
|
|
40
|
+
.filter(([key]) => !collisions.has(posix.dirname(key)))
|
|
41
|
+
.flatMap(([, group]): readonly Finding[] => {
|
|
42
|
+
const all = group.flatMap((path) => sources.get(path) ?? []).sort();
|
|
43
|
+
return all.map((source) => ({
|
|
44
|
+
file: source,
|
|
45
|
+
message: message(
|
|
46
|
+
source,
|
|
47
|
+
all.filter((other) => other !== source),
|
|
48
|
+
),
|
|
49
|
+
}));
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
description: "No two paths, or the files they build to, differ only by case, so a checkout and a build work on a case-insensitive filesystem.",
|
|
53
|
+
id: "files/distinct-case",
|
|
54
|
+
});
|
|
@@ -9,7 +9,15 @@ import { CAMEL, KEBAB, sameWord, wordsOf } from "#naming/words.ts";
|
|
|
9
9
|
import { defineRule, type Finding, type SourceFile } from "#rule.ts";
|
|
10
10
|
import { parse } from "#rules/syntax.ts";
|
|
11
11
|
|
|
12
|
-
const DEFAULT_TOOL_OWNED: readonly string[] = [
|
|
12
|
+
const DEFAULT_TOOL_OWNED: readonly string[] = [
|
|
13
|
+
"*.config.*",
|
|
14
|
+
"*.d.ts",
|
|
15
|
+
"*.d.mts",
|
|
16
|
+
"*.d.cts",
|
|
17
|
+
"**/.railway/railway.ts",
|
|
18
|
+
"**/.railway/railway.py",
|
|
19
|
+
"**/.railway/railway.go",
|
|
20
|
+
];
|
|
13
21
|
|
|
14
22
|
const TEST = /\.(?:test|spec)\.[cm]?[jt]sx?$/u;
|
|
15
23
|
|