@morev/bem/match-file-name
Requires the file name to begin with the name of the BEM block it represents.
// ✅ The file name matches the name of the block
.the-component {}// ✅ The file name begins with the block name
.the-component {}// ❌ The file name does not match the name of the block
.the-component {}🧱 How the BEM block is determined
Some BEM linters — like postcss-bem-linter — require you to define the block name explicitly using a comment (/** @define the-component */) or derive it from the filename via configuration.
This plugin takes a different, much simpler approach:
The first class selector in the file is considered the BEM block.
✅ Why this is enough
In a component-oriented architecture, there is rarely a reason to define more than one block per file. Assuming that the first top-level class represents the component's block is usually both practical and predictable — without requiring additional annotations or configuration.
It also avoids coupling the rule to naming conventions or file structure.
❓ What if you need more control?
If your project uses a different structure, this assumption may not work well.
This plugin doesn't currently support custom block resolution logic or in-file annotations — mostly because there's little evidence it's needed.
But if you have a real use case — feel free to open an issue and describe your use case.
Motivation
A well-structured component (especially in modern component-based frameworks) follows the principle of separation of concerns - separate files for markup, styles, documentation, i18n, tests, and so on:
/the-component
├── the-component.vue
├── the-component.scss
├── the-component.i18n.yaml
├── the-component.test.ts
├── the-component.stories.ts
└── ...This approach is especially valuable when using the BEM methodology.
If a component's name matches the name of a BEM block (as it should), it makes navigating the project much easier: when we see a block named "the-component" in the HTML, we can be 100% sure there's a corresponding the-component.scss file containing its styles. We know that it is the only source of truth for the component, and we can jump to it instantly.
It also improves the DX - different style files can be edited independently, and IDE tabs show clear, descriptive names instead of a dozen identical index.scss files.
Rule options
All options are optional and come with recommended default values.
// 📄 .stylelintrc.js
export default {
plugins: ['@morev/stylelint-plugin'],
rules: {
'@morev/bem/match-file-name': true,
}
}// 📄 .stylelintrc.js
export default {
plugins: ['@morev/stylelint-plugin'],
rules: {
'@morev/bem/match-file-name': [true, {
caseSensitive: true,
matchDirectory: false,
messages: {
match: (entity, blockName) =>
`The ${entity} name must start with its block name: "${blockName}"`,
matchCase: (entity, blockName) =>
`The ${entity} name must start with its block name: "${blockName}", including case`,
}
}],
}
}Show full type of the options
export type MatchFileNameOptions = {
/**
* Whether the comparison should be case-sensitive.
*
* If `true`, the file or directory name must match the block name exactly,
* including character case. If `false`, case is ignored.
*
* @default true
*/
caseSensitive?: boolean;
/**
* Whether to use the name of the containing directory instead of the file name
* for block name comparison.
*
* This is useful when using a folder-based structure like:
* `/components/the-component/index.scss`
*
* @default false
*/
matchDirectory?: boolean;
/**
* Custom message functions for rule violations.
* If provided, overrides the default error messages.
*/
messages?: {
/**
* Custom message for when the name does not match the block name.
*
* @param entity Either `'file'` or `'directory'`, depending on which name is being checked.
* @param blockName The name of the BEM block.
*
* @returns The error message to report.
*/
match?: (entity: 'file' | 'directory', blockName: string) => string;
/**
* Custom message for when the name matches the block name structurally
* but fails due to case mismatch (if `caseSensitive: true`).
*
* @param entity Either `'file'` or `'directory'`, depending on which name is being checked.
* @param blockName The name of the BEM block.
*
* @returns The error message to report.
*/
matchCase?: (entity: 'file' | 'directory', blockName: string) => string;
};
};Show info about Stylelint-wide options
Every rule in this plugin also supports the standard Stylelint per-rule options (disableFix, severity, url, reportDisables, and message), even though they are not explicitly reflected in the type definitions to avoid unnecessary noise.
Note: the message option is technically available, but its use is discouraged: each rule already provides a typed messages object, which not only offers IDE autocompletion but also supports multiline strings and automatically handles indentation.
For more information, see the official Stylelint configuration docs.
caseSensitive
Requires the beginning of the file name to match the block name exactly, including case sensitivity.
/**
* @default true
*/
type CaseSensitiveOption = boolean;Examples
// ✅ The file name begins with the block name
// 📄 the-component.scss
.the-component {}
// ✅ The file name begins with the block name
// 📄 the-component.styles.scss
.the-component {}
// ❌ The file name matches the block name, but case is different.
// 📄 the-component.scss
.TheComponent {}
// ❌ The file name does not begin with the block name
// 📄 index.scss
.foo-component {}// ✅ The file name begins with the block name
// 📄 the-component.scss
.the-component {}
// ✅ The file name begins with the block name
// 📄 the-component.styles.scss
.the-component {}
// ✅ The file name matches the block name, but case is different.
// 📄 the-component.scss
.TheComponent {}
// ❌ The file name does not begin with the block name
// 📄 index.scss
.foo-component {}matchDirectory
Whether to use the name of the containing directory instead of the file name for block name comparison.
/**
* @default false
*/
type MatchDirectoryOption = boolean;Examples
// ✅ The directory name matches the block name
// 📄 /the-component/index.scss
.the-component {}
// ❌ The directory name does not match the block name
// 📄 /bar-component/index.scss
.the-component {}Notes
- The
caseSensitiveoption applies regardless of whethermatchDirectoryis enabled. - When both options are enabled, the rule compares the directory name to the block name, respecting case sensitivity if
caseSensitiveistrue.
messages
The rule provides built-in error messages for all violations it detects.
You can customize them using the messages option. This can be useful to:
- Adjust the tone of voice to match your team's style;
- Translate messages into another language;
- Provide additional project-specific context or documentation links.
INFO
You don't need to override all message functions — or any of them at all.
Message functions receive the checked entity type ("file" or "directory") and the BEM block name as arguments.
Example
export default {
plugins: ['@morev/stylelint-plugin'],
rules: {
'@morev/bem/match-file-name': [true, {
messages: {
match: (entity, block) =>
`🚫 The ${entity} name must start with "${block}".`,
matchCase: (entity, block) =>
`🔠 The ${entity} name must exactly match "${block}" including case.`,
},
}],
},
}Show function signatures
export type MessagesOption = {
/**
* Custom message for when the name does not match the block name at all.
*
* @param entity Either `'file'` or `'directory'`, depending on which name is being checked.
* @param blockName The name of the BEM block.
*
* @returns The error message to report.
*/
match?: (entity: 'file' | 'directory', blockName: string) => string;
/**
* Custom message for when the name structurally matches the block name,
* but fails due to a case mismatch (if `caseSensitive: true`).
*
* @param entity Either `'file'` or `'directory'`, depending on which name is being checked.
* @param blockName The name of the BEM block.
*
* @returns The error message to report.
*/
matchCase?: (entity: 'file' | 'directory', blockName: string) => string;
};How message formatting works
If your custom message function returns anything other than a string (e.g., undefined), the rule will automatically fall back to the default built-in message.
Additionally, all custom messages are automatically processed through stripIndent function, so it's safe and recommended to use template literals (backticks, `) for multiline messages without worrying about inconsistent indentation.