@shivaedev/quality 0.7.3 → 0.8.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.
Files changed (90) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +103 -9
  3. package/biome/plugins/constant-names.grit +17 -0
  4. package/biome/plugins/effect-fn-spans.grit +13 -3
  5. package/biome/plugins/handler-names.grit +10 -0
  6. package/biome/plugins/schema-names.grit +13 -0
  7. package/biome/plugins/schema-struct-keys.grit +7 -0
  8. package/biome/plugins/service-layers.grit +13 -0
  9. package/biome/plugins/type-re-exports.grit +8 -0
  10. package/biome/preset.json +48 -1
  11. package/dist/imports/graph.d.ts.map +1 -1
  12. package/dist/imports/graph.js +9 -6
  13. package/dist/imports/graph.js.map +1 -1
  14. package/dist/imports/resolve.d.ts +1 -0
  15. package/dist/imports/resolve.d.ts.map +1 -1
  16. package/dist/imports/resolve.js +14 -0
  17. package/dist/imports/resolve.js.map +1 -1
  18. package/dist/naming/exportFits.d.ts +2 -0
  19. package/dist/naming/exportFits.d.ts.map +1 -0
  20. package/dist/naming/exportFits.js +20 -0
  21. package/dist/naming/exportFits.js.map +1 -0
  22. package/dist/naming/mainExport.d.ts +3 -0
  23. package/dist/naming/mainExport.d.ts.map +1 -0
  24. package/dist/naming/mainExport.js +10 -0
  25. package/dist/naming/mainExport.js.map +1 -0
  26. package/dist/naming/ownExports.d.ts +8 -0
  27. package/dist/naming/ownExports.d.ts.map +1 -0
  28. package/dist/naming/ownExports.js +78 -0
  29. package/dist/naming/ownExports.js.map +1 -0
  30. package/dist/naming/packageLayout.d.ts +8 -0
  31. package/dist/naming/packageLayout.d.ts.map +1 -0
  32. package/dist/naming/packageLayout.js +57 -0
  33. package/dist/naming/packageLayout.js.map +1 -0
  34. package/dist/naming/testName.d.ts +11 -0
  35. package/dist/naming/testName.d.ts.map +1 -0
  36. package/dist/naming/testName.js +13 -0
  37. package/dist/naming/testName.js.map +1 -0
  38. package/dist/naming/words.d.ts +8 -0
  39. package/dist/naming/words.d.ts.map +1 -0
  40. package/dist/naming/words.js +21 -0
  41. package/dist/naming/words.js.map +1 -0
  42. package/dist/rules/built-in.d.ts +13 -1
  43. package/dist/rules/built-in.d.ts.map +1 -1
  44. package/dist/rules/built-in.js +10 -0
  45. package/dist/rules/built-in.js.map +1 -1
  46. package/dist/rules/files/folderNames.d.ts +5 -0
  47. package/dist/rules/files/folderNames.d.ts.map +1 -0
  48. package/dist/rules/files/folderNames.js +74 -0
  49. package/dist/rules/files/folderNames.js.map +1 -0
  50. package/dist/rules/files/namedAfterExport.d.ts +4 -0
  51. package/dist/rules/files/namedAfterExport.d.ts.map +1 -0
  52. package/dist/rules/files/namedAfterExport.js +76 -0
  53. package/dist/rules/files/namedAfterExport.js.map +1 -0
  54. package/dist/rules/files/otherNames.d.ts +5 -0
  55. package/dist/rules/files/otherNames.d.ts.map +1 -0
  56. package/dist/rules/files/otherNames.js +99 -0
  57. package/dist/rules/files/otherNames.js.map +1 -0
  58. package/dist/rules/test-names/colocated.d.ts +4 -0
  59. package/dist/rules/test-names/colocated.d.ts.map +1 -0
  60. package/dist/rules/test-names/colocated.js +31 -0
  61. package/dist/rules/test-names/colocated.js.map +1 -0
  62. package/dist/rules/test-names/follow.d.ts +4 -0
  63. package/dist/rules/test-names/follow.d.ts.map +1 -0
  64. package/dist/rules/test-names/follow.js +66 -0
  65. package/dist/rules/test-names/follow.js.map +1 -0
  66. package/dist/rules/test-names/usesDom.d.ts +3 -0
  67. package/dist/rules/test-names/usesDom.d.ts.map +1 -0
  68. package/dist/rules/test-names/usesDom.js +18 -0
  69. package/dist/rules/test-names/usesDom.js.map +1 -0
  70. package/dist/vitest.d.ts +3 -1
  71. package/dist/vitest.d.ts.map +1 -1
  72. package/dist/vitest.js +11 -11
  73. package/dist/vitest.js.map +1 -1
  74. package/package.json +1 -1
  75. package/src/imports/graph.ts +11 -7
  76. package/src/imports/resolve.ts +18 -0
  77. package/src/naming/exportFits.ts +21 -0
  78. package/src/naming/mainExport.ts +11 -0
  79. package/src/naming/ownExports.ts +95 -0
  80. package/src/naming/packageLayout.ts +75 -0
  81. package/src/naming/testName.ts +25 -0
  82. package/src/naming/words.ts +28 -0
  83. package/src/rules/built-in.ts +10 -0
  84. package/src/rules/files/folderNames.ts +92 -0
  85. package/src/rules/files/namedAfterExport.ts +87 -0
  86. package/src/rules/files/otherNames.ts +118 -0
  87. package/src/rules/test-names/colocated.ts +34 -0
  88. package/src/rules/test-names/follow.ts +78 -0
  89. package/src/rules/test-names/usesDom.ts +22 -0
  90. package/src/vitest.ts +14 -11
