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
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
- [CLI](./reference/cli.md) — commands, flags, output formats, exit codes.
- [konsistent.json configuration](./reference/configuration.md) — top-level schema and convention shape.
- [Path patterns](./reference/path-patterns.md) — globs, placeholders, case transformations, negation.
- [Predicates](./reference/predicates.md) — every `must` predicate (`haveType`, `haveFiles`, `export`, `exportTypes`, `exportConstants`, `exportFunctions`, `exportInterfaces`, `exportClasses`, `import`, `importTypes`).
- [Predicates](./reference/predicates.md) — every `must` predicate (`haveType`, `haveFiles`, `export`, `exportTypes`, `exportConstants`, `exportFunctions`, `exportInterfaces`, `exportClasses`, `import`, `importTypes`, import source predicates).
- [Constraints](./reference/constraints.md) — `matches`, `segments` for filtering placeholders.
- [Conditional rules](./reference/conditional-rules.md) — `if`, `for`, `excludeFiles` blocks inside `must` arrays.
- [Case maps](./reference/case-maps.md) — `kebabToPascalMap`, `kebabToCamelMap` for acronyms and special casing.
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/exploring-codebases.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ For every pattern you identify:
- Required files → `haveFiles`.
- Local declarations → `declareTypes`, `declareConstants`, `declareFunctions`, `declareInterfaces`, `declareClasses`.
- Exports → `export`, `exportTypes`, `exportConstants`, `exportFunctions`, `exportInterfaces`, `exportClasses`.
- Imports → `import`, `importTypes`, `importFromCurrentDir`, `importFromParents`, `importFromExternals`.
- Imports → `import`, `importTypes`, `importFromCurrentDir`, `importFromParents`, `importFromExternals`, `importTypesFromCurrentDir`, `importTypesFromParents`, `importTypesFromExternals`.
- Optional file conditions → `if.hasFile` blocks.
- Subset-only rules → `if.placeholderSatisfies` with a `matches` or `segments` constraint.

