Skip to content
Draft
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
33 changes: 33 additions & 0 deletions docs/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,39 @@ These options have sensible defaults and are typically only customized for speci

</details>

#### `excludeFromChecks` {#excludefromchecks}

- **Type**: `(string | RegExp)[]`
- **Default**: `[]`
- **Description**: Patterns to exclude from ALL Sheriff rule enforcement. Files matching these patterns are still processed and traversed for dependency analysis, but no rule violations are reported. Supports glob patterns and regular expressions.

**Key Distinction: `ignoreFileExtensions` vs `excludeFromChecks`**

The `ignoreFileExtensions` option makes Sheriff skip parsing and traversing certain file types, such as `.scss`, because the TypeScript parser cannot read them. This means dependencies inside those files are completely hidden from Sheriff.

In contrast, `excludeFromChecks` disables rule enforcement for specific paths while still parsing and traversing them. Excluded files themselves are not checked, but any files they import remain visible to Sheriff. This ensures that non-excluded code imported through excluded paths is still validated.

**Examples:**

```typescript
export const config: SheriffConfig = {
excludeFromChecks: [
'src/client/**', // Skip all rule checks for generated client
'src/generated/**', // Skip all rule checks for generated files
'src/**/*.gen.ts', // Skip all rule checks for .gen.ts files
/src\/.*\.gen\.ts$/, // Regex pattern for .gen.ts files
'src/legacy/**' // Skip all rule checks for legacy code
],
// ... other config options
};
```

**Use Cases:**

- **Legacy code migration**: Existing codebases that haven't been refactored to follow strict module boundaries yet need time to gradually adopt Sheriff's rules
- **Third-party generated code**: Users import from generated SDKs or client libraries that have internal dependencies (e.g., OpenAPI clients)
- **Temporary exclusions**: During refactoring phases where certain areas need to be temporarily excluded from rule enforcement

### Legacy Options

#### `excludeRoot` {#excluderoot}
Expand Down
44 changes: 44 additions & 0 deletions docs/docs/release-notes/0.20.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,47 @@ This feature is particularly useful when:
No migration is required. Existing configurations will continue to work with the new default ignored extensions. If you want to customize the ignored extensions, simply add the `ignoreFileExtensions` option to your configuration.