@@ -0,0 +1,78 @@
1
+ import ts from "typescript";
2
+ const CONSTANT_CASE = /^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$/u;
3
+ const PRIMITIVE_LITERALS = new Set([
4
+ ts.SyntaxKind.BigIntLiteral,
5
+ ts.SyntaxKind.FalseKeyword,
6
+ ts.SyntaxKind.NoSubstitutionTemplateLiteral,
7
+ ts.SyntaxKind.NumericLiteral,
8
+ ts.SyntaxKind.StringLiteral,
9
+ ts.SyntaxKind.TrueKeyword,
10
+ ]);
11
+ function isPrimitiveLiteral(node) {
12
+ if (node === undefined) {
13
+ return false;
14
+ }
15
+ if (ts.isPrefixUnaryExpression(node)) {
16
+ return isPrimitiveLiteral(node.operand);
17
+ }
18
+ return PRIMITIVE_LITERALS.has(node.kind);
19
+ }
20
+ function isExported(statement) {
21
+ const modifiers = ts.canHaveModifiers(statement) ? ts.getModifiers(statement) : undefined;
22
+ const exported = modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ?? false;
23
+ const isDefault = modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword) ?? false;
24
+ return exported && !isDefault;
25
+ }
26
+ function variables(statement) {
27
+ const constant = (statement.declarationList.flags & ts.NodeFlags.Const) !== 0;
28
+ return statement.declarationList.declarations.flatMap((declaration) => ts.isIdentifier(declaration.name)
29
+ ? [
30
+ {
31
+ constant: constant && (CONSTANT_CASE.test(declaration.name.text) || isPrimitiveLiteral(declaration.initializer)),
32
+ kind: "value",
33
+ name: declaration.name.text,
34
+ },
35
+ ]
36
+ : []);
37
+ }
38
+ function declared(statement) {
39
+ if (ts.isVariableStatement(statement)) {
40
+ return variables(statement);
41
+ }
42
+ if (ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement)) {
43
+ return [{ constant: false, kind: "type", name: statement.name.text }];
44
+ }
45
+ const named = ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement) || ts.isEnumDeclaration(statement) || ts.isModuleDeclaration(statement)
46
+ ? statement.name
47
+ : undefined;
48
+ return named !== undefined && ts.isIdentifier(named) ? [{ constant: false, kind: "value", name: named.text }] : [];
49
+ }
50
+ function locals(source) {
51
+ const all = source.statements.flatMap(declared);
52
+ return new Map(all.map((local) => [local.name, local]));
53
+ }
54
+ function listed(statement, known) {
55
+ const clause = statement.exportClause;
56
+ if (statement.moduleSpecifier !== undefined || clause === undefined || !ts.isNamedExports(clause)) {
57
+ return [];
58
+ }
59
+ return clause.elements.flatMap((element) => {
60
+ const name = element.name.text;
61
+ const local = known.get((element.propertyName ?? element.name).text);
62
+ if (name === "default" || local === undefined) {
63
+ return [];
64
+ }
65
+ const typeOnly = statement.isTypeOnly || element.isTypeOnly;
66
+ return [{ constant: local.constant, kind: typeOnly ? "type" : local.kind, name }];
67
+ });
68
+ }
69
+ export function ownExports(source) {
70
+ const known = locals(source);
71
+ return source.statements.flatMap((statement) => {
72
+ if (ts.isExportDeclaration(statement)) {
73
+ return listed(statement, known);
74
+ }
75
+ return isExported(statement) ? declared(statement) : [];
76
+ });
77
+ }
78
+ //# sourceMappingURL=ownExports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ownExports.js","sourceRoot":"","sources":["../../src/naming/ownExports.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { RuleInputs } from "#rule.ts";
2
+ export interface PackageLayout {
3
+ readonly foldersOf: (path: string) => readonly string[];
4
+ readonly names: ReadonlyMap<string, string>;
5
+ readonly scripts: ReadonlySet<string>;
6
+ }
7
+ export declare function packageLayout({ files, readText }: Pick<RuleInputs, "files" | "readText">): Promise<PackageLayout>;
8
+ //# sourceMappingURL=packageLayout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packageLayout.d.ts","sourceRoot":"","sources":["../../src/naming/packageLayout.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,MAAM,EAAE,CAAC;IACxD,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACtC;AA+CD,wBAAsB,aAAa,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,GAAG,UAAU,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,CAoBvH"}
@@ -0,0 +1,57 @@
1
+ import { posix } from "node:path";
2
+ const MANIFEST = "package.json";
3
+ const SOURCE_FOLDER = "src";
4
+ const RUNNABLE = /\.[cm]?[jt]sx?$/u;
5
+ const BUILT = /^dist\/(?<entry>.+)\.[cm]?js$/u;
6
+ function parsed(text) {
7
+ if (text === undefined) {
8
+ return undefined;
9
+ }
10
+ try {
11
+ return JSON.parse(text);
12
+ }
13
+ catch {
14
+ return undefined;
15
+ }
16
+ }
17
+ function field(value, key) {
18
+ return typeof value === "object" && value !== null && key in value ? Reflect.get(value, key) : undefined;
19
+ }
20
+ function texts(value) {
21
+ if (typeof value === "string") {
22
+ return [value];
23
+ }
24
+ return typeof value === "object" && value !== null ? Object.values(value).filter((entry) => typeof entry === "string") : [];
25
+ }
26
+ function sourcesOf(target) {
27
+ const built = BUILT.exec(target)?.groups?.entry;
28
+ return built === undefined ? [target] : [target, `src/${built}.ts`, `src/${built}.tsx`];
29
+ }
30
+ function packageOf(path, roots) {
31
+ return roots.find((root) => root === "" || path.startsWith(`${root}/`)) ?? "";
32
+ }
33
+ function runBy(directory, manifest) {
34
+ const commands = texts(field(manifest, "scripts")).flatMap((command) => command.split(/\s+/u).filter((token) => RUNNABLE.test(token)));
35
+ const bins = texts(field(manifest, "bin")).flatMap(sourcesOf);
36
+ return [...commands, ...bins].map((target) => posix.join(directory, target));
37
+ }
38
+ export async function packageLayout({ files, readText }) {
39
+ const directories = ["", ...files.filter((path) => posix.basename(path) === MANIFEST).map((path) => posix.dirname(path))];
40
+ const manifests = await Promise.all([...new Set(directories)].map(async (directory) => ({
41
+ directory: directory === "." ? "" : directory,
42
+ manifest: parsed(await readText(directory === "" ? MANIFEST : `${directory}/${MANIFEST}`)),
43
+ })));
44
+ const present = manifests.filter((entry) => entry.manifest !== undefined);
45
+ const roots = present.map((entry) => entry.directory).toSorted((left, right) => right.length - left.length);
46
+ const names = new Map(present.flatMap(({ directory, manifest }) => texts(field(manifest, "name")).map((name) => [directory, name])));
47
+ return {
48
+ foldersOf: (path) => {
49
+ const root = packageOf(path, roots);
50
+ const folders = posix.dirname(root === "" ? path : path.slice(root.length + 1)).split("/");
51
+ return folders.filter((folder, index) => folder !== "." && !(index === 0 && folder === SOURCE_FOLDER));
52
+ },
53
+ names,
54
+ scripts: new Set(present.flatMap(({ directory, manifest }) => runBy(directory, manifest))),
55
+ };
56
+ }
57
+ //# sourceMappingURL=packageLayout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packageLayout.js","sourceRoot":"","sources":["../../src/naming/packageLayout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export declare const ENVIRONMENTS: readonly ["dom", "slow", "typecheck"];
2
+ export type Environment = (typeof ENVIRONMENTS)[number];
3
+ export interface TestName {
4
+ readonly extension: string;
5
+ readonly folder: string;
6
+ readonly kind: "spec" | "test";
7
+ readonly modifiers: readonly string[];
8
+ readonly stem: string;
9
+ }
10
+ export declare function testName(path: string): TestName | undefined;
11
+ //# sourceMappingURL=testName.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testName.d.ts","sourceRoot":"","sources":["../../src/naming/testName.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,uCAAwC,CAAC;AAElE,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAExD,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAID,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAQ3D"}
@@ -0,0 +1,13 @@
1
+ import { posix } from "node:path";
2
+ export const ENVIRONMENTS = ["dom", "slow", "typecheck"];
3
+ const TEST_FILE = /^(?<base>.+)\.(?<kind>test|spec)(?<extension>\.[cm]?[jt]sx?)$/u;
4
+ export function testName(path) {
5
+ const groups = TEST_FILE.exec(posix.basename(path))?.groups;
6
+ const kind = groups?.kind;
7
+ if (groups?.base === undefined || groups.extension === undefined || (kind !== "test" && kind !== "spec")) {
8
+ return undefined;
9
+ }
10
+ const [stem = "", ...modifiers] = groups.base.split(".");
11
+ return { extension: groups.extension, folder: posix.dirname(path), kind, modifiers, stem };
12
+ }
13
+ //# sourceMappingURL=testName.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testName.js","sourceRoot":"","sources":["../../src/naming/testName.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAElC,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAU,CAAC;AAYlE,MAAM,SAAS,GAAG,gEAAgE,CAAC;AAEnF,MAAM,UAAU,QAAQ,CAAC,IAAY;IACpC,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC;IAC5D,MAAM,IAAI,GAAG,MAAM,EAAE,IAAI,CAAC;IAC1B,IAAI,MAAM,EAAE,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,MAAM,CAAC,EAAE,CAAC;QAC1G,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,MAAM,CAAC,IAAI,GAAG,EAAE,EAAE,GAAG,SAAS,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzD,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AAC5F,CAAC"}
@@ -0,0 +1,8 @@
1
+ export declare const KEBAB: RegExp;
2
+ export declare const CAMEL: RegExp;
3
+ export declare const PASCAL: RegExp;
4
+ export declare function wordsOf(name: string): readonly string[];
5
+ export declare function singular(word: string): string;
6
+ export declare function sameWord(left: string, right: string): boolean;
7
+ export declare function isUpper(text: string): boolean;
8
+ //# sourceMappingURL=words.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"words.d.ts","sourceRoot":"","sources":["../../src/naming/words.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,KAAK,QAAgC,CAAC;AAEnD,eAAO,MAAM,KAAK,QAAyB,CAAC;AAE5C,eAAO,MAAM,MAAM,QAAyB,CAAC;AAI7C,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAEvD;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK7C;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE7C"}
@@ -0,0 +1,21 @@
1
+ const WORD = /[A-Z]?[a-z0-9]+|[A-Z]+(?![a-z])/gu;
2
+ export const KEBAB = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
3
+ export const CAMEL = /^[a-z][a-zA-Z0-9]*$/u;
4
+ export const PASCAL = /^[A-Z][a-zA-Z0-9]*$/u;
5
+ const UPPER_START = /^[A-Z]/u;
6
+ export function wordsOf(name) {
7
+ return [...name.matchAll(WORD)].map((match) => match[0].toLowerCase());
8
+ }
9
+ export function singular(word) {
10
+ if (word.endsWith("ies") && word.length > 3) {
11
+ return `${word.slice(0, -3)}y`;
12
+ }
13
+ return word.endsWith("s") && !word.endsWith("ss") && word.length > 1 ? word.slice(0, -1) : word;
14
+ }
15
+ export function sameWord(left, right) {
16
+ return singular(left) === singular(right);
17
+ }
18
+ export function isUpper(text) {
19
+ return UPPER_START.test(text);
20
+ }
21
+ //# sourceMappingURL=words.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"words.js","sourceRoot":"","sources":["../../src/naming/words.ts"],"names":[],"mappings":"AAAA,MAAM,IAAI,GAAG,mCAAmC,CAAC;AAEjD,MAAM,CAAC,MAAM,KAAK,GAAG,6BAA6B,CAAC;AAEnD,MAAM,CAAC,MAAM,KAAK,GAAG,sBAAsB,CAAC;AAE5C,MAAM,CAAC,MAAM,MAAM,GAAG,sBAAsB,CAAC;AAE7C,MAAM,WAAW,GAAG,SAAS,CAAC;AAE9B,MAAM,UAAU,OAAO,CAAC,IAAY;IACnC,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAY;IACpC,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7C,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;IAChC,CAAC;IACD,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACjG,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,KAAa;IACnD,OAAO,QAAQ,CAAC,IAAI,CAAC,KAAK,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,IAAY;IACnC,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC/B,CAAC"}
@@ -17,5 +17,17 @@ export declare const builtInRules: readonly [import("../rule.ts").Rule<"structur
17
17
  readonly fences?: readonly import("../index.ts").Fence[];
18
18
  }>, import("../rule.ts").Rule<"imports/resolvable", {
19
19
  readonly generated?: readonly string[];
20
- }>, import("../rule.ts").Rule<"imports/aliased", undefined>, import("../rule.ts").Rule<"manifests/sorted", undefined>];
20
+ }>, import("../rule.ts").Rule<"imports/aliased", undefined>, import("../rule.ts").Rule<"manifests/sorted", undefined>, import("../rule.ts").Rule<"files/named-after-export", {
21
+ readonly toolOwned?: readonly string[];
22
+ }>, import("../rule.ts").Rule<"files/folder-names", {
23
+ readonly content?: readonly string[];
24
+ readonly toolOwned?: readonly string[];
25
+ }>, import("../rule.ts").Rule<"files/other-names", {
26
+ readonly content?: readonly string[];
27
+ readonly toolOwned?: readonly string[];
28
+ }>, import("../rule.ts").Rule<"tests/follow", {
29
+ readonly suites?: readonly string[];
30
+ }>, import("../rule.ts").Rule<"tests/colocated", {
31
+ readonly suites?: readonly string[];
32
+ }>];
21
33
  //# sourceMappingURL=built-in.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"built-in.d.ts","sourceRoot":"","sources":["../../src/rules/built-in.ts"],"names":[],"mappings":"AAkBA,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;sHAkBf,CAAC"}