Expand Down
45 changes: 39 additions & 6 deletions docs/reference/predicates.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ The full machine-readable schema lives at `node_modules/konsistent/konsistent.sc
- [`importFromCurrentDir`](#importfromcurrentdir)
- [`importFromParents`](#importfromparents)
- [`importFromExternals`](#importfromexternals)
- [`importTypesFromCurrentDir`](#importtypesfromcurrentdir)
- [`importTypesFromParents`](#importtypesfromparents)
- [`importTypesFromExternals`](#importtypesfromexternals)
- [Structural predicates](#structural-predicates)
- [`useDeclarationOrder`](#usedeclarationorder)
- [`areBarrelFiles`](#arebarrelfiles)
Expand Down Expand Up @@ -338,33 +341,63 @@ Same shape as `import`. Useful for enforcing dependency direction — every adap

### `importFromCurrentDir`

Assert whether the file imports from the current directory via `./...`.
Assert whether the file has value imports from the current directory via `./...`.

```json
"must": { "importFromCurrentDir": true }
```

`true` requires at least one import whose module specifier is `"."` or starts with `"./"`. `false` forbids those imports. Both value and type imports count, as do side-effect imports such as `import "./setup"`.
`true` requires at least one non-type import whose module specifier is `"."` or starts with `"./"`. `false` forbids those imports. Type-only imports are ignored. Side-effect imports such as `import "./setup"` count as value imports.

### `importFromParents`

Assert whether the file imports from parent directories via `../...`.
Assert whether the file has value imports from parent directories via `../...`.

```json
"must": { "importFromParents": false }
```

`true` requires at least one import whose module specifier is `".."` or starts with `"../"`. `false` forbids those imports.
`true` requires at least one non-type import whose module specifier is `".."` or starts with `"../"`. `false` forbids those imports. Type-only imports are ignored.

### `importFromExternals`

Assert whether the file imports from external module specifiers.
Assert whether the file has value imports from external module specifiers.

```json
"must": { "importFromExternals": true }
```

`true` requires at least one import that is not `./...` or `../...`. `false` forbids those imports. Scoped packages, `node:` modules, and unresolved path aliases such as `@/utils` count as external because `konsistent` does not resolve module aliases.
`true` requires at least one non-type import that is not `./...` or `../...`. `false` forbids those imports. Scoped packages, `node:` modules, and unresolved path aliases such as `@/utils` count as external because `konsistent` does not resolve module aliases. Type-only imports are ignored.

### `importTypesFromCurrentDir`

Assert whether the file has type-only imports from the current directory via `./...`.

```json
"must": { "importTypesFromCurrentDir": true }
```

`true` requires at least one type import whose module specifier is `"."` or starts with `"./"`. `false` forbids those imports. Value imports are ignored.

### `importTypesFromParents`

Assert whether the file has type-only imports from parent directories via `../...`.

```json
"must": { "importTypesFromParents": false }
```

`true` requires at least one type import whose module specifier is `".."` or starts with `"../"`. `false` forbids those imports. Value imports are ignored.

### `importTypesFromExternals`

Assert whether the file has type-only imports from external module specifiers.

```json
"must": { "importTypesFromExternals": true }
```

`true` requires at least one type import that is not `./...` or `../...`. `false` forbids those imports. Scoped packages, `node:` modules, and unresolved path aliases such as `@/utils` count as external because `konsistent` does not resolve module aliases. Value imports are ignored.

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,24 @@
"name": "no-current-dir-imports",
"paths": "src/current.ts",
"mustNot": {
"importFromCurrentDir": true
"importFromCurrentDir": true,
"importTypesFromCurrentDir": true
}
},
{
"name": "no-parent-imports",
"paths": "src/parent.ts",
"mustNot": {
"importFromParents": true
"importFromParents": true,
"importTypesFromParents": true
}
},
{
"name": "no-external-imports",
"paths": "src/external.ts",
"mustNot": {
"importFromExternals": true
"importFromExternals": true,
"importTypesFromExternals": true
}
}
]
Expand Down
15 changes: 12 additions & 3 deletions e2e/fixtures/import-source-groups-broken/konsistent.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
"must": {
"importFromCurrentDir": true,
"importFromParents": false,
"importFromExternals": false
"importFromExternals": false,
"importTypesFromCurrentDir": true,
"importTypesFromParents": false,
"importTypesFromExternals": false
}
},
{
Expand All @@ -16,7 +19,10 @@
"must": {
"importFromCurrentDir": false,
"importFromParents": true,
"importFromExternals": false
"importFromExternals": false,
"importTypesFromCurrentDir": false,
"importTypesFromParents": true,
"importTypesFromExternals": false
}
},
{
Expand All @@ -25,7 +31,10 @@
"must": {
"importFromCurrentDir": false,
"importFromParents": false,
"importFromExternals": true
"importFromExternals": true,
"importTypesFromCurrentDir": false,
"importTypesFromParents": false,
"importTypesFromExternals": true
}
}
]
Expand Down
2 changes: 2 additions & 0 deletions e2e/fixtures/import-source-groups-broken/shared.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export interface Shared {
value: string;
}

export const sharedValue = "shared";
4 changes: 2 additions & 2 deletions e2e/fixtures/import-source-groups-broken/src/current.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import type { Shared } from "../shared";
import { sharedValue, type Shared } from "../shared";

export type Current = Shared;
export const current = (shared: Shared) => shared.value || sharedValue;
5 changes: 3 additions & 2 deletions e2e/fixtures/import-source-groups-broken/src/external.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { helper } from "./helper";
import { helper, type HelperOptions } from "./helper";

export const external = helper;
export const external = (options: HelperOptions) =>
`${helper}:${options.value}`;
4 changes: 4 additions & 0 deletions e2e/fixtures/import-source-groups-broken/src/helper.ts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export interface HelperOptions {
value: string;
}

export const helper = "helper";
4 changes: 2 additions & 2 deletions e2e/fixtures/import-source-groups-broken/src/parent.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { z } from "zod";
import { z, type ZodType } from "zod";

export const parent = z.string();
export const parent: ZodType = z.string();
9 changes: 6 additions & 3 deletions e2e/fixtures/import-source-groups/konsistent-reverse.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,24 @@
"name": "no-current-dir-imports",
"paths": "src/current.ts",
"mustNot": {
"importFromCurrentDir": true
"importFromCurrentDir": true,
"importTypesFromCurrentDir": true
}
},
{
"name": "no-parent-imports",
"paths": "src/parent.ts",
"mustNot": {
"importFromParents": true
"importFromParents": true,
"importTypesFromParents": true
}
},
{
"name": "no-external-imports",
"paths": "src/external.ts",
"mustNot": {
"importFromExternals": true
"importFromExternals": true,
"importTypesFromExternals": true
}
}
]
Expand Down
15 changes: 12 additions & 3 deletions e2e/fixtures/import-source-groups/konsistent.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
"must": {
"importFromCurrentDir": true,
"importFromParents": false,
"importFromExternals": false
"importFromExternals": false,
"importTypesFromCurrentDir": true,
"importTypesFromParents": false,
"importTypesFromExternals": false
}
},
{
Expand All @@ -16,7 +19,10 @@
"must": {
"importFromCurrentDir": false,
"importFromParents": true,
"importFromExternals": false
"importFromExternals": false,
"importTypesFromCurrentDir": false,
"importTypesFromParents": true,
"importTypesFromExternals": false
}
},
{
Expand All @@ -25,7 +31,10 @@
"must": {
"importFromCurrentDir": false,
"importFromParents": false,
"importFromExternals": true
"importFromExternals": true,
"importTypesFromCurrentDir": false,
"importTypesFromParents": false,
"importTypesFromExternals": true
}
}
]
Expand Down
2 changes: 2 additions & 0 deletions e2e/fixtures/import-source-groups/shared.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export interface Shared {
value: string;
}

export const sharedValue = "shared";
4 changes: 2 additions & 2 deletions e2e/fixtures/import-source-groups/src/current.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { helper } from "./helper";
import { helper, type HelperOptions } from "./helper";

export const current = helper;
export const current = (options: HelperOptions) => `${helper}:${options.value}`;
4 changes: 2 additions & 2 deletions e2e/fixtures/import-source-groups/src/external.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { z } from "zod";
import { z, type ZodType } from "zod";

export const external = z.string();
export const external: ZodType = z.string();
4 changes: 4 additions & 0 deletions e2e/fixtures/import-source-groups/src/helper.ts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export interface HelperOptions {
value: string;
}

export const helper = "helper";
4 changes: 2 additions & 2 deletions e2e/fixtures/import-source-groups/src/parent.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import type { Shared } from "../shared";
import { sharedValue, type Shared } from "../shared";

export type Parent = Shared;
export const parent = (shared: Shared) => shared.value || sharedValue;
3 changes: 3 additions & 0 deletions e2e/must-not.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ describe("mustNot reverse configs", () => {
"Forbidden import from current directory",
"Forbidden import from parent directories",
"Forbidden import from external packages",
"Forbidden type import from current directory",
"Forbidden type import from parent directories",
"Forbidden type import from external packages",
],
});
await expect(
Expand Down
12 changes: 12 additions & 0 deletions e2e/new-predicates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,18 @@ describe("import-source-groups-broken fixture", () => {
expect(error.stdout).toContain(
"Import from current directory is not allowed"
);
expect(error.stdout).toContain(
"Missing type import from current directory"
);
expect(error.stdout).toContain(
"Type import from parent directories is not allowed"
);
expect(error.stdout).toContain(
"Missing type import from external packages"
);
expect(error.stdout).toContain(
"Type import from current directory is not allowed"
);
}
});
});
36 changes: 36 additions & 0 deletions packages/convention/reusable-convention-package.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,15 @@
"importFromExternals": {
"type": "boolean"
},
"importTypesFromCurrentDir": {
"type": "boolean"
},
"importTypesFromParents": {
"type": "boolean"
},
"importTypesFromExternals": {
"type": "boolean"
},
"useDeclarationOrder": {
"type": "array",
"items": {
Expand Down Expand Up @@ -993,6 +1002,15 @@
"importFromExternals": {
"type": "boolean"
},
"importTypesFromCurrentDir": {
"type": "boolean"
},
"importTypesFromParents": {
"type": "boolean"
},
"importTypesFromExternals": {
"type": "boolean"
},
"useDeclarationOrder": {
"type": "array",
"items": {
Expand Down Expand Up @@ -1533,6 +1551,15 @@
"importFromExternals": {
"type": "boolean"
},
"importTypesFromCurrentDir": {
"type": "boolean"
},
"importTypesFromParents": {
"type": "boolean"
},
"importTypesFromExternals": {
"type": "boolean"
},
"useDeclarationOrder": {
"type": "array",
"items": {
Expand Down Expand Up @@ -1992,6 +2019,15 @@
"importFromExternals": {
"type": "boolean"
},
"importTypesFromCurrentDir": {
"type": "boolean"
},
"importTypesFromParents": {
"type": "boolean"
},
"importTypesFromExternals": {
"type": "boolean"
},
"useDeclarationOrder": {
"type": "array",
"items": {
Expand Down
3 changes: 3 additions & 0 deletions packages/convention/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ describe("ReusableConventionV1Schema", () => {
importFromCurrentDir: true,
importFromParents: false,
importFromExternals: true,
importTypesFromCurrentDir: true,
importTypesFromParents: false,
importTypesFromExternals: true,
},
});
expect(result.success).toBe(true);
Expand Down
3 changes: 3 additions & 0 deletions packages/convention/src/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ export const MustPredicatesV1Schema = z.strictObject({
importFromCurrentDir: z.boolean().optional(),
importFromParents: z.boolean().optional(),
importFromExternals: z.boolean().optional(),
importTypesFromCurrentDir: z.boolean().optional(),
importTypesFromParents: z.boolean().optional(),
importTypesFromExternals: z.boolean().optional(),
useDeclarationOrder: z.array(z.string()).optional(),
areBarrelFiles: z.boolean().optional(),
});
Expand Down
Loading