For more details, see the [Configuration Reference](../configuration.md#ignorefileextensions).

## New Feature: Exclude Files from Rule Checks

Sheriff now supports excluding specific files or patterns from all rule enforcement through the new `excludeFromChecks` configuration option.

### What it does

The `excludeFromChecks` option allows you to exclude specific file paths or patterns from Sheriff's rule enforcement while still maintaining dependency analysis. This is particularly useful for legacy code, generated files, or areas of your codebase that need time to gradually adopt Sheriff's rules.

### Key Distinction: `ignoreFileExtensions` vs `excludeFromChecks`

- **`ignoreFileExtensions`**: Makes Sheriff skip parsing and traversing certain file types (e.g., `.scss` files that TypeScript can't read)
- **`excludeFromChecks`**: Disables rule enforcement for specific paths while still parsing and traversing them for dependency analysis

### Configuration Options

You can use a combination of glob patterns and regular expressions:

```typescript
export const config: SheriffConfig = {
excludeFromChecks: [
'src/client/**', // Skip all rule checks for generated client
'src/generated/**', // Skip all rule checks for generated files
'src/**/*.gen.ts', // Skip all rule checks for .gen.ts files
/src\/.*\.gen\.ts$/, // Regex pattern for .gen.ts files
'src/legacy/**' // Skip all rule checks for legacy code
],
// ... other configuration
};
```

### Use Cases

This feature is particularly useful when:

- **Legacy code migration**: Existing codebases that haven't been refactored to follow strict module boundaries yet
- **Third-party generated code**: Importing from generated SDKs or client libraries with internal dependencies
- **Temporary exclusions**: During refactoring phases where certain areas need to be temporarily excluded

### Migration

No migration is required. Existing configurations will continue to work. The `excludeFromChecks` option defaults to an empty array, meaning no files are excluded by default.

For more details, see the [Configuration Reference](../configuration.md#excludefromchecks).
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { FsPath, toFsPath } from '../file-info/fs-path';
import { ProjectInfo } from '../main/init';
import { calcTagsForModule } from '../tags/calc-tags-for-module';
import { isDependencyAllowed } from './is-dependency-allowed';
import { isExcludedFromChecks } from '../util/is-excluded-from-checks';

export type DependencyRuleViolation = {
rawImport: string;
Expand All @@ -21,6 +22,11 @@ export function checkForDependencyRuleViolation(
return [];
}

// Skip checks for excluded files
if (isExcludedFromChecks(fsPath, config.excludeFromChecks)) {
return [];
}

const assignedFileInfo = getFileInfo(fsPath);
const importedModulePathsWithRawImport = assignedFileInfo.imports
// skip imports of same module
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/lib/checks/has-encapsulation-violations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Configuration } from '../config/configuration';
import { ProjectInfo } from '../main/init';
import { FileInfo } from '../modules/file.info';
import getFs from '../fs/getFs';
import { isExcludedFromChecks } from '../util/is-excluded-from-checks';

/**
* verifies if an existing file has imports which break
Expand All @@ -17,6 +18,12 @@ export function hasEncapsulationViolations(
{ rootDir, config, getFileInfo }: ProjectInfo,
): Record<string, FileInfo> {
const encapsulationViolations: Record<string, FileInfo> = {};

// Skip checks for excluded files
if (isExcludedFromChecks(fsPath, config.excludeFromChecks)) {
return encapsulationViolations;
}

const assignedFileInfo = getFileInfo(fsPath);

for (const importedFileInfo of assignedFileInfo.imports) {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/lib/config/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ export type Configuration = Required<
entryPoints?: Record<string, string>;
// ignoreFileExtensions is always present (either user-specified or default)
ignoreFileExtensions: string[];
// excludeFromChecks is always present (either user-specified or default)
excludeFromChecks: (string | RegExp)[];
};
1 change: 1 addition & 0 deletions packages/core/src/lib/config/default-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export const defaultConfig: Configuration = {
barrelFileName: 'index.ts',
entryPoints: undefined,
ignoreFileExtensions: defaultIgnoreFileExtensions,
excludeFromChecks: [],
};
11 changes: 11 additions & 0 deletions packages/core/src/lib/config/parse-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,14 @@ export const parseConfig = (configFile: FsPath): Configuration => {
mergedConfig.ignoreFileExtensions,
);

const excludeFromChecks = getExcludeFromChecks(
mergedConfig.excludeFromChecks,
);

return {
...mergedConfig,
ignoreFileExtensions,
excludeFromChecks,
};
};

Expand All @@ -82,3 +87,9 @@ function getIgnoreFileExtensions(
: ignoreFileExtensions;
return Array.from(new Set(extensions.map((ext) => ext.toLowerCase())));
}

function getExcludeFromChecks(
excludeFromChecks: (string | RegExp)[] | undefined,
): (string | RegExp)[] {
return excludeFromChecks ?? [];
}
77 changes: 77 additions & 0 deletions packages/core/src/lib/config/tests/parse-config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,4 +367,81 @@ export const config: SheriffConfig = {
);
});
});

describe('excludeFromChecks', () => {
it('should use empty array when excludeFromChecks is not provided', () => {
getFs().writeFile(
'sheriff.config.ts',
`
import { SheriffConfig } from '@softarc/sheriff-core';

export const config: SheriffConfig = {
depRules: { root: 'noTag', noTag: 'noTag' }
};
`,
);
const config = parseConfig(
toFsPath(getFs().cwd() + '/sheriff.config.ts'),
);
expect(config.excludeFromChecks).toEqual([]);
});

it('should parse string patterns correctly', () => {
getFs().writeFile(
'sheriff.config.ts',
`
import { SheriffConfig } from '@softarc/sheriff-core';

export const config: SheriffConfig = {
excludeFromChecks: ['src/client/**', 'src/generated/**'],
depRules: { root: 'noTag', noTag: 'noTag' }
};
`,
);
const config = parseConfig(
toFsPath(getFs().cwd() + '/sheriff.config.ts'),
);
expect(config.excludeFromChecks).toEqual(['src/client/**', 'src/generated/**']);
});

it('should parse regex patterns correctly', () => {
getFs().writeFile(
'sheriff.config.ts',
`
import { SheriffConfig } from '@softarc/sheriff-core';

export const config: SheriffConfig = {
excludeFromChecks: [/src\/.*\.gen\.ts$/, /.*\.spec\.ts$/],
depRules: { root: 'noTag', noTag: 'noTag' }
};
`,
);
const config = parseConfig(
toFsPath(getFs().cwd() + '/sheriff.config.ts'),
);
expect(config.excludeFromChecks).toEqual([/src\/.*\.gen\.ts$/, /.*\.spec\.ts$/]);
});

it('should parse mixed patterns correctly', () => {
getFs().writeFile(
'sheriff.config.ts',
`
import { SheriffConfig } from '@softarc/sheriff-core';

export const config: SheriffConfig = {
excludeFromChecks: ['src/client/**', /src\/.*\.gen\.ts$/, 'src/legacy/**'],
depRules: { root: 'noTag', noTag: 'noTag' }
};
`,
);
const config = parseConfig(
toFsPath(getFs().cwd() + '/sheriff.config.ts'),
);
expect(config.excludeFromChecks).toEqual([
'src/client/**',
/src\/.*\.gen\.ts$/,
'src/legacy/**'
]);
});
});
});
19 changes: 19 additions & 0 deletions packages/core/src/lib/config/user-sheriff-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,4 +280,23 @@ export interface UserSheriffConfig {
* ```
*/
ignoreFileExtensions?: string[] | ((defaults: string[]) => string[]);

/**
* Patterns to exclude from ALL Sheriff rule enforcement.
* Files matching these patterns are still processed and traversed
* for dependency analysis, but no rule violations are reported.
* Supports glob patterns and regular expressions.
*
* @example
* ```typescript
* excludeFromChecks: [
* 'src/client/**', // Skip all rule checks for generated client
* 'src/generated/**', // Skip all rule checks for generated files
* 'src/**/*.gen.ts', // Skip all rule checks for .gen.ts files
* /src\/.*\.gen\.ts$/, // Regex pattern for .gen.ts files
* 'src/legacy/**' // Skip all rule checks for legacy code
* ]
* ```
*/
excludeFromChecks?: (string | RegExp)[];
}
Loading
Loading