1
+ {"version":3,"file":"built-in.d.ts","sourceRoot":"","sources":["../../src/rules/built-in.ts"],"names":[],"mappings":"AAuBA,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuBf,CAAC"}
@@ -5,6 +5,9 @@ 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 { folderNames } from "#rules/files/folderNames.ts";
9
+ import { namedAfterExport } from "#rules/files/namedAfterExport.ts";
10
+ import { otherNames } from "#rules/files/otherNames.ts";
8
11
  import { importsAliased } from "#rules/imports/aliased.ts";
9
12
  import { importCycles } from "#rules/imports/cycles.ts";
10
13
  import { importFences } from "#rules/imports/fences.ts";
@@ -12,6 +15,8 @@ import { importsResolvable } from "#rules/imports/resolvable.ts";
12
15
  import { biomeOverrides } from "#rules/suppressions/biome-overrides.ts";
13
16
  import { noDoubleCast } from "#rules/suppressions/no-double-cast.ts";
14
17
  import { noInline } from "#rules/suppressions/no-inline.ts";
18
+ import { testsColocated } from "#rules/test-names/colocated.ts";
19
+ import { testsFollow } from "#rules/test-names/follow.ts";
15
20
  import { biome } from "./biome.js";
16
21
  import { manifestsSorted } from "./manifests-sorted.js";
