# core/stylelint/consistent-naming.stylelint.rule.ts

> 92 lines of code and 13 definitions.

Tree: GovLab Quality
Language: typescript
Canonical: https://banes-lab.com/anatomy/quality#file-quality-core-stylelint-consistent-naming-stylelint-rule-ts
Source text: https://banes-lab.com/source/quality/core/stylelint/consistent-naming.stylelint.rule.ts.txt

Listed in [core/stylelint](https://banes-lab.com/api/source/quality/core/stylelint.md), before [core/stylelint/custom-property-order.stylelint.rule.ts](https://banes-lab.com/source/quality/core/stylelint/custom-property-order.stylelint.rule.ts.md).

## Definitions

- `hasUpperCase` (lexical_declaration, line 34)
- `isKebabSegment` (lexical_declaration, line 43)
- `{ createPlugin, utils }` (lexical_declaration, line 8)
- `ruleName` (lexical_declaration, line 9)
- `ruleId` (lexical_declaration, line 10)
- `RULE_META` (lexical_declaration, line 12, exported)
- `messages` (lexical_declaration, line 22)
- `MAX_BEM_PARTS` (lexical_declaration, line 32)
- `i` (lexical_declaration, line 47)
- `isValidBem` (lexical_declaration, line 55)
- `elementParts` (lexical_declaration, line 56)
- `modifierParts` (lexical_declaration, line 61)
- `rule` (lexical_declaration, line 69)

## Contained in

- [core/stylelint](https://banes-lab.com/anatomy/quality/folder-quality-core-stylelint.md)

## Uses

- [core/predicates/code-point.predicate.ts](https://banes-lab.com/source/quality/core/predicates/code-point.predicate.ts.md)

## Linked from

- [core/predicates](https://banes-lab.com/anatomy/quality/folder-quality-core-predicates.md)

## Source

```typescript
import type { Rule as CssRule, Root } from "postcss";
import { isAsciiLower, isAsciiUpper, isKebabChar } from "#core/predicates/code-point.predicate";
import stylelint, { type PostcssResult, type Rule } from "stylelint";
import type { GovlabStylelintMeta } from "#types/stylelint.types";
import { extractClassNames } from "#core/parsers/css.parser";
import { withRuleId } from "#core/formatters/stylelint.formatter";

const { createPlugin, utils } = stylelint;
const ruleName = "govlab/consistent-naming";
const ruleId = "consistent_naming";

export const RULE_META: GovlabStylelintMeta = {
    meta: {
        canonical: ["naming-convention"],
        description: "Require lowercase-kebab-case CSS class names (underscores only as valid BEM)",
        fixable: false,
    },
    ruleId,
    ruleName,
};

const messages = utils.ruleMessages(ruleName, {
    underscore: (name: string): string =>
        withRuleId(
            `Class '${name}' uses underscores outside valid BEM. Use kebab-case (hyphens), not underscores.`,
            ruleId,
        ),
    upperCase: (name: string): string =>
        withRuleId(`Class '${name}' has uppercase letters. Use lowercase-kebab-case for CSS class names.`, ruleId),
});

const MAX_BEM_PARTS = 2;

const hasUpperCase = function hasUpperCase(name: string): boolean {
    for (let i = 0; i < name.length; i += 1) {
        if (isAsciiUpper(name.codePointAt(i))) {
            return true;
        }
    }
    return false;
};

const isKebabSegment = function isKebabSegment(segment: string): boolean {
    if (segment.length === 0 || !isAsciiLower(segment.codePointAt(0))) {
        return false;
    }
    for (let i = 1; i < segment.length; i += 1) {
        if (!isKebabChar(segment.codePointAt(i))) {
            return false;
        }
    }
    return true;
};

const isValidBem = function isValidBem(name: string): boolean {
    const elementParts = name.split("__");
    if (elementParts.length > MAX_BEM_PARTS) {
        return false;
    }
    for (const part of elementParts) {
        const modifierParts = part.split("--");
        if (modifierParts.length > MAX_BEM_PARTS || !modifierParts.every(isKebabSegment)) {
            return false;
        }
    }
    return true;
};

const rule: Rule = Object.assign(
    (primary: unknown) =>
        (root: Root, result: PostcssResult): void => {
            if (primary !== true) {
                return;
            }
            root.walkRules((cssRule: CssRule) => {
                for (const name of extractClassNames(cssRule.selector)) {
                    if (hasUpperCase(name)) {
                        utils.report({
                            message: messages.upperCase(name),
                            node: cssRule,
                            result,
                            ruleName,
                            word: name,
                        });
                    }
                    if (name.includes("_") && !isValidBem(name)) {
                        utils.report({
                            message: messages.underscore(name),
                            node: cssRule,
                            result,
                            ruleName,
                            word: name,
                        });
                    }
                }
            });
        },
    { messages, ruleName },
);

export default createPlugin(ruleName, rule);
```
