feat(core-eslint): add entity-must-have-test and no-relative-parent-import rules
Two CLAUDE.md conventions had no mechanical gate, so both drifted: entity models shipped without sibling tests, and feature test files imported src modules via `../` instead of the `@/` alias. - `entity-must-have-test` — every entities/models/<x>.ts needs a sibling <x>.test.ts (errors and barrels excluded). - `no-relative-parent-import-in-tests` — feature test files must import src via `@/`, not `../`. Scoped to feature packages; core packages are governed by their own generator templates. Both register at warn level, bringing the conformance rule count to 15.
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Feature test files import from `src/` through the `@/` alias, never via
|
||||
* `../` parent traversal (CLAUDE.md "Key Conventions"). A `../` import in a
|
||||
* test file is always reaching across `src/` directories — `@/` keeps those
|
||||
* imports stable under file moves and makes the test's dependencies legible.
|
||||
*
|
||||
* Scoped to feature packages (`packages/<name>/src/`, excluding `core-*`):
|
||||
* the convention is part of the feature template's contract. Core packages
|
||||
* are generated and governed by their own templates, and tooling packages
|
||||
* (turbo/generators, scripts) legitimately use relative paths.
|
||||
*/
|
||||
function isFeatureSrcTestFile(filename) {
|
||||
const normalized = filename.replace(/\\/g, "/");
|
||||
if (!normalized.endsWith(".test.ts") && !normalized.endsWith(".test.tsx")) {
|
||||
return false;
|
||||
}
|
||||
return /\/packages\/(?!core-)[^/]+\/src\//.test(normalized);
|
||||
}
|
||||
|
||||
/** @type {import("eslint").Rule.RuleModule} */
|
||||
export default {
|
||||
meta: {
|
||||
type: "problem",
|
||||
docs: {
|
||||
description:
|
||||
"Feature test files must import src modules via the @/ alias, not ../ parent paths.",
|
||||
},
|
||||
schema: [],
|
||||
messages: {
|
||||
relativeParentImport:
|
||||
'Test file imports "{{source}}" with a ../ parent path. Use the "@/" alias for src imports (e.g. "@/application/...").',
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
if (!isFeatureSrcTestFile(context.filename)) return {};
|
||||
return {
|
||||
ImportDeclaration(node) {
|
||||
const source = node.source.value;
|
||||
if (typeof source === "string" && source.startsWith("../")) {
|
||||
context.report({
|
||||
node: node.source,
|
||||
messageId: "relativeParentImport",
|
||||
data: { source },
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user