17
22
  import { maxLines } from "./max-lines.js";
@@ -33,5 +38,10 @@ export const builtInRules = [
33
38
  importsResolvable,
34
39
  importsAliased,
35
40
  manifestsSorted,
41
+ namedAfterExport,
42
+ folderNames,
43
+ otherNames,
44
+ testsFollow,
45
+ testsColocated,
36
46
  ];
37
47
  //# sourceMappingURL=built-in.js.map
@@ -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,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAC5D,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,cAAc;IACd,KAAK;IACL,YAAY;IACZ,YAAY;IACZ,iBAAiB;IACjB,cAAc;IACd,eAAe;CACN,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,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,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAC5D,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,cAAc;IACd,KAAK;IACL,YAAY;IACZ,YAAY;IACZ,iBAAiB;IACjB,cAAc;IACd,eAAe;IACf,gBAAgB;IAChB,WAAW;IACX,UAAU;IACV,WAAW;IACX,cAAc;CACL,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare const folderNames: import("#rule.ts").Rule<"files/folder-names", {
2
+ readonly content?: readonly string[];
3
+ readonly toolOwned?: readonly string[];
4
+ }>;
5
+ //# sourceMappingURL=folderNames.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folderNames.d.ts","sourceRoot":"","sources":["../../../src/rules/files/folderNames.ts"],"names":[],"mappings":"AAyEA,eAAO,MAAM,WAAW;;;EAkBtB,CAAC"}
@@ -0,0 +1,74 @@
1
+ import { posix } from "node:path";
2
+ import { Effect, Schema } from "effect";
3
+ import ignore, {} from "ignore";
4
+ import { packageLayout } from "#naming/packageLayout.ts";
5
+ import { KEBAB, PASCAL } from "#naming/words.ts";
6
+ import { defineRule } from "#rule.ts";
7
+ const DEFAULT_TOOL_OWNED = ["generated/", "migrations/"];
8
+ const SNAKE = /^[a-z0-9]+(?:_[a-z0-9]+)*$/u;
9
+ const CODE = /\.[cm]?[jt]sx?$/u;
10
+ const FolderNamesOptions = Schema.Struct({
11
+ content: Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed([]))),
12
+ toolOwned: Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed(DEFAULT_TOOL_OWNED))),
13
+ });
14
+ function foldersIn(files) {
15
+ const folders = new Set();
16
+ for (const file of files) {
17
+ for (let folder = posix.dirname(file); folder !== "."; folder = posix.dirname(folder)) {
18
+ folders.add(folder);
19
+ }
20
+ }
21
+ return [...folders].sort();
22
+ }
23
+ function stemsIn(files) {
24
+ return new Set(files.filter((file) => CODE.test(file)).map((file) => `${posix.dirname(file)}/${posix.basename(file).replace(CODE, "")}`));
25
+ }
26
+ function isModule(folder, stems) {
27
+ const name = posix.basename(folder);
28
+ return stems.has(`${folder}/${name}`) || stems.has(`${posix.dirname(folder)}/${name}`);
29
+ }
30
+ function packageMessage(name, packageName) {
31
+ const expected = packageName.replace(/^@[^/]+\//u, "");
32
+ return name === expected ? undefined : `The folder of the package ${packageName} is named "${expected}", in kebab-case after the package name.`;
33
+ }
34
+ function groupMessage(name) {
35
+ if (PASCAL.test(name)) {
36
+ return `"${name}" is PascalCase but holds no ${name}.ts or ${name}.tsx. A PascalCase folder is a module named after its main file: add that file, or name the folder in kebab-case.`;
37
+ }
38
+ return KEBAB.test(name.replace(/^\./u, ""))
39
+ ? undefined
40
+ : `"${name}" groups files, so it is named in kebab-case, such as test-support/. A folder named after a file beside or inside it is a module and takes that file's name.`;
41
+ }
42
+ function messageFor(folder, context) {
43
+ const name = posix.basename(folder);
44
+ if (context.toolOwned.ignores(`${folder}/`) || isModule(folder, context.stems)) {
45
+ return undefined;
46
+ }
47
+ const packageName = context.names.get(folder);
48
+ if (packageName !== undefined) {
49
+ return packageMessage(name, packageName);
50
+ }
51
+ if (context.content.ignores(`${folder}/`)) {
52
+ return SNAKE.test(name) ? undefined : `"${name}" holds content, so it is named in snake_case, such as forest_path/.`;
53
+ }
54
+ return groupMessage(name);
55
+ }
56
+ export const folderNames = defineRule({
57
+ check: async ({ files, options, readText }) => {
58
+ const layout = await packageLayout({ files, readText });
59
+ const context = {
60
+ content: ignore().add([...options.content]),
61
+ names: layout.names,
62
+ stems: stemsIn(files),
63
+ toolOwned: ignore().add([...options.toolOwned]),
64
+ };
65
+ return foldersIn(files).flatMap((folder) => {
66
+ const message = messageFor(folder, context);
67
+ return message === undefined ? [] : [{ file: folder, message }];
68
+ });
69
+ },
70
+ description: "A package folder is named after its package, a module folder after its main file, a content folder in snake_case and every other folder in kebab-case.",
71
+ id: "files/folder-names",
72
+ options: Schema.toStandardSchemaV1(FolderNamesOptions, { parseOptions: { errors: "all", onExcessProperty: "error" } }),
73
+ });
74
+ //# sourceMappingURL=folderNames.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folderNames.js","sourceRoot":"","sources":["../../../src/rules/files/folderNames.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare const namedAfterExport: import("#rule.ts").Rule<"files/named-after-export", {
2
+ readonly toolOwned?: readonly string[];
3
+ }>;
4
+ //# sourceMappingURL=namedAfterExport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"namedAfterExport.d.ts","sourceRoot":"","sources":["../../../src/rules/files/namedAfterExport.ts"],"names":[],"mappings":"AA4EA,eAAO,MAAM,gBAAgB;;EAU3B,CAAC"}
@@ -0,0 +1,76 @@
1
+ import { posix } from "node:path";
2
+ import { Effect, Schema } from "effect";
3
+ import ignore from "ignore";
4
+ import { exportFits } from "#naming/exportFits.ts";
5
+ import { mainExport } from "#naming/mainExport.ts";
6
+ import { ownExports } from "#naming/ownExports.ts";
7
+ import { packageLayout } from "#naming/packageLayout.ts";
8
+ import { CAMEL, KEBAB, sameWord, wordsOf } from "#naming/words.ts";
9
+ import { defineRule } from "#rule.ts";
10
+ import { parse } from "#rules/syntax.ts";
11
+ const DEFAULT_TOOL_OWNED = ["*.config.*", "*.d.ts", "*.d.mts", "*.d.cts"];
12
+ const TEST = /\.(?:test|spec)\.[cm]?[jt]sx?$/u;
13
+ const SHEBANG = "#!";
14
+ 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.";
15
+ const NamedAfterExportOptions = Schema.Struct({
16
+ toolOwned: Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed(DEFAULT_TOOL_OWNED))),
17
+ });
18
+ function stemOf(path) {
19
+ return posix.basename(path).replace(/\.[^.]+$/u, "");
20
+ }
21
+ function repeated(stem, folders) {
22
+ if (folders.at(-1) === stem) {
23
+ return undefined;
24
+ }
25
+ const words = wordsOf(stem);
26
+ return folders.find((folder) => wordsOf(folder).some((word) => words.some((own) => sameWord(own, word))));
27
+ }
28
+ function nameFinding(file, stem, folders) {
29
+ const source = parse(file);
30
+ const exports = source === undefined ? [] : ownExports(source);
31
+ const [only] = exports;
32
+ if (only === undefined) {
33
+ return CAMEL.test(stem)
34
+ ? undefined
35
+ : `"${stem}" exports nothing, so it is named in camelCase, such as setupTests.ts. A file that a package.json script or bin runs is kebab-case.`;
36
+ }
37
+ if (exports.length === 1 && only.constant) {
38
+ return `"${stem}" exports only the constant ${only.name}. Constants live in a topic file with related constants, such as limits.ts holding MAX_ITEMS and MAX_DEPTH.`;
39
+ }
40
+ if (exports.some((entry) => exportFits(entry.name, stem, folders))) {
41
+ return undefined;
42
+ }
43
+ const main = mainExport(exports);
44
+ if (main !== undefined) {
45
+ return `"${stem}" does not name its export ${main.name}. ${SHAPES} Rename the file or the export.`;
46
+ }
47
+ return CAMEL.test(stem)
48
+ ? undefined
49
+ : `"${stem}" exports several things, so it is a topic file named in camelCase, such as limits.ts. Or split it so each file has one main export. ${SHAPES}`;
50
+ }
51
+ function findingsOf(file, layout) {
52
+ const stem = stemOf(file.path);
53
+ const folders = layout.foldersOf(file.path);
54
+ if (layout.scripts.has(file.path) || file.text.startsWith(SHEBANG)) {
55
+ return KEBAB.test(stem)
56
+ ? []
57
+ : [{ file: file.path, message: `"${stem}" is run as a script, so it is named in kebab-case, such as build-docs.ts.` }];
58
+ }
59
+ const folder = repeated(stem, folders);
60
+ const messages = [
61
+ nameFinding(file, stem, folders),
62
+ folder === undefined ? undefined : `"${stem}" repeats its folder "${folder}". The folder is the prefix: items/listItems.ts is items/list.ts.`,
63
+ ];
64
+ return messages.flatMap((message) => (message === undefined ? [] : [{ file: file.path, message }]));
65
+ }
66
+ export const namedAfterExport = defineRule({
67
+ check: async ({ files, options, readText, sources }) => {
68
+ const layout = await packageLayout({ files, readText });
69
+ const toolOwned = ignore().add([...options.toolOwned]);
70
+ return sources.filter((file) => !(TEST.test(file.path) || toolOwned.ignores(file.path))).flatMap((file) => findingsOf(file, layout));
71
+ },
72
+ description: "A file is named after its main export, with its folders as the prefix; a file of several exports is a camelCase topic file, and a script is kebab-case. Rename the file or the export; nothing renames them for you.",
73
+ id: "files/named-after-export",
74
+ options: Schema.toStandardSchemaV1(NamedAfterExportOptions, { parseOptions: { errors: "all", onExcessProperty: "error" } }),
75
+ });
76
+ //# sourceMappingURL=namedAfterExport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"namedAfterExport.js","sourceRoot":"","sources":["../../../src/rules/files/namedAfterExport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export declare const otherNames: import("#rule.ts").Rule<"files/other-names", {
2
+ readonly content?: readonly string[];
3
+ readonly toolOwned?: readonly string[];
4
+ }>;
5
+ //# sourceMappingURL=otherNames.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otherNames.d.ts","sourceRoot":"","sources":["../../../src/rules/files/otherNames.ts"],"names":[],"mappings":"AAqGA,eAAO,MAAM,UAAU;;;EAgBrB,CAAC"}
@@ -0,0 +1,99 @@
1
+ import { posix } from "node:path";
2
+ import { Effect, Schema } from "effect";
3
+ import ignore, {} from "ignore";
4
+ import { CAMEL, KEBAB } from "#naming/words.ts";
5
+ import { defineRule } from "#rule.ts";
6
+ const DEFAULT_TOOL_OWNED = [
7
+ ".*",
8
+ "package.json",
9
+ "tsconfig*.json",
10
+ "biome.json",
11
+ "biome.jsonc",
12
+ "*.config.*",
13
+ "migrations/",
14
+ "generated/",
15
+ ];
16
+ const CONVENTIONAL = new Set(["AGENTS", "CHANGELOG", "CLAUDE", "CONTRIBUTING", "LICENSE", "README", "SECURITY", "SKILL"]);
17
+ const KEBAB_KINDS = new Set([
18
+ "avif",
19
+ "gif",
20
+ "grit",
21
+ "ico",
22
+ "jpeg",
23
+ "jpg",
24
+ "json",
25
+ "jsonc",
26
+ "md",
27
+ "mdx",
28
+ "otf",
29
+ "png",
30
+ "svg",
31
+ "ttf",
32
+ "webp",
33
+ "woff",
34
+ "woff2",
35
+ ]);
36
+ const STYLESHEETS = new Set(["css", "scss"]);
37
+ const DOTTED_KEBAB = /^[a-z0-9]+(?:-[a-z0-9]+)*(?:\.[a-z0-9]+(?:-[a-z0-9]+)*)*$/u;
38
+ const SNAKE = /^[a-z0-9]+(?:_[a-z0-9]+)*$/u;
39
+ const STYLESHEET_IMPORT = /["'](?<stylesheet>\.{1,2}\/[^"'\n]+\.s?css)["']/gu;
40
+ const CODE = /\.[cm]?[jt]sx?$/u;
41
+ const OtherNamesOptions = Schema.Struct({
42
+ content: Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed([]))),
43
+ toolOwned: Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed(DEFAULT_TOOL_OWNED))),
44
+ });
45
+ function importers(sources) {
46
+ const found = new Map();
47
+ for (const source of sources) {
48
+ for (const match of source.text.matchAll(STYLESHEET_IMPORT)) {
49
+ const target = posix.normalize(posix.join(posix.dirname(source.path), match.groups?.stylesheet ?? ""));
50
+ found.set(target, [...new Set([...(found.get(target) ?? []), source.path])]);
51
+ }
52
+ }
53
+ return found;
54
+ }
55
+ function stylesheetMessage(path, stem, importedBy) {
56
+ const [only] = importedBy;
57
+ if (importedBy.length === 1 && only !== undefined) {
58
+ const owner = posix.basename(only).replace(CODE, "");
59
+ return stem === owner ? undefined : `"${stem}" is styling for ${posix.basename(only)} alone, so it is named ${owner}${posix.extname(path)}.`;
60
+ }
61
+ return KEBAB.test(stem) ? undefined : `"${stem}" is a shared stylesheet, so it is named in kebab-case, such as form-controls.css.`;
62
+ }
63
+ function messageFor(path, context) {
64
+ const name = posix.basename(path);
65
+ const kind = posix.extname(name).slice(1);
66
+ const stem = name.slice(0, name.length - kind.length - 1);
67
+ if (kind === "" || context.toolOwned.ignores(path) || CONVENTIONAL.has(stem)) {
68
+ return undefined;
69
+ }
70
+ if (context.content.ignores(path)) {
71
+ return SNAKE.test(stem) ? undefined : `"${name}" is content, so it is named in snake_case, such as forest_path.json.`;
72
+ }
73
+ if (STYLESHEETS.has(kind)) {
74
+ return stylesheetMessage(path, stem, context.stylesheets.get(path) ?? []);
75
+ }
76
+ if (kind === "prisma") {
77
+ return CAMEL.test(stem) ? undefined : `"${name}" is a Prisma schema, so it is named in camelCase, such as schema.prisma.`;
78
+ }
79
+ return KEBAB_KINDS.has(kind) && !DOTTED_KEBAB.test(stem)
80
+ ? `"${name}" is named in kebab-case, such as release-notes.md. Only conventional names such as README.md and CHANGELOG.md are upper case.`
81
+ : undefined;
82
+ }
83
+ export const otherNames = defineRule({
84
+ check: ({ files, options, sources }) => {
85
+ const context = {
86
+ content: ignore().add([...options.content]),
87
+ stylesheets: importers(sources),
88
+ toolOwned: ignore().add([...options.toolOwned]),
89
+ };
90
+ return files.flatMap((path) => {
91
+ const message = messageFor(path, context);
92
+ return message === undefined ? [] : [{ file: path, message }];
93
+ });
94
+ },
95
+ description: "Documents, data, assets and GritQL files are kebab-case, a stylesheet that one component imports is named after it, a Prisma schema is camelCase and content is snake_case.",
96
+ id: "files/other-names",
97
+ options: Schema.toStandardSchemaV1(OtherNamesOptions, { parseOptions: { errors: "all", onExcessProperty: "error" } }),
98
+ });
99
+ //# sourceMappingURL=otherNames.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otherNames.js","sourceRoot":"","sources":["../../../src/rules/files/otherNames.ts"],"names":[],"mappings":"